whyoung
5/14/2017 - 12:36 AM

[Java字符串连接] #tags:java

[Java字符串连接] #tags:java

字符串的连接的底层实现?

String a = "a";
String b = "b";
String c = "ab";
String d = "a" + "b";
String e = a + b;
String f = "a" + b; //or a + "b"    
        
System.out.println(c == d);
System.out.println(c == e);
System.out.println(c == f);
System.out.println(e == f); 

执行结果:

true
false
false
false

从执行结果来看,"ab"和"a" + "b"是完全相同的,内存的地址一样。但a + b"a" + b和"ab"是不同的对象。

分析: 对于像"a" + "b"这种字符串常量的"+"号连接,在程序编译期,JVM将常量字符串的"+"连接优化为连接后的值,"a" + "b"在经编译器优化后在class中是"ab"。在编译期其字符串常量的值就确定下来。但c == d见常量池部分的分析。

但对于字符串引用,引用的值在程序编译期是无法确定的a + b"a"+b无法被编译器优化,只有在程序运行期来动态分配。

底层的实现逻辑如下:

1.调用StringBuilder的构造方法,创建一个StringBuilder对象

/**
 * Constructs a string builder with no characters in it and an
 * initial capacity of 16 characters.
 */
public StringBuilder() {
    super(16);
}

2.调用StringBuilder的append方法

@Override
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

3.调用StringBuilder的toString方法,返回String对象

@Override
public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

综上可知,a + b 或者 "a" + b的执行过程中创建类两个对象:StringBuilder对象和String对象

注意

但对于如下代码:

final String h = "b";
String s = "a" + h;
System.out.println(c == s);

程序的执行结果为true,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + h"a" + "b"效果是一样的。

常量池

继续上面的代码

System.out.println(c == d);
d = d.intern();
System.out.println(c == d);

输出结果

false
true

String类的intern方法的源码

public native String intern();

方法注释:

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

intern是个native方法,从注释来看,当执行intern方法的时候,如果池中已经存在和这个字符串相等的字符串(根据String方法的equals方法判断),就返回池中的这个字符串。否则,这个对象将被添加到池中,并返回这个对象的引用。

引用《深入理解Java虚拟机:JVM高级特性与最佳实践》:

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信 息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在 类加载后进入方法区的运行时常量池中存放。

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。

"ab"、"a"、"b"这些都属于java中的常量,对于"a" + "b",由上面的分析可知,编译器会将代码直接优化成"ab",而"ab"在常量池中已存在,"ab" == "a" + "b"的结果是true。

String t = new String("ab");
System.out.println("ab" == t);

执行的结果返回false

String类的构造方法的源码:

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

JVM虚拟机通过new生成的对象存放在堆内存空间中,而常量存放在方法区,所以一定是不同的对象,只是对象中的字符串数组和方法区中的常量数组是同一个。