String杂谈
String s = new String(“abc”)产生几个对象?
首先会检查常量池中是否有“abc”这个对象
- 如果没有,则先在常量池中创建“abc”对象,然后在堆中创建String对象,其中的value指向常量池中的“abc”
- 如果有,则只在堆中创建String对象
总结:常量池中没有“abc”对象则会创建2个对象,常量池中有“abc”对象则会创建1个对象。
关于String.inner()方法
先说结论:
jdk1.7之前,intern()方法会把首次遇到的字符串实例复制到方法区的常量池中,然后返回方法区中这个字符串实例的引用。
jdk1.7之后,intern()方法不再采取复制的方式,而是在常量池中记录首次出现的实例的引用。
String类的intern()方法描述:
String类维护着一个初始化为空的字符串池,当某String实例调用intern()方法的时候,如果字符串池中已包含与此字符串相同的字符串(用equal(obj)方法判断相等),则返回池中的字符串对象。否则,将此字符串对象加入到常量池中,并返回此字符串对象的引用。对于任意两个字符串s和t,当切仅当s.equals(t)为true,s.intern() ==t.intern()才为true。所有字面值字符串和字符串赋值常量表达式都使用intern方法进行处理。
例如字符串为”abc“,则只要程序中使用到”abc“这个值时都会使用intern()方法处理,其目的还是为了重用常量值。
运行环境:java version “11.0.4” 2019-07-16 LTS
String s = new String("ab") + new String("c");
System.out.println(s.intern() == s);
System.out.println(s.intern() == "abc");
System.out.println(s == "abc");
true
true
true
首先执行new String("ab")
时,会先在常量池中检查是否有“ab”对象,如果没有则会在常量池中创建“ab”对象,然后在堆中创建String对象。new String("c")
同理。
在Java中对于String的‘+’操作是由StringBuilder调用append()操作完成的,StringBuilder继承自AbstractStringBuilder,对于StringBuilder的操作大部分是调用父类方法来完成的。在AbstractStringBuilder中,有一个名为value的byte数组,用来保存字符串ASCII值(String类内部也有value成员变量起着同样的作用)。
调用append()方法时,先得到要添加的字符串的byte数组,将其添加到StringBuilder的byte数组中(其实是AbstractStringBuilder中的成员变量),最后调用StringBuiler.toString()方法时,会用byte数组作为参数来new一个String对象。需要注意的是用byte数组来创建String对象不会去常量池中做检查,也不会在常量池中创建对象,这种方式只是单纯的在堆上new一个String对象。
StringBuilder的toString()方法
@Override
@HotSpotIntrinsicCandidate
public String toString() {
// Create a copy, don't share the array
return isLatin1() ? StringLatin1.newString(value, 0, count)
: StringUTF16.newString(value, 0, count);
}
public static String newString(byte[] val, int index, int len) {
return new String(Arrays.copyOfRange(val, index, index + len),
LATIN1);
}
按上述流程执行完第一条语句后,变量的分布如下图:
接着来看刚开始那段程序
String s = new String("ab") + new String("c");
System.out.println(s.intern() == s);
System.out.println(s.intern() == "abc");
System.out.println(s == "abc");
当执行s.intern()时,发现常量池中没有“abc”对象,则会在常量池中保存对堆中“abc”对象的引用。
对于第一条输出语句,s.intern()返回的是堆中“abc”对象的地址,因此s.intern() == s成立。
对于第二条输出语句,s.intern()返回值不变,当再次引用“abc”常量时,会去常量池中查找是否存在“abc”对象,然后返回堆中“abc”对象的实际地址。第三条输出语句原理相同。
再看另一个程序
String s = new String("abc");
System.out.println(s.intern() == s);
System.out.println(s.intern() == "abc");
System.out.println(s == "abc");
false
true
false
String s = new String(“abc”);语句执行后的变量分布:
与上个程序不同的是在执行输出语句之前先将“abc”保存到了常量池中,然后才在堆中创建String对象。此时调用s.intern()返回的是常量池中“abc”的地址而不是堆中“abc”的地址,因此第一条输出为false。
当再次引用常量值“abc”时,发现常量池中已经有“abc”对象,所以返回的是常量池中的“abc”对象,与s.intern()返回值相同,因此第二条输出为true。
第三条比较的是堆中的“abc”地址与常量池中的“abc”地址,因此返回false。
详细参考: