String s1='Hello ';
String s2='Hello ';
String s3='Hel' 'lo ';
stringS4='Hel'newstring('lo );
stringS5=newstring(Hello );
String s6=s5.intern (;
字符串S7=' h ';
String s8='ello ';
String s9=s7 s8;
system.out.println(S1==S2;//真
system.out.println(S1==S3 );//真
system.out.println(S1==S4 ); //false
system.out.println(S1==S9 ); //false
system.out.println(S4==S5; //false
system.out.println(S1==S6 );//真
刚开始看字符串的时候,看到很相似的问题,总会有点困惑。 看回答总是提到字符串常量池、执行常量池等概念,容易混淆。
接下来,我们将讨论Java字符串是如何创建的。
String有两种赋值方法。 第一,按“字面量”赋值。
String str='Hello ';
第二,通过new关键字创建新对象。
stringstr=newstring('Hello );
要弄清楚这两种方法的区别,首先需要知道存储器中的存储位置。
通常内存是图中的运行时数据区(Runtime Data Area ),与字符串创建有关的有方法区(Method Area )、堆区(Heap Area )、堆栈区(Stack Area )
方法区域:存储类信息、常量和静态变量。 全球共享。
堆:存储对象和数组。 全球共享。
堆栈区域:基本数据类型,对对象的引用存储在此。 线程的私人。
每次执行方法时,都会在堆栈区域创建堆栈帧,基本数据类型和对象引用存在于堆栈帧内的局部变量表(Local Variables )中。
加载类后,类信息存储在堆以外的方法区域中。 方法区域有一个称为“运行时常量池”(Runtime Constant Pool )的区域。 这对于每个类都是专用的,每个class文件中的“常量池”由加载器加载,然后映射并存储在其中。 这将在后面叙述。
与String关联最大的是字符串池(String Pool ),其位置是方法区域上方的驻留字符串)的位置,一直以来与运行时常量池混淆,但实际上是两个完全不同的存储区域,字符串常量池是全局共享的字符串为String.intern (调用方法时,其引用存储在String Pool中。
在理解这些概念的基础上,介绍两个字符串创建方法的区别。
以下Test类使用main方法中的“文字”赋值方法将" Hello "赋值给字符串str。
公共类测试{ publicstaticvoidmain (字符串[ ] args ) }
String str='Hello ';
}
}
编译Test.java文件将得到. class文件。 该文件包含类的信息,其中有一个称为常量池的区域。 class常量池和内存中的常量池不同。
. class文件常量池主要存储常量,而常量包含类中定义的常量。 因为String是不变的,所以“String为什么是不变的? ),所以字符串“Hello”保存在这里。
当程序使用Test类时,Test.class解析为内存中的方法区域。 class文件中的常量池信息加载到运行时常量池中,而字符串则不是。
示例中的" Hello "在堆中创建对象,并将引用存储在“字符串池”(String Pool )中,如下图所示。
Test类刚刚加载,没有为主函数创建str," Hello "对象是在堆中创建的。
当主线程开始创建str变量时,虚拟机会去查找字符串池中是否存在equals(“Hello”字符串,如果等于,则将字符串池中对“Hello”的引用复制到str )。 如果找不到等效字符串,请在将引用放在字符串池中的同时,在堆中创建新对象并将引用分配给str。
使用文字赋值方法创建字符串时,如果字符串的值相同,则无论创建多少次,它都指向堆中的同一对象。
公共类
est {public static voidmain(String[] args) {String str1= "Hello";
String str2=“Hello”;
String str3=“Hello”;
}
}
当利用new关键字去创建字符串时,前面加载的过程是一样的,只是在运行时无论字符串池中有没有与当前值相等的对象引用,都会在堆中新开辟一块内存,创建一个对象。
public classTest {public static voidmain(String[] args) {
String str1= "Hello";
String str2=“Hello”;
String str3= new String("Hello");
}
}
现在我们来回头看之前的例子。
String s1 = "Hello";
String s2= "Hello";
String s3= "Hel" + "lo";
String s4= "Hel" + new String("lo");
String s5= new String("Hello");
String s6=s5.intern();
String s7= "H";
String s8= "ello";
String s9= s7 +s8;
System.out.println(s1== s2); //true
System.out.println(s1 == s3); //true
System.out.println(s1 == s4); //false
System.out.println(s1 == s9); //false
System.out.println(s4 == s5); //false
System.out.println(s1 == s6); //true
有了上面的基础,之前的问题就迎刃而解了。
s1在创建对象的同时,在字符串池中也创建了其对象的引用。
由于s2也是利用字面量创建,所以会先去字符串池中寻找是否有相等的字符串,显然s1已经帮他创建好了,它可以直接使用其引用。那么s1和s2所指向的都是同一个地址,所以s1==s2。
s3是一个字符串拼接操作,参与拼接的部分都是字面量,编译器会进行优化,在编译时s3就变成“Hello”了,所以s1==s3。
s4虽然也是拼接,但“lo”是通过new关键字创建的,在编译期无法知道它的地址,所以不能像s3一样优化。所以必须要等到运行时才能确定,必然新对象的地址和前面的不同。
同理,s9由两个变量拼接,编译期也不知道他们的具体位置,不会做出优化。
s5是new出来的,在堆中的地址肯定和s4不同。
s6利用intern()方法得到了s5在字符串池的引用,并不是s5本身的地址。由于它们在字符串池的引用都指向同一个“Hello”对象,自然s1==s6。
总结一下:
字面量创建字符串会先在字符串池中找,看是否有相等的对象,没有的话就在堆中创建,把地址驻留在字符串池;有的话则直接用池中的引用,避免重复创建对象。
new关键字创建时,前面的操作和字面量创建一样,只不过最后在运行时会创建一个新对象,变量所引用的都是这个新对象的地址。
参考资料:
https://www.zhihu.com/question/29884421/answer/113785601
http://www.cnblogs.com/iyangyuan/p/4631696.html
http://blog.csdn.net/sugar_rainbow/article/details/68150249
原文:http://www.cnblogs.com/justcooooode/p/7603381.html