文章转自:https://blog.csdn.net/qq_34115899/article/details/86583262
- String类和常量池内存分析
- 8种基本类型的包装类和常量池
String 类和常量池
1 String 对象的两种创建方式
String str1 = "abcd"; String str2 = new String("abcd"); System.out.println(str1==str2);//false
记住:只要使用 new 方法,便需要创建新的对象。
2 String 类型的常量池比较特殊。它的主要使用方法有两种:
- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用(在JDK1.6和1.7操作不同)是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,在jdk1.6中,将此String对象添加到常量池中,然后返回这个String对象的引用(此时引用的串在常量池)。在jdk1.7中,放入一个引用,指向堆中的String对象的地址,返回这个引用地址(此时引用的串在堆)。根据《java虚拟机规范 Java SE 8版》记录,如果某String实例所包含的Unicode码点序列与CONSTANT——String_info结构所给出的序列相同,而之前又曾在该实例上面调用过String.intern方法,那么此次字符串常量获取的结果将是一个指向相同String实例的引用。这是什么意思呢?解释一下,比如在jdk1.7及以后("a"+"b"+"c").intern() == "abc",结果返回true。常量池中有"a", "b", "c", 堆中有连接的"abc",调用intern(),发现字符串常量池没有"abc",那么就保存堆中的"abc"引用,接着==右边的"abc"会在字符串常量池中查找"abc",结果发现有了"abc"引用,那么获取结果就是堆中的"abc",两者是相同位置(都是常量池)的"abc",所以返回true。如果这里不清楚,请看下面的例子,再返回来看这里即可。
关于String的intern()问题,可参考这篇文章Java技术——你真的了解String类的intern()方法吗
关于运行时常量池:java虚拟机为每个类型都维护着一个常量池。该常量池是java虚拟机中的运行时数据结构,像传统编程语言实现中的符号表一样有很多用途。当类或接口创建时,它的二进制表示中的常量池表被用来构造运行时常量池,运行时常量池中的所有引用最初都是符号引用。
以下所说常量池为字符串常量池。
接下来我们均以示例的方式来解释问题,也是我在某篇文章底下解决的别人问题的笔记。
问题一:
- String h = new String("cc");
- String intern = h.intern();
- System.out.println(intern == h); // 返回false
这里为什么不返回true,而是返回false呢?
解释:
当new String("cc")后,堆中创建了"cc",也会将"cc"放入常量池,即可以理解为创建了2个字符串对象。当你String intern = h.intern();其中h.intern()会去常量池检查是否有了"cc",结果发现有了,那么此时返回常量池的引用地址给intern,用常量池的引用intern和堆中的h引用去比较肯定不相等。所以返回false。
问题二:
我对以下代码的操作过程有疑问
- String str2 = new String("str") + new String("01");
- String str1 = "str01";
- str2.intern();
- System.out.println(str2 == str1); // false
解释:
第一句new String("str") + new String("01");现在在堆中创建了"str",同时放入常量池,创建了"01",同时放入常量池,再进行连接,堆中出现了"str01"。此时常量池中有:"str","01",此时堆中有"str","01","str01"。str2引用指向堆中的"str01"。
接着第二句String str1 = "str01";直接在常量池创建了"str01"。此时常量池中有:"str","01","str01",此时堆中有"str","01","str01"。str1指向常量池中的"str01"。
接着第三句str2.intern();检查常量池是否有"str01",结果发现有了,返回常量池"str01"的地址,很可惜,没有变量去接收,所以这一句没什么用,str2指向也不会改变,还是指向堆中"str01"。
第四句去打印str2==str1,一个堆中的"str01"和一个常量池中的"str01"比较,返回false。
问题三:
那这一段代码呢?
- String str2 = new String("str") + new String("01");
- String str1 = "str01";
- String str3 = str2.intern();
- System.out.println(str3 == str1); // true
解释:
比问题二多了一个str3引用保存了常量池"str01",str3和str1均指向常量池的"str01",所以返回true
问题四:
- String str2 = new String("str") + new String("01");
- str2.intern();
- String str1 = "str01";
- System.out.println(str2 == str1);
-
- String str3 = new String("str01");
- str3.intern();
- String str4 = "str01";
- System.out.println(str3 == str4);
这个代码的过程晕乎了,到底这些串在堆还是在常量池呢?
解释:
第一句new String("str") + new String("01");现在在堆中创建了"str",同时放入常量池,创建了"01",同时放入常量池,再进行连接,堆中出现了"str01"。此时常量池中有:"str","01",此时堆中有"str","01","str01"。str2引用指向堆中的"str01"。
第二句,str2.intern();检查到常量池不存在"str01",如果在jdk1.6,那么就将堆中的"str01"添加到常量池中,如果是jdk1.7,那么就在常量池保存指向堆中"str01"的地址,即保存堆中"str01"的引用。接下来的讲解以jdk1.7为准!!
第三句String str1 = "str01";检查到常量池有一个地址保存了这个串,str1就直接指向这个地址,即还是堆中的"str01"。
接着打印str2==str1是否相等,str2指向堆中的"str01",str1指向常量池的某个地址,这个地址恰好是保存堆中的"str01",所以仍然是true。
接着往下看,String str3 = new String("str01");又在堆中创建了"str01",现在堆中有了2个"str01"
下一句str3.intern(); 去检查一下常量池到底有没有"str01"呢?检查发现常量池有个引用指向堆中的"str01",检查是用equals比较的,JVM认为常量池是有"str01"的,那么返回指向堆中的"str01"地址,很可惜,没有变量去接收,这一句在这里没有什么用。
下一句String str4 = "str01";检查到常量池有个引用指向堆中的"str01",检查是用equals相比,结果为true,那么str4保存这个地址,所以这个"str01"还是堆中的第一个"str01"。
下一句打印str3==str4,str3是堆中新建的第二个"str01",str4保存的仍是第一个堆中的"str01",两块堆的地址,所以返回false。
问题五:
String str2 = new String("str") + new String("01"); 为什么第一行要这么写呢? 为什么不String str2 = new String("str01");呢? 区别在哪里呢?
解释:
我们来单独执行比较,前者new String("str")堆中创建"str",常量池没有"str",所以也要放到常量池,new String("01")在堆中创建"01",也放入到常量池,相加操作只会在堆中创建"str01",所以前者执行以后,内存:堆中有"str","01","str01",常量池中"str","01"。str2引用指向堆中的"str01"。
现在来看后者String str2 = new String("str01");这个就是在堆中创建"str01"并且放入常量池,str2引用指向堆中的"str01",内存:堆中有"str01",常量池中有"str01"。
综上所述,区别就在于这些串处于不同的位置,前者在常量池是没有"str01"的。
问题六:
- String s = new String("abc");
- String s1 = "abc";
- String s2 = new String("abc");
- System.out.println(s == s1);// 堆内存s和常量池内存s1相比,false
- System.out.println(s == s2);// 堆内存s和堆内存s2相比,false
- System.out.println(s == s1.intern());// 堆内存地址s和常量池地址s1相比,false
- System.out.println(s == s2.intern());// 堆内存地址s和常量池地址s1相比,false
- System.out.println(s1 == s2.intern());// 常量池地址s1和常量池地址s1相比,true
- System.out.println(s.intern() == s2.intern());// 常量池地址s1和常量池地址s1相比,true
解释:有注释,无需多余解释,上面的问题看懂了这个一看就懂。
问题七:
- String s1 = "abc";
- String s2 = "a";
- String s3 = "bc";
- String s4 = s2 + s3;
- System.out.println(s1 == s4);//false,因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。
- // s1指向常量池"abc",s4指向堆中"abc"(append连接而来)
- String S1 = "abc";
- final String S2 = "a";
- final String S3 = "bc";
- String S4 = S2 + S3;
- System.out.println(S1 == S4);//true,因为final变量在编译后会直接替换成对应的值
- // 所以实际上等于s4="a"+"bc",而这种情况下,编译器会直接合并为s4="abc",所以最终s1==s4。
8种基本类型的包装类和常量池
- Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte、Short、Integer、Long、Character、Boolean;这5种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。
- 两种浮点数类型的包装类 Float、Double 并没有实现常量池技术。
- Integer i1 = 33;
- Integer i2 = 33;
- System.out.println(i1 == i2);// 输出true
- Integer i11 = 333;
- Integer i22 = 333;
- System.out.println(i11 == i22);// 输出false
- Double i3 = 1.2;
- Double i4 = 1.2;
- System.out.println(i3 == i4);// 输出false
在[-128,127]区间内的利用cache数组的值,否则new一个新的Integer对象。这里2个333不等因为是2块不同的堆内存。2个33相等是因为利用了同一个cache数组,是值的比较,这里i1==33,打印出来也是true。
Integer 缓存源代码:
- public static Integer valueOf(int i) {
- if (i >= IntegerCache.low && i <= IntegerCache.high)
- return IntegerCache.cache[i + (-IntegerCache.low)];
- return new Integer(i);
- }
应用场景:
- Integer i1=40;Java 在编译的时候会直接将代码封装成 Integer i1=Integer.valueOf(40); 从而使用常量池中的对象。
- Integer i1 = new Integer(40) ;这种情况下会创建新的对象。
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2); //输出false
Integer 比较(==)更丰富的一个例子:
- Integer i1 = 40;
- Integer i2 = 40;
- Integer i3 = 0;
- Integer i4 = new Integer(40);
- Integer i5 = new Integer(40);
- Integer i6 = new Integer(0);
- System.out.println("i1=i2 " + (i1 == i2));
- System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
- System.out.println("i1=i4 " + (i1 == i4));
- System.out.println("i4=i5 " + (i4 == i5));
- System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
- System.out.println("40=i5+i6 " + (40 == i5 + i6));
结果:
i1=i2 true
i1=i2+i3 true
i1=i4 false
i4=i5 false
i4=i5+i6 true
40=i5+i6 true
解释:
语句 i4 == i5 + i6,因为 + 这个操作符不适用于 Integer 对象,首先 i5 和 i6 进行自动拆箱操作,进行数值相加,即 i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。
===============Talk is cheap, show me the code================