文章目录
- 前言
- [一、new String()创建了几个对象?](#一、new String()创建了几个对象?)
- [二、Stting a=new String("ab")+new String("c")创建了几个对象](#二、Stting a=new String("ab")+new String("c")创建了几个对象)
- 三、String的intern()方法
- 四:面试题
- 五:总结
前言
在开发过程中很多朋友,由于不会正确使用intern(),导致开发的程序,执行效率比较差。同时最近发现一道非常有意思的关于intern()的面试题,这道面试题还是有不小的难度,相信很多朋友看到以后也不知道怎么解答,所以今天咱们深入详解下intern()。
Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class String.
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.
It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.
All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java ™ Language Specification.
当调用intern()时,如果池子里已经包含了一个与这个String对象相等的字符串,正如equals(Object)方法所确定的,那么池子里的字符串会被返回。否则,这个String对象被添加到池中,并返回这个String对象的引用。
注意:这里说的池子就是字符串常量池,大白话就是,调用intern()后,如果String对象的值如果在字符串常量池中,直接返回常量池中的地址,否则这个String对象将被添加到字符串常量池中,并返回字符串常量池中的地址。
由此可见,对于任何两个字符串s和t,当且仅当s.equals(t)为真时,s.intern() == t.intern()为真。
所有字面字符串和以字符串为值的常量表达式都是interned。
一、new String()创建了几个对象?
cpp
public static void main(String[] args) {
// 2个对象
// 对象1:new String()的时候会在字符串常量池创建一个"abc"的字符串
// 对象2:在堆空间创建一个a对象
String a=new String("abc");
// 1个对象
// 在字符串常量池创建一个"ab"的字符串对象
String b="ab";
}
jvm内存结构分为方法区、堆、java虚拟机栈、本地方法栈、程序计数器等。当我们执行上面方法时。
- 首先将当前main方法压入java虚拟机栈的栈帧中。
- 在编译期的时候jvm会帮我们创建局部变量表,也就是我们当前方法的局部变量表。
- 局部变量有两个变量a和b,
局部变量a的地址指向的是堆空间的对象a,对象a指向的是字符串常量池的"abc"
局部变量b的地址指向的是字符串常量池的"ab";
二、Stting a=new String("ab")+new String("c")创建了几个对象
cpp
public static void main(String[] args) {
// 6个对象
// 对象1:new String("ab") 在堆空间创建一个对象
// 对象2:new String("ab") 在字符串常量池创建"ab"
// 对象3:new String("c") 在堆空间创建一个对象
// 对象4:new String("c") 在字符串常量池创建"c"
// 对象5:创建StringBuffer对象,在我们进行符串拼接的过程中,java底层会使用StringBuffer使用append进行拼接
// 对象6:最后会调用StringBuffer的tostring方法在堆空间创建abc对象
String abc=new String("ab")+new String("c")
}
在上述例子当中我们一共创建了6个对象。可能大家都好奇,字符串"abc"怎么没有去创建。因为在我们StringBuffer中,它会单独维护一个char数组去创建我们拼接的字符串,所以这里就不会去字符串常量池申请"abc"
三、String的intern()方法
当调用intern()时,如果池子里已经包含了一个与这个String对象相等的字符串,正如equals(Object)方法所确定的,那么池子里的字符串会被返回。否则,这个String对象被添加到池中,并返回这个String对象的引用。
注意:这里说的池子就是字符串常量池,大白话就是,调用intern()后,如果String对象的值如果在字符串常量池中,直接返回常量池中的地址,否则这个String对象将被添加到字符串常量池中,并返回字符串常量池中的地址。
cpp
public static void main(String[] args) {
String a=new String("abc");
String b="abc";
System.out.println(a==b); // false
String a1=new String("abcd").intern();
String b1="abcd";
System.out.println(a1==b1); // true
}
第一个案例是false,其实在上面已经和大家解释过了,第一个a指向的是堆空间的a对象(字符串"abc存储在a对象当中"),第二个b指向的是字符串常量池的"abc" 所以是false。
第二个案例是true,因为当我们创建a1时,它首先还是会先在堆空间创建a对象,然后在字符串常量池创建"abcd"字符串,堆空间的a对象指向字符串常量池的"abcd"。因为我们调用了intern()方法,所以在这里它会直接返回的是字符串常量池的"abcd"地址。 b1则是直接指向字符串常量池的"abcd",所以返回结果是true。
cpp
public static void main(String[] args) {
String a = new String("ab") + new String("c");
String b = "abc";
System.out.println(a == b); // false
String a1 = new String("ab") + new String("cd");
a1.intern();
String b1 = "abcd";
System.out.println(a1 == b1); // true
}
在上述这个案例中,第一个打印的是false第二个打印的是true。
第一个案例false,其实在上面已经和大家解释过了,第一个a指向的是堆空间的a对象(字符串"abc存储在a对象当中"),第二个b指向的是字符串常量池的"abc" 所以是false。
第二个案例是true,因为在这个案例中,我们a1指向的是堆空间的a1对象,a1对象里面存储了我们的"abcd",当我们调用intern()方法时,它会去字符串常量池创建一个对象,然后把当前对象的地址指向我们的a1的堆空间地址。(这里会有点绕,因为我们已经创建了字符串"abcd",所以为了节省空间。就不会在去字符串常量池去申请一个"abcd",而是直接创建一个对象指向我们堆空间的a1对象)。
四:面试题
有了对以上的知识的了解,我们现在再来看常见的面试或笔试题就很简单了:
cpp
Q:下列程序的输出结果:
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);
A:true,均指向常量池中对象。
Q:下列程序的输出结果:
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);
A:false,两个引用指向堆中的不同对象。
Q:下列程序的输出结果:
String s1 = "abc";
String s2 = "a";
String s3 = "bc";
String s4 = s2 + s3;
System.out.println(s1 == s4);
A:false,因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。
Q:下列程序的输出结果:
String s1 = "abc";
final String s2 = "a";
final String s3 = "bc";
String s4 = s2 + s3;
System.out.println(s1 == s4);
A:true,因为final变量在编译后会直接替换成对应的值,所以实际上等于s4="a"+"bc",而这种情况下,编译器会直接合并为s4="abc",所以最终s1==s4。
Q:下列程序的输出结果:
String s = new String("abc");
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s == s1.intern());
System.out.println(s == s2.intern());
System.out.println(s1 == s2.intern());
A:false,false,true。
Q:下列程序的输出结果:
String a=new String("abc");
String b="abc";
System.out.println(a==b);
String a1=new String("abcd").intern();
String b1="abcd";
System.out.println(a1==b1);
A:false,true
Q:下列程序的输出结果:
String a = new String("ab") + new String("c");
String b = "abc";
System.out.println(a == b);
String a1 = new String("ab") + new String("cd");
a1.intern();
String b1 = "abcd";
System.out.println(a1 == b1);
A:false,true
五:总结
对于程序中大量使用存在的字符串时,尤其存在很多已经重复的字符串时,使用intern()方法能够节省内存空间。
大的网站平台,需要内存中存储大量的字符串。比如社交网站,很多人都存储:北京市、海淀区等信息。这时候如果字符串都调用intern()方法,就会很明显降低内存的大小。
以下是java在不同版本中String字符串的内存变化,希望可以对你有所帮助。