浅谈StringBuilder - 简书
理解Java常量池 - gegewx - 博客园
通过反编译深入理解Java String及intern - liuxiaopeng - 博客园
深入分析String.intern和String常量的实现原理 - 简书
深入解析String#intern -
总结:
原理
常量存储内容
- java6常量池在永久代
- java7+java8常量池在堆中常量池中可以存储堆中的引用
- 方法名、java等字符串在代码执行前已在常量池中存在
intern定义:
- 如果常量池中存在当前字符串,就会直接返回当前字符串.如果常量池中没有此字符串,会将此字符串放入常量池中后再返回,返回的是常量池中的引用
- java7及以后 常量池中存储的是:堆中的引用
如果堆中有,常量池中没有,如何确定堆中是有的?因为执行intern方法的对象已确定。
对象声明
- newString(“china”)每次执行都会在堆中建一个新的对象,但常量区就一个,返回堆中的引用
对象数
String str1 = “a”;//一个对象 放在常量池中(不会指向堆中的对象)
String str1 = new String(“a”);//两个对象(无论是jdk1.6还是1.7) 一个堆中 一个常量区 返回堆中的
String s3 = new String(“1”) + new String(“1”);//四个对象,一个常量区的1 一个堆中的11 两个堆中的匿名对象1
String c = “a” + “b” + “c”; //应该是一个,编译时已确定(三个对象,都在常量区中 操作会加到常量池中)
String test = “test”;
通过反编译出来的字节码可以看出字符串 “test” 在常量池中的定义方式:
#2 = String #14 // test
#14 = Utf8 test
在main方法字节码指令中,0 ~ 2行对应代码 String test = “test”; 由两部分组成:ldc #2 和 astore_1。
// main方法字节码指令
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String test
2: astore_1
3: return
1、Test类加载到虚拟机时,”test"字符串在Constant pool中使用符号引用symbol表示,当调用 ldc #2 指令时,
如果Constant pool中索引 #2 的symbol还未解析,则调用C++底层的 StringTable::intern 方法生成char数组,
并将引用保存在StringTable和常量池中,当下次调用 ldc #2 时,可以直接从Constant pool根据索引 #2 获取 "test" 字符串的引用,
避免再次到StringTable中查找。
2、astore_1指令将”test”字符串的引用保存在局部变量表中。
总结:String test = “test”; 只在常量池中操作,跟堆中没关系。
ldc:Push item from run-time constant pool,从常量池中加载指定项的引用到栈。
astore_<n>:Store reference into local variable,将引用赋值给第n个局部变量
ldc #2:加载常量池中的第二项("baseStr")到栈中。
astore_1 将引用类型或returnAddress类型值存入局部变量1
指令格式:
astore index: 将栈顶数值(objectref)存入当前frame的局部变量数组中指定下标(index)处的变量中,栈顶数值出栈。
astore_0 该指令的行为类似于astore指令index为0的情况。
String str1 = new String(“a”);
String str2 = new String("str");
System.out.println(str2 == "str");//false
javap -c
Constant pool:
#1 = Methodref #6.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // java/lang/String
#3 = String #24 // str
#4 = Methodref #2.#25 // java/lang/String."<init>":(Ljava/lang/String;)V
Code:
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String str
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: return
可以看出生成了两个对象:一个是第4行在常量池中生成的对象,一个是第6行初始化的对象。
String str2 = new String("str")+new String("01");
String str2 = new String("str") + new String("01");
System.out.println(str2 == "str01");//false
System.out.println(str2.intern() == "str01");//true
@Test
public void test1() {
String str2 = new String("str")+new String("01");
str2.intern();
String str1 = "str01";
System.out.println(str2==str1);//true
}
@Test
public void test2() {
String str1 = "str01";
String str2 = new String("str")+new String("01");
str2.intern();
System.out.println(str2 == str1);//false
}
这个原因主要是从JDK 1.7后,HotSpot 将字符常量池从永久代移到了堆中,正因为如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。
所以:test1中,因为常量池中没有“str01”这个字符串,所以会在常量池中生成一个对堆中的“str01”的引用,而在进行字面量赋值的时候,常量池中已经存在,所以直接返回该引用即可,因此str1和str2都指向堆中的字符串,返回true。
调换位置以后,因为在进行字面量赋值(String str1 = “str01")的时候,常量池中不存在,所以str1指向的常量池中的位置,而str2指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,所以返回false。
看以下示例:
String s1 = new String("a") + new String("b");
/**
String s11 = "ab"; //存在这个前后值不一样
是否存在 String s11 = "ab";
不存在 存在
true true
true false
true false
*/
System.out.println(s1.intern() == "ab");
System.out.println(s1.intern() == s1);
System.out.println(s1 == "ab");
String s2 = new String("ja") + new String("va");
System.out.println(s2.intern() == "java");//true
System.out.println(s2.intern() == s2);//false
System.out.println(s2 == "java");//false
原因: 不存在 String s11 = "ab”;时,s1.intern()会将在常量池中查找是否存在“ab”,查找不存在,会在常量池中生成对s1对象的引用,所以s1.intern() == "ab" ,s1 == "ab”;
存在 String s11 = "ab”;时,s11 = "ab"; 会常量直接操作常量池中生成常量,不会查找是否存在“ab”,所以生成的常量不会引用堆中(s1)对角,所以s1.intern() == "ab" ,s1 <> "ab”。
网友评论