代码中字符串的使用对于每个程序员来说都司空见惯,大家用起来也得心应手,所以很少会花时间深入研究。但是在笔试面试中,却常常被提问,而且往往是看似熟悉的程序,却无法说出自己坚信的答案。而且今天的重点是String.inter()。今天花时间整理一下,一来自己做一个记录,二来希望帮助更多的猿类!Hahaha~
先热热身做俩道题:
Q:下列程序的输出结果:
String s1 = “abc”;
String s2 = “abc”;
System.out.println(s1 == s2);
A:true
分析一下:程序执行,常量池中生成一个字符串,并将栈中的引用指向 s1,s2 先去查询常量池中是否有已经存在,存在,则返回常量池中的引用,所以s1 = s2。
Q:下列程序的输出结果:
String s1 = new String(“abc”);
String s2 = new String(“abc”);
System.out.println(s1 == s2);
A:false,两个引用指向堆中的不同对象。
分析一下:第一步、常量池中生成一个字符串“abc”,并在堆中生成一个s1引用指向的对象,第二步、先去查询常量池中是否有已经存在“abc”,因为已经存在,所以不在生成,但堆中仍然会生成一个s2引用指向的对象,所以s1 , s2引用不一样。
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来完成,会生成不同的对象。
分析一下:程序执行,s1,s2,s3在常量池中分别生成字符串,并将栈中的引用指向s1,s2,s3,而s4在编译的时候,其方法内部创建StringBuilder来拼接对象,在堆中生成了新的对象。
引号声明的字符串都是会直接在字符串常量池中生成的,而 new 出来的 String 对象是放在堆空间中的。
接下来好戏开始:
public static void main(String[] args) {
String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
System.out.println(str1==str2);//#1
System.out.print (str1==str3);//#2
}
输出结果为: false true
#1:str1指向常量池中的对象引用,str2指向堆中的对象引用,所以返回false;
#2:str2.intern()指向str1的在常量池中的对象引用,所以str1==str3。
public static void main(String[] args) {
String abc = "abc";
final String abcFinal = "abc";
String str1 = "abc01";
String str2 = "abc"+"01";
String str3 = abc + "01";
String str4 = abcFinal+"01";
String str5 = new String("abc01").intern();
System.out.println(str1 == str2);//#3
System.out.println(str1 == str3);//#4
System.out.println(str1 == str4);//#5
System.out.println(str1 == str5);//#6
}
输出的结果为:true false true true
#3:str1指向常量池中的对象引用,而str2在编译期间,+号会自动合并为一个字符串,因为常量池中已经生成了str1的对象,所以str2不在生成,直接将常量池对象的引用赋值给栈中的str2。
#4:str4实际上是使用StringBuilder.append来完成,会生成不同的对象。
#5:final修饰的变量,在编译期间会自动进行常量替换,这是与#4不同。所以不在生成新的对象,直接将常量池对象的引用str1赋值给栈中的str4。
#6:都是指向常量池中的对象引用。
public static void main(String[] args) {
String str2 = new String("abc")+new String("01");
str2.intern();
String str1 = "abc01";
System.out.println(str2==str1);//#7
}
输出的结果为:true
#7:str2在常量池中分别生成了俩个对象,“abc”和“01”,堆中则存的是abc01的引用,并赋值给了str2,str2.intern()在常量池中生成了一个abc01的引用,并赋值给了str2,str1在生成对象之前先是查找,找到并直接返回str2在常量池中生成的引用。
public static void main(String[] args) {
String str1 = "abc01";
String str2 = new String("abc")+new String("01");
str2.intern();
System.out.println(str2 == str1);//#8
}
输出的结果为:false
#8:str1指向常量池中的对象引用,而str2指向堆中的对象引用,并在常量池生成俩个对象,“abc”和“01”,接着str2.intern()返回str1指向常量池中的对象引用,但是对于str2没有影响,结果为false,如果是str2 = str2.intern();则最终结果将是true。
上面讲了一些原理,那么为什么要使用String的intern()方法呢,甚至是非用不可??
假设有这样的一个问题,我们需要读取一个大的文件(文件中有重复的一些信息),并且要处理这些读取出来的字符串,这时候我们应该怎样编写程序才能使程序高效执行呢?
Tips:是不是首先想到用Set,HashSet来存这些字符串,回忆一下Set集合的特点,无序,不可重复。在仔细一想,无序:基于链表的数据结构,查询效率低,不方便我们处理大量的字符串;不可重复:在set值的时候,先先生成HashCode,再迭代查询是否有重复的值。。。那么有没有更好的方法呢?
intern()方法设计的初衷,就是重用String对象,以节省内存消耗。
点击打开链接
但是调用intern()方法开销也不小所以灵活运用。