文章目录
String#intern()的作用是:运行期,将字符串动态加入字符串常量池,实现字符串对象复用,减小内存开销。
-
jdk1.6及之前,由于字符串常量池在永久代(方法区)中,和堆区不在同一内存区域。此时调用String#intern() 会先通过equals(key)查看字符串常量池中是否有该字符串存在,有则直接返回该字符串对象的引用,没有则将堆中的字符串复制一份,放到字符串常量池中,并保存复制到字符串常量池中的这个字符串对象的引用到hash表中,然后返回该字符串对象的引用。
-
jdk1.7及之后,字符串常量池被移至堆区的新生代中,此时调用String#intern()后,查找字符串常量池发现没有该字符串对象时,不再是将堆中的字符串对象赋值一份到字符串常量池中,而是将堆中的字符串对象的引用保存到hash表中,然后返回该字符串对象的引用。
String s = new String(“hello”);创建了几个对象
① 1个 或 2个
String s = new String("hello"); 这段代码创建了几个字符串对象?
② 3个 或 4个 或 5个
String s = new String("he") + new String("llo"); 这段代码创建了几个字符串对象?
③ 1个
StringBuilder类的toString(), 底层是动态创建new String("hello"), 那么此时的new String(object)创建了几个字符串对象?
② 与jdk版本有关:(jdk1.6及之前是:3个 或 4个 或 5个 或 6个),(jdk1.7及之后是:3个 或 4个 或 5个)
String s = new String("he") + new String("llo"); 这段代码创建了几个字符串对象?
String s1 = s.intern();
字符串常量拼接
① 对于字符串常量的拼接操作,编译期间编译器会对其进行优化。
public static void main(String[] args){
String s1 = "he" + "llo"; //编译期会进行优化:等价与String s1 = "hello";
String s2= "hello";
System.out.println(s1==s2); //true
System.out.println(s1.equals(s2)); //true
}
② 对于字符串实例引用的拼接,编译器无法对其进行优化。在底层会调用new StringBuilder().append().toString()来生成对象引用。 并且 StringBuilder 类的 toString() 方法会生成新的String对象,所以二者不相等
public static void main(String[] args){
String s0 = "he" + "llo"; //编译后会优化成hello: 等价于String s0 = "hello";
String s1 = "he";
String s2 = "llo";
//s3不会被编译器优化,因为s1、s2是变量,后期是可能会发生变化的,所以编译器无法对他们进行优化。而是使用StringBuilder拼接:等价于String s3 = new StringBuilder().append(s1).append(s2).toString();
String s3 = s1 + s2;
System.out.println(s0 == s3); //false
String ss1 = "he";
//ss3不会被编译器优化,因为ss1是变量,后期是可能会发生变化的,所以编译器无法对他们进行优化。而是使用StringBuilder拼接:等价于String ss3 = new StringBuilder().append(ss1).append("llo").toString();
String ss3 = ss1 + "llo";
System.out.println(s0 == ss3); //false
}
//StringBuilder类中toString()方法的源码
public String toString() {
return new String(value, 0, count);
}
③ final修饰的字符串,再编译器也会进行优化
public static void main(String[] args) {
String s0 = "hello";
final String s1 = "he";
final String s2 = "llo";
String s3 = s1 + s2; //编译后会优化成hello: 等价于String s3 = "hello";
System.out.println(s0 == s3); //true
}
public static void main(String[] args) {
String s0 = "hello";
final String s1 = "he";
String s2 = s1 + "llo"; //编译后会优化成hello: 等价于String s2 = "hello";
System.out.println(s0 == s2); //true
}
0、判断输出
public static void main(String[] args) {
String s1 = "hello";
s1 = "world";
System.out.println(s1 == s2); //false
}
String s1 = “hello”; //
s1 = “world”; // String 底层被final修饰,已经创建就不允许被修改。这里表面是被修改了,实则是创建了一个新的字符串对象,并将新字符串对象的引用指向s1
1、判断输出
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); //true
}
String s1 = “hello”; //
String s2 = “hello”; // 字面量方式创建字符串对象前,会先去检索字符串常量池,判断是否存有该字符串对象的引用。有则直接返回该引用。没有则在字符串常量池中创建一个字符串对象,并将该对象的引用保存到hash表中,然后再返回该对象的引用。
2、判断输出
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); //false
}
String s1 = new String(“hello”); //
String s2 = new String(“hello”); //
3、判断输出
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = "hello";
System.out.println(s1 == s2); //false
}
String s1 = new String(“hello”); //
String s2 = “hello”; //
4、判断输出
public static void main(String[] args) {
String s1 = new String("hello");
s1.intern();
String s2 = "hello";
System.out.println(s1 == s2); //false
}
String s1 = new String(“hello”); //
s1.intern(); // 由于没有定义变量来接收String#intern() 返回的引用,所以这里就不画图了
String s2 = “hello”; //
5、判断输出
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = s1.intern();
String s3 = "hello";
System.out.println(s1 == s2); //false
System.out.println(s2 == s3); //true
}
String s1 = new String(“hello”); //
String s2 = s1.intern(); // StringTable中有保存hello字符串的对象引用,所以直接返回该对象引用
String s3 = “hello”; //
6、判断输出(String#intern()- - > jdk1.8)
public static void main(String[] args) {
String s1 = new String("he") + new String("llo");
s1.intern();
String s2 = "hello";
System.out.println(s1 == s2); //true
}
String s1 = new String(“he”) + new String(“llo”); // 会创建5个字符串对象(也可能是3个或4个)。在字符串常量池通过字面量创建"he" 和 “llo"字符串对象,并保存其对象引用,以及在堆中创建"he” 和 “llo"字符串对象,另外底层是通过new StringBuilder().append(“he”).append(“llo”).toString()进行拼接。其中StringBuilder的toString()会生成一个新的字符串对象new String(“hello”),但由于这个代码是运行期动态生成的,所以不会在字符串常量池中创建字符串对象"hello”,但会在堆中创建字符串对象"hello"
s1.intern(); // 在jdk1.7及以后,intern()方法会先去查看StringTable中是否已经有该字符串对象引用存在。没有则直接将在堆中创建的字符串对象的引用存到StringTable中 (jdk1.6之前,是直接则将堆中的字符串对象复制一份,放到方法区的字符串常量池中,并保存复制到字符串常量池中的这个字符串对象的引用到hash表中,然后返回该字符串对象的引用)。有则直接返回字符串常量池中的引用
String s2 = “hello”; // 通过字符串的hash查找时,发现hash表中有该字符串对象引用存在,所以直接返回对象引用给变量s2。没有则再字符串常量池中创建一个字符串对象,然后将其引用保存到hash表中,然后返回该字符串对象引用给变量s2
6、判断输出(String#intern()- - > jdk1.6)
public static void main(String[] args) {
String s1 = new String("he") + new String("llo");
s1.intern();
String s2 = "hello";
System.out.println(s1 == s2); //false
}
String s1 = new String(“he”) + new String(“llo”);
s1.intern(); // jdk1.8中调用intern()方法,会通过字符串的hash值查找StringTable中是否保存了该字符串的引用,有则直接返回该引用,没有则将堆中该字符串对象的引用存到StringTable中,然后再返回改该引用。 // jdk1.6则是判断StringTable中是否保存了该字符串的引用,有则直接返回该引用,没有则将堆中该字符串对象赋值一份到字符串常量池中,然后将复制的这份字符串对象的引用存到StringTable中,然后再返回该引用。
【初学者注意啦:图中的"he" 字符串对象 和 new String(“he”)字符串对象只是为了让读者容易理解才这样表示而已,它们都是字符串对象,内部存储结构都是一样的。 // 所以别有:new String(“hello”)创建的字符串对象不是都放在堆中吗,怎么还能放到字符串常量池中啊,这样的错误理解。new创建的字符串对象确实是只能放在堆中,但这里我只是把这个字符串对象用new String(“hello”)形式表示而已,别把它理解成程序中的new String(“hello”)了】
String s2 = “hello”; // 通过字符串的hash查找时,发现hash表中有该字符串对象引用存在,所以直接返回对象引用给变量s2。没有则再字符串常量池中创建一个字符串对象,然后将其引用保存到hash表中,然后返回该字符串对象引用给变量s2
7、判断输出(String#intern()- - > jdk1.8)
public static void main(String[] args) {
String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
String s3 = "hello";
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //true
System.out.println(s2 == s3); //true
}
String s1 = new String(“he”) + new String(“llo”);
String s2 = s1.intern();
String s3 = “hello”;
7、判断输出(String#intern()- - > jdk1.6)
public static void main(String[] args) {
String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
String s3 = "hello";
System.out.println(s1 == s2); //false
System.out.println(s1 == s3); //false
System.out.println(s2 == s3); //true
}
String s1 = new String(“he”) + new String(“llo”);
String s2 = s1.intern(); // 因为跨内存区域调用是不推荐的。所以jdk1.6之前String#intern()采用复制的方式。直到jdk1.7之后,字符串常量池被移至堆中,改为存引用方式
String s3 = “hello”;
8、判断输出(String#intern()- - > jdk1.8)
public static void main(String[] args) {
String s1 = "hello";
String s2 = new String("he") + new String("llo");
String s3 = s2.intern();
System.out.println(s1 == s2); //false
System.out.println(s1 == s3); //true
System.out.println(s2 == s3); //false
}
String s1 = “hello”; //
String s2 = new String(“he”) + new String(“llo”); //
String s3 = s2.intern(); // String#intern() 会先去字符串常量池,通过字符串的hash查找,发现hash表中有该字符串对象引用存在,所以直接返回该引用