java中String的intern、StringBuilder和new String
2018-03-29 11:37
459 查看
1.String s1 = “abc”和String s2 = new String(“abc”)的区别
在执行String s1 = “abc”的时候,首先会在常量池中有没有abc这个字符串,如果有则直接返回该字符串的引用,否则就在常量池中创建一个abc的字符串并且返回该引用。
在执行String s2 = new String("abc")的时候,我们先来看看new String()的源码
String temp = "abc"; // 在常量池中
String str = new String(temp); // 在堆上
这个时候会创建连个对象,一个在常量池中,一个在堆上。同样在常量池中创建abc的时候,String temp = "abc"; 和上面的
String s1 = “abc”的原理相同。
因此在执行String s2 = new String("abc")时相当于:
String temp = "abc";
String str = new String(temp);
2.intern()方法下面我们通过几个例子来说明
代码段一
结果为:false,true
结果 #1:因为str1指向的是字符串中的常量,str2是在堆中生成的对象,所以str1==str2返回false。结果 #2:str2调用intern方法,会将str2中值(“string”)复制到常量池中,但是常量池中已经存在该字符串(即str1指向的字符串),所以直接返回该字符串的引用,因此str1==str2返回true。下面转自:http://www.cnblogs.com/paddix/p/5326863.html代码段二
结果为:true,false,true,true
对于代码段二的结果,还是通过反编译StringTest01.class文件比较容易理解:常量池内容(部分):
![](http://images2015.cnblogs.com/blog/820406/201603/820406-20160331072611348-942598812.png)
执行指令(部分,第二列#+序数对应常量池中的项):
![](http://images2015.cnblogs.com/blog/820406/201603/820406-20160331072104082-574379653.png)
在解释上述执行过程之前,先了解两条指令:ldc:Push item from run-time constant pool,从常量池中加载指定项的引用到栈。astore_<n>:Store reference into local variable,将引用赋值给第n个局部变量。现在我们开始解释代码段二的执行过程:0: ldc #2:加载常量池中的第二项(”baseStr”)到栈中。2: astore_1 :将1中的引用赋值给第一个局部变量,即String baseStr = “baseStr”;3: ldc #2:加载常量池中的第二项(”baseStr”)到栈中。5: astore_2 :将3中的引用赋值给第二个局部变量,即 final String baseFinalStr=”baseStr”;6: ldc #3:加载常量池中的第三项(”baseStr01″)到栈中。8: astore_3 :将6中的引用赋值给第三个局部变量,即String str1=”baseStr01″;9: ldc #3:加载常量池中的第三项(”baseStr01″)到栈中。11: astore 4:将9中的引用赋值给第四个局部变量:即String str2=”baseStr01″;结果#3:str1==str2 肯定会返回true,因为str1和str2都指向常量池中的同一引用地址。所以其实在JAVA 1.6之后,常量字符串的“+”操作,编译阶段直接会合成为一个字符串。13: new #4:生成StringBuilder的实例。16: dup :复制13生成对象的引用并压入栈中。17: invokespecial #5:调用常量池中的第五项,即StringBuilder.<init>方法。以上三条指令的作用是生成一个StringBuilder的对象。20: aload_1 :加载第一个参数的值,即”baseStr”21: invokevirtual #6 :调用StringBuilder对象的append方法。24: ldc #7:加载常量池中的第七项(”01″)到栈中。26: invokevirtual #6:调用StringBuilder.append方法。29: invokevirtual #8:调用StringBuilder.toString方法。32: astore 5:将29中的结果引用赋值改第五个局部变量,即对变量str3的赋值。结果 #4:因为str3实际上是stringBuilder.append()生成的结果,所以与str1不相等,结果返回false。34: ldc #3:加载常量池中的第三项(”baseStr01″)到栈中。36: astore 6:将34中的引用赋值给第六个局部变量,即str4=”baseStr01″;结果 #5 :因为str1和str4指向的都是常量池中的第三项,所以str1==str4返回true。这里我们还能发现一个现象,对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的。38: new #9:创建String对象41: dup :复制引用并压如栈中。42: ldc #3:加载常量池中的第三项(”baseStr01″)到栈中。44: invokespecial #10:调用String.”<init>”方法,并传42步骤中的引用作为参数传入该方法。47: invokevirtual #11:调用String.intern方法。从38到41的对应的源码就是new String(“baseStr01″).intern()。50: astore 7:将47步返回的结果赋值给变量7,即str5指向baseStr01在常量池中的位置。结果 #6 :因为str5和str1都指向的都是常量池中的同一个字符串,所以str1==str5返回true。
代码段三(1)
代码段三(2)
对于代码段三,在 JDK 1.6 和 JDK 1.7中的运行结果不同。我们先看一下运行结果,然后再来解释其原因:
在jdk1.6中false,false
在jdk1.7及以后:true,false
根据对代码段一的分析,应该可以很简单得出 JDK 1.6 的结果,因为 str2 和 str1本来就是指向不同的位置,理应返回false。比较奇怪的问题在于JDK 1.7后,对于第一种情况返回true,但是调换了一下位置返回的结果就变成了false。这个原因主要是从JDK 1.7后,HotSpot 将常量池从永久代移到了元空间,正因为如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。所以:结果 #7:在第一种情况下,因为常量池中没有“str01”这个字符串,所以会在常量池中生成一个对堆中的“str01”的引用,而在进行字面量赋值的时候,常量池中已经存在,所以直接返回该引用即可,因此str1和str2都指向堆中的字符串,返回true。结果 #8:调换位置以后,因为在进行字面量赋值(String str1 = “str01″)的时候,常量池中不存在,所以str1指向的常量池中的位置,而str2指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,所以返回false。
下面转自:https://segmentfault.com/a/11900000115439953、StringBuilder的用法
先运行这个代码
String str3 = new StringBuilder("ni").append("hao").toString();
System.out.println(str3==str3.intern());
上面的代码等价于下面的代码
String a = "ni";
String b = "hao";
String str3 = new StringBuilder(a).append(b).toString();
System.out.println(str3==str3.intern());
很容易分析出:
“nihao” 最先创建在堆中 str3.intern()然后缓存在字符串常连池中 运行结果为true.lz代码
String str3 = new StringBuilder("nihao").toString();
System.out.println(str3==str3.intern());
可以写成下面的形式
String a = "nihao";
String str3 = new StringBuilder(a).toString();
System.out.println(str3==str3.intern());很容易分析出:
“nihao” 最先创建在常量池中, 运行结果为false.
在执行String s1 = “abc”的时候,首先会在常量池中有没有abc这个字符串,如果有则直接返回该字符串的引用,否则就在常量池中创建一个abc的字符串并且返回该引用。
在执行String s2 = new String("abc")的时候,我们先来看看new String()的源码
String temp = "abc"; // 在常量池中
String str = new String(temp); // 在堆上
这个时候会创建连个对象,一个在常量池中,一个在堆上。同样在常量池中创建abc的时候,String temp = "abc"; 和上面的
String s1 = “abc”的原理相同。
因此在执行String s2 = new String("abc")时相当于:
String temp = "abc";
String str = new String(temp);
2.intern()方法下面我们通过几个例子来说明
代码段一
结果为:false,true
结果 #1:因为str1指向的是字符串中的常量,str2是在堆中生成的对象,所以str1==str2返回false。结果 #2:str2调用intern方法,会将str2中值(“string”)复制到常量池中,但是常量池中已经存在该字符串(即str1指向的字符串),所以直接返回该字符串的引用,因此str1==str2返回true。下面转自:http://www.cnblogs.com/paddix/p/5326863.html代码段二
结果为:true,false,true,true
对于代码段二的结果,还是通过反编译StringTest01.class文件比较容易理解:常量池内容(部分):
![](http://images2015.cnblogs.com/blog/820406/201603/820406-20160331072611348-942598812.png)
执行指令(部分,第二列#+序数对应常量池中的项):
![](http://images2015.cnblogs.com/blog/820406/201603/820406-20160331072104082-574379653.png)
在解释上述执行过程之前,先了解两条指令:ldc:Push item from run-time constant pool,从常量池中加载指定项的引用到栈。astore_<n>:Store reference into local variable,将引用赋值给第n个局部变量。现在我们开始解释代码段二的执行过程:0: ldc #2:加载常量池中的第二项(”baseStr”)到栈中。2: astore_1 :将1中的引用赋值给第一个局部变量,即String baseStr = “baseStr”;3: ldc #2:加载常量池中的第二项(”baseStr”)到栈中。5: astore_2 :将3中的引用赋值给第二个局部变量,即 final String baseFinalStr=”baseStr”;6: ldc #3:加载常量池中的第三项(”baseStr01″)到栈中。8: astore_3 :将6中的引用赋值给第三个局部变量,即String str1=”baseStr01″;9: ldc #3:加载常量池中的第三项(”baseStr01″)到栈中。11: astore 4:将9中的引用赋值给第四个局部变量:即String str2=”baseStr01″;结果#3:str1==str2 肯定会返回true,因为str1和str2都指向常量池中的同一引用地址。所以其实在JAVA 1.6之后,常量字符串的“+”操作,编译阶段直接会合成为一个字符串。13: new #4:生成StringBuilder的实例。16: dup :复制13生成对象的引用并压入栈中。17: invokespecial #5:调用常量池中的第五项,即StringBuilder.<init>方法。以上三条指令的作用是生成一个StringBuilder的对象。20: aload_1 :加载第一个参数的值,即”baseStr”21: invokevirtual #6 :调用StringBuilder对象的append方法。24: ldc #7:加载常量池中的第七项(”01″)到栈中。26: invokevirtual #6:调用StringBuilder.append方法。29: invokevirtual #8:调用StringBuilder.toString方法。32: astore 5:将29中的结果引用赋值改第五个局部变量,即对变量str3的赋值。结果 #4:因为str3实际上是stringBuilder.append()生成的结果,所以与str1不相等,结果返回false。34: ldc #3:加载常量池中的第三项(”baseStr01″)到栈中。36: astore 6:将34中的引用赋值给第六个局部变量,即str4=”baseStr01″;结果 #5 :因为str1和str4指向的都是常量池中的第三项,所以str1==str4返回true。这里我们还能发现一个现象,对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的。38: new #9:创建String对象41: dup :复制引用并压如栈中。42: ldc #3:加载常量池中的第三项(”baseStr01″)到栈中。44: invokespecial #10:调用String.”<init>”方法,并传42步骤中的引用作为参数传入该方法。47: invokevirtual #11:调用String.intern方法。从38到41的对应的源码就是new String(“baseStr01″).intern()。50: astore 7:将47步返回的结果赋值给变量7,即str5指向baseStr01在常量池中的位置。结果 #6 :因为str5和str1都指向的都是常量池中的同一个字符串,所以str1==str5返回true。
代码段三(1)
代码段三(2)
对于代码段三,在 JDK 1.6 和 JDK 1.7中的运行结果不同。我们先看一下运行结果,然后再来解释其原因:
在jdk1.6中false,false
在jdk1.7及以后:true,false
根据对代码段一的分析,应该可以很简单得出 JDK 1.6 的结果,因为 str2 和 str1本来就是指向不同的位置,理应返回false。比较奇怪的问题在于JDK 1.7后,对于第一种情况返回true,但是调换了一下位置返回的结果就变成了false。这个原因主要是从JDK 1.7后,HotSpot 将常量池从永久代移到了元空间,正因为如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。所以:结果 #7:在第一种情况下,因为常量池中没有“str01”这个字符串,所以会在常量池中生成一个对堆中的“str01”的引用,而在进行字面量赋值的时候,常量池中已经存在,所以直接返回该引用即可,因此str1和str2都指向堆中的字符串,返回true。结果 #8:调换位置以后,因为在进行字面量赋值(String str1 = “str01″)的时候,常量池中不存在,所以str1指向的常量池中的位置,而str2指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,所以返回false。
下面转自:https://segmentfault.com/a/11900000115439953、StringBuilder的用法
public class RuntimeConstantPoolOOM { public static void main(String[]args) { String str1=new StringBuilder("计算机").append("软件").toString(); System.out.println(str1.intern()==str1); String str2=new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern()==str2); } }运行结果:true false
JDK 1.7(以及部分其他虚拟机,例如JRockit)的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。对str2比较返回false是因为“java”这个字符串在执StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,因此返回true。java虚拟机会自动调用System类
/* register the natives via the static initializer. * * VM will invoke the initializeSystemClass method to complete * the initialization for this class separated from clinit. * Note that to use properties set by the VM, see the constraints * described in the initializeSystemClass method. */ 在System类中的注释可以知道,调用了initializeSystemClass方法,在此方法中调用了Version对象的init静态方法 sun.misc.Version.init(); 因此sun.misc.Version类会在JDK类库的初始化过程中被加载并初始化。 查看Version类定义的私有静态字符串常量如下: private static final String launcher_name = "java"; private static final String java_version = "1.7.0_51"; private static final String java_runtime_name = "Java(TM) SE Runtime Environment"; private static final String java_runtime_version = "1.7.0_51-b13"; 在初始化Version类时,对其静态常量字段根据指定的常量值做默认初始化,所以"java"被加载到了字符串常量池中,修改上面代码使字符串值为上面常量中的任意一个都会返回false。 String str2=new StringBuilder("1.7.0").append("_51").toString(); System.out.println(str2.intern()==str2);这个问题解决了,然后我又发现了另外一个问题。除了这些在虚拟机加载时就初始化的常量,定义其他的字符串常量,比如“nihao”.
先运行这个代码 String str3 = new StringBuilder("ni").append("hao").toString(); System.out.println(str3==str3.intern()); 通过上面的解释,运行结果为true. 在运行这个代码 String str3 = new StringBuilder("nihao").toString(); System.out.println(str3==str3.intern()); 其结果是什么?应该还是true吧,毕竟通过上一个运行结果可以知道"nihao"这个字符串常量没有被预先加载到常量池中。 但是运行结果却是false.分析:
先运行这个代码
String str3 = new StringBuilder("ni").append("hao").toString();
System.out.println(str3==str3.intern());
上面的代码等价于下面的代码
String a = "ni";
String b = "hao";
String str3 = new StringBuilder(a).append(b).toString();
System.out.println(str3==str3.intern());
很容易分析出:
“nihao” 最先创建在堆中 str3.intern()然后缓存在字符串常连池中 运行结果为true.lz代码
String str3 = new StringBuilder("nihao").toString();
System.out.println(str3==str3.intern());
可以写成下面的形式
String a = "nihao";
String str3 = new StringBuilder(a).toString();
System.out.println(str3==str3.intern());很容易分析出:
“nihao” 最先创建在常量池中, 运行结果为false.
相关文章推荐
- Java String和new String,Stringbuilder,StringBuffer
- Android/Java 中的 String, StringBuffer, StringBuilder的区别和使用
- java面试题6--String,StringBuffer,StringBuilder
- 详述 Java 语言中的 String、StringBuffer 和 StringBuilder 的使用方法及区别
- String、StringBuffer与StringBuilder之间区别-----(java复习)
- java源码解读之String,StringBuilder,StringBuffer
- 通过反编译深入理解Java String及intern(转)
- java基础(二)------ String,StringBuffer,StringBuilder有什么区别?
- java中String、StringBuffer、StringBuilder的区别
- Java String 和 new String()的比较与区别
- Java中String、StringBuffer和StringBuilder的区别
- JAVA中String与StringBuffer,StringBuilder应用的区别
- 解析Java中的String、StringBuilder、StringBuffer类(一)
- java中String、StringBuffer、StringBuilder的区别
- Java基础1(String StringBuilder StringBuffer 三者区别)
- Java中的String,StringBuilder,StringBuffer的区别
- Java基础12:String、StringBuffer和StringBuilder的区别
- java中String,StringBuffer与StringBuilder的区别??
- Java中的String,StringBuilder,StringBuffer三者的区别
- java-String中的 intern()