类似的Integer也存在这样的特性。
那么为什么以字面量的创建的字符串不会重复呢?如下:
String str= "abc";
jdk使用的是字符串常量池的方法来解决这个问题,其数据结构上就是一个 hash 表,字符串对象就充当 hash 表中的 key,key 的不重复性,是 hash 表的基本特性。
当代码运行到一个字面量 “abc” 时,会首先检查 StringTable 中有没有相同的 key,如果没有,创建新字符串对象加入;否则直接返回已有的字符串对象。
上面我们说过以字面量的方式来创建字符串,其实从jvm的角度,字面量在代码运行到它所在语句之前,它还不是字符串对象。
当 java 代码被编译为 class 文件后,“abc” 存储于【类文件常量池】中。
Constant pool: // 常量池
#1 = Methodref #19.#41 // java/lang/Object."":()V
#2 = String #42 // abc
...
当 class 完成类加载之后,“abc” 这个字面量被存储于【运行时常量池】(归属于方法区)中,其中 #1 #2 都会被翻译为运行时真正的内存地址。
再看一下 class 中 main 方法的字节码:
public static void main(java.lang.String[]); // 字节码指令
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: ldc #2 // String abc
2: astore_1
3: return
...
将来 main 方法被调用时,就会执行里面的字节码指令:
0: ldc #2 // String abc
2: astore_1
3: return
ldc #2 就是到运行时常量池中找到 #2 的内存地址,找到 “abc” 这个字面量,再根据它创建一个 String 对象。
例1
String s = "a" + "b";
例2
final String x = "b";
String s = "a" + x;
例3
String x = "b";
String s = "a" + x;
例4
String s = "a" + 1;
有同学会问,例1与例2与例3 不同吗?还别说,真就不同,其中例1 与例2 原理是一样的,例3 与例4 原理是一样的,反编译一下
例1
String s = "a" + "b";
常量池
直接拼接成 a b
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."":()V
#2 = String #21 // ab
...
主方法
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: ldc #2 // String ab
2: astore_1
3: return
...
可以看到,其实并没有真正的【拼接】操作发生,从源码编译为字节码时,javac 就已经把 “a” 和 “b” 串在一起了,这是一种编译期的优化处理
例2
final String x = "b";
String s = "a" + x;
常量池
Constant pool:
#1 = Methodref #5.#22 // java/lang/Object."":()V
#2 = String #23 // b
#3 = String #24 // ab
...
主方法
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: ldc #2 // String b final b
2: astore_1
3: ldc #3 // String ab
5: astore_2
6: return
...
可以看到,还是没有真正的【拼接】操作发生,final 意味着 x 的值不可改变,因此其它引用 x 的地方都可以安全地被替换为 “b”,而不用担心 x 被改变,从源码编译为字节码时,javac 就也进行了优化,把所有出现 x 的地方都替换成为了 “b”
那么,什么是真正的【拼接】操作呢?看一下例3 反编译后的结果
String x = "b";
String s = "a" + x;
常量池
Constant pool:
#1 = Methodref #9.#26 // java/lang/Object."":()V
#2 = String #27 // b
#3 = Class #28 // java/lang/StringBuilder
#4 = Methodref #3.#26 // java/lang/StringBuilder."":()V
#5 = String #29 // a
...
可以看到常量池中并没有 ab 字面量
主方法
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // String b
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."":()V
10: ldc #5 // String a
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_2
23: return
翻译人能读懂的就是:
String x = "b";
String s = "a" + x;
String x = "b";
String s = new StringBuilder().append("a").append(x).toString();
StringBuilder 的 toString() 方法又是怎么实现的呢?
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
// 从 AbstractStringBuilder 继承的属性,方便阅读加在此处
char[] value;
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
}
可以看到,本质上就是根据 StringBuilder 维护的 char[] 创建了新的 String 对象。
String str = "abc";
str.intern();
如果常量池中已有:
例子:
String x = new String(new char[]{
'a', 'b', 'c'}); // 新创建
String y = "abc"; // 将 "abc" 加入 StringTable
String z = x.intern(); // 已有,返回 StringTable 中 "abc",即 y
System.out.println(z == y);//true
System.out.println(z == x);//false
如果常量池中没有,jdk1.7:
String x = new String(new char[]{
'a', 'b', 'c'});
String z = x.intern(); // x 加入 StringTable,StringTable 中有了 "abc"
String y = "abc"; // 已有,不会产生新的对象,用的是 StringTable 中 "abc"
System.out.println(z == x);//true
System.out.println(z == y);//true
jdk1.6 :
intern(去重的好处):减少内存占用
在jdk版本的发展中,常量池在JVM的位置随版本有些变化。
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
int i = 0;
try {
for (int j = 0; j < 260000; j++) {
list.add(String.valueOf(j).intern());
i++;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(i);
}
}
存在字符串常量池内的字符串也可能被垃圾回收。
1. 判断输出
String str1 = "string"; // 家
String str2 = new String("string"); // 野生
String str3 = str2.intern(); // 家
System.out.println(str1==str2);//#1 false
System.out.println(str1==str3);//#2 true
2. 判断输出
String baseStr = "baseStr";
final String baseFinalStr = "baseStr";
String str1 = "baseStr01"; // 家
String str2 = "baseStr"+"01"; // 家
String str3 = baseStr + "01"; // 野生
String str4 = baseFinalStr+"01";// 家
String str5 = new String("baseStr01").intern(); // 家
System.out.println(str1 == str2);//#3 true
System.out.println(str1 == str3);//#4 false
System.out.println(str1 == str4);//#5 true
System.out.println(str1 == str5);//#6 true
3. 判断输出(注意版本)
String str2 = new String("str")+new String("01");
str2.intern(); //1.6
String str1 = "str01";
System.out.println(str2==str1);//#7 1.7 true, 1.6 false
4. 判断输出
String str1 = "str01";
String str2 = new String("str")+new String("01");
str2.intern();
System.out.println(str2 == str1);//#8 false
5. String s = new String(“xyz”),创建了几个String Object?
第一个对象是字符串常量"xyz" 第二个对象是new String(“xyz”)的时候产生的,
在堆中分配内存给这个对象,只不过这个对象的内容是指向字符串常量"xyz"
另外还有一个引用s,指向第二个对象。这是一个变量,在栈中分配内存。
6. 判断输出
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); // true
7. 判断输出
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); //false
8. 判断输出
String s1 = "abc";
String s2 = "a";
String s3 = "bc";
String s4 = s2 + s3;
System.out.println(s1 == s4); //false
9. 判断输出
String s1 = "abc";
final String s2 = "a";
final String s3 = "bc";
String s4 = s2 + s3;
System.out.println(s1 == s4);//true
10. 判断输出
String s = new String("abc"); // 野生
String s1 = "abc"; // 家
String s2 = new String("abc"); // 野生
System.out.println(s == s1.intern()); // false
System.out.println(s == s2.intern()); // false
System.out.println(s1 == s2.intern()); // true
参考:
String s=new String(“xyz”);创建了几个String Object?