java基础(3)--详解String

java基础(3)--详解String

其实与八大基本数据类型一样,String也是我们日常中使用非常频繁的对象,但知其然更要知其所以然,现在就去阅读源码深入了解一下String类对象,并解决一些我由来已久的疑问。

为什么String是不可变序列

  • String类使用final修饰的,所以不可以通过继承方式来修改String
  • String类字符串存储是使用final修饰的value数组实现的,所以不可以通过修改value数组来修改String。但是数组其实也是一个引用,那能不能通过修改引用来达到修改String的目的?
    下面来看个例子:
public class Test03 {
    public static void main(String[] args) {
        char[] arr = new char[]{'a', 'b', 'c', 'd'};
        String s = new String(arr);
        arr[0] = 'b';
        System.out.println(s);
    }
}
abcd

那为什么不能通过修改引用来修改String呢,看看源码

 public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

String通过拷贝数组保证了本身不会被修改

  • String类的方法返回的字符串只要不是原字符串就会new一个对象。
public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

还有等等......

String类用"=="进行比较的问题

看下面一段代码

public class Test03 {
    public static void main(String[] args) {
        String s1 = "helloWorld";
        String s2 = "hello" + "World";
        String s3 = "helloWorld";
        String s4 = "hello";
        String s5 = "World";
        final String s6 = "World";
        String s7 = s4 + s5;
        String s8 = "hello" + s6;

        String str1 = new String("helloWorld");
        String str2 = new String("helloWorld");

        System.out.println(s1 == s2);
        System.out.println(s1 == s3);
        System.out.println(s1 == s7);
        System.out.println(s1 == s8);

        System.out.println(str1 == str2);
        System.out.println(str2 == s1);
    }
}
true
true
false
true
false
false

一共进行了5次比较,现在一一道来

  • 第一次和第二次之所以都为true,是因为两者的引用都指向了字符串常量池中的"helloWorld",虚拟机会在编译期间进行优化,字面常量相”+“会直接进行拼接而不是调用其他方法。
  • 当字符串拼接存在不是字符常量而是一个变量的时候,虚拟机在编译期间不会将其进行优化,而是用会在堆上生成一个对象,所以第三个比较是false;但如果String对象使用final修饰,那么他就是不可修改的常量,这时等同于是字符常量拼接,虚拟机才会无顾忌的将其优化,所以第四个比较为true。
  • 最后一部分对象的比较就比较容易了,由于是在堆上分配内存的,所以内容是相等的,但是地址引用并不相等,所以都为false。

StringBuilder和StringBuffer

由于String本身不可修改,所以Java提供了StirngBuiler和StringBuffer来弥补这一空缺。现在String之间使用”+“的使用的时候实际上是调用StringBuilder的append方法完成的。
先看一个例子

public class Test03 {
    public static void main(String[] args) {
        String s = "hello";
       for (int i = 0; i < 100; i++){
           s += "hello";
       }
    }
}

再看看反编译

Compiled from "Test03.java"
public class base.Test03 {
  public base.Test03();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String hello
       2: astore_1
       3: iconst_0
       4: istore_2
       5: iload_2
       6: bipush        100
       8: if_icmpge     37
      11: new           #3                  // class java/lang/StringBuilder
      14: dup
      15: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      18: aload_1
      19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: ldc           #2                  // String hello
      24: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      27: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      30: astore_1
      31: iinc          2, 1
      34: goto          5
      37: return
}

可以看出实际上在进行字符串拼接的时候,是调用了append方法进行拼接,再用toString方法返回String对象。

StringBuilder和StringBuffer的不同

StringBuilder是线程不安全的,但是速度快,StringBuffer是线程安全的,他的方法都是用synchronized修饰的,但是速度慢

String,StringBuilder,StringBuffer的效率比较

下面用代码验证一下

String

public class Test03 {
    public static void main(String[] args) {
        Long start = System.currentTimeMillis();
        String s = "hello";
        for (int i = 0; i < 10000; i++) {
            s += "world";
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}
713

StringBuilder

public class Test03 {
    public static void main(String[] args) {
        Long start = System.currentTimeMillis();
        StringBuilder s = new StringBuilder("hello");
        for (int i = 0; i < 10000; i++) {
            s = s.append("world");
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}
2

StringBuffer

public class Test03 {
    public static void main(String[] args) {
        Long start = System.currentTimeMillis();
        StringBuffer s = new StringBuffer("hello");
        for (int i = 0; i < 10000; i++) {
            s = s.append("world");
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}
6

显然StringBuilder > StringBuffer > String;但是在相加字符串较少的时候还是用String效率高点。

猜你喜欢

转载自www.cnblogs.com/curiousforcode/p/12003546.html