首先来看一到面试题,不管你能不能答上来,我都相信你能够在这篇文章学到一些你以前不知道的知识
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
String a1 = new String("c") + new String("d");
String a2 = "cd";
a1.intern();
System.out.println(a1 == a2);
String b1 = new String("q") + new String("w");
b1.intern();
String b2 = "qw";
System.out.println(b1 == b2);
你能够全部答上来,并知道底层的原理吗?
我们先来说一下jvm内存结构
大概的画了一下结构图,我们关注的是堆中的这个StringTable
StringTable里面存储的是什么?
我们在程序当中经常会定义一些字符串常量 例如: String jdbcUrl = “数据库地址”;
当我们这么定义一个字符串时,这个String对应的值就会放到StringTable中
StringTable到底是什么?
就是一个帮我们存储String常量的一个东西。用的是一个哈希表存储的
正文
String s1 = “a”;
这行代码其实就是把 “a” 这个字符串放到了 StringTable中
String s2 = “b”;
同理,就是把 “b” 这个字符串放到了StringTable中
String s3 = “a” + “b”;
这行代码是把 "ab"这个字符串放到了StringTable中
为什么这里放的是 “ab”
这是因为java编译器,在编译阶段帮我们做的一个优化, 由于"a"和"b"都是常量,
在编译器期间就已经确定了该值,所以javac就帮我们优化成了"ab"
String s4 = s1 + s2;
重点就在这
这里创建了几个对象?分别创建了什么对象?
其实这行代码只创建了一个String对象,为什么重新创建对象了? 因为s1和s2都是变量,这个值在编译期间是不确定的,那么当程序运行的时候会直接创建新的对象来将这俩个值拼凑到一起,but 它还创建了一个StringBuilder对象,没错,你没看错,还创建了一个StringBuilder对象,其实当使用 “+” 来拼接字符串的时候默认就是创建一个新的StringBuilder对象来拼接,然后再调用StringBuilder的toString()方法,那么很显然,这个新的String对象是调用StringBuilder对象的toString()时才创建的
我们知道通过 new 关键字所创建的对象都会在堆中,所以s4指向的是堆中的一个引用
String s5 = “ab”;
上面我们知道 String s3 = “a” + “b”; 其实就是等于 String s3 = “ab”; 那么 “ab” 肯定是已经放到了StringTable中的
String s6 = s4.intern();
很显然这里的重点在于 intern()这个方法
是一个native方法,大概意思就是 当常量池中已经有了这个String 对象就是通过 equal()方法判断为true 的这个对象时就返回这个对象在常量池中的引用,否则就在当前对象放到常量池中
简单的理解就是 有了我就返回串池中的那个,没有我就放进去再返回
我们知道 String s4 = s1 + s2; 其实是在堆中又创建了一个String对象,但是并没有保存在常量池中,可是我们 s3 = “ab” 已经将 "ab"放到常量池中去了, 所以这里调用s4.intern()这个方法并没有将 s4的引用放到常量池去中,因为已经存在了,那么就将已经存在的那个对象返回回来。所以 s6是等于s3的
那么
s3 == s4
s3 == s5
s3 == s6
的结果是什么?
经过上面的分析,我相信你已经有了自己的答案
s3 引用的常量池中的 “ab” 而 s4 是又在堆中重新创建了一个对象值也是 “ab”, 那么他俩一个在常量池中一个在堆中,肯定是不相等的
s3 == s5 s5引用也是常量池中的"ab" s3和s5引用的是同一对象,那么它俩肯定是相等的
s3 == s6 我们知道 s6就是s3对象
s3 == s4 = false
s3 == s5 = true
s3 == s6 = true
String a1 = new String(“c”) + new String(“d”);
这行代码又创建了几个对象?
这里只算String对象 是创建了5个。为什么? 又是哪5个对象?
首先会将"c" 和 “d” 在常量池创建一份 这里就是两个对象。又分别在堆中创建了 “c” 和 “d” 这两个对象,上面分析过,字符串拼接是通过创建StringBuilder对象来调用append()方法来实现,最后调用toString()方法来创建一个新的对象。那么这里也一样,还有一个对象就是 new String(“cd”);
那么 a2 = “cd”;
此时常量池中是没有 “cd” 这对象的,那么下面 a1.intern() 这个方法将 “cd” 放到了常量池中,但此时由于 常量池中有了,那么就不会放了。所以这行代码在这就是去意义了。
a1 == a2 很显然这是不相等的。 a1指向的是堆中的对象,a2指向的是常量池中的对象
String b1 = new String(“q”) + new String(“w”);
b1.intern();
String b2 = “qw”;
经过上面的分析,我相信你已经能够理解了, b1在堆中创建对象之后,又通过intern()这个方法将"qw" 放到了常量池中
String b2 = “qw”;
执行这行代码时 "qw"已经存在了,那么将直接引用这个对象,
所以这两个对象是相等的
如何证明我上面所说的?
总不能张口就来,说什么就是什么吧。
先看下执行结果
由于通过javap 反编译之后的结果较多不好一一分析,因此我这里只分析
String s1 = new String("a") + new String("b");
这行代码是否真的和我们上面说的那样创建了5个对象?
我们可以很清晰的看到 除了再常量池保存了"a" 和 “b” 对象之外还通过 new String()的方法在堆中又分别创建了 “a” 和 “b”
然后通过调用 StringBuiler.toString()方法又创建了一个 String 对象
后面可能会考虑写一篇关于StringTable的相关知识点,以及怎么调优等等。。