JVM进阶-面试字符串拼接底层(字符串常量池)

123 阅读3分钟

前言

我们大家都知道JVM中有一个常量池的概念,他的出现是为了省略我们一些基本变量重复创建导致空间浪费,我们在创建变量时变量将存入常量池中在下次创建我们就会直接引用字符串常量池中的对象。

但常量池究竟是如何运作的我们接下来看看吧

首先在阅读这篇文章的时候需要对JVM内存模型有一定的了解

示例

String a = "aa";
String b = "a"+"a";
String c = "a";
String d = "a"+c;


sout(a == b); // true
sout(a == d); // false

====================================================

String a = new StringBuilder("aa").toString();
String b = "aa";
sout(a == b); //false

==================================================

String a = new StringBuild("aa").append("bb").toString();
String b = "aabb";
sout(a == b); //true

字符串常量池

在我们使用String b = "XXX"或者是aa("XX")这种直接引号形式静态声明字符串,都会向常量池中添加数据

1.6及以前有着永久代的概念(存储字符串常量池、对象class、静态变量) image.png

JDK1.6后字符串常量池就到了栈内存中、永久代被元空间替代了

image.png

字符串常量池其实很像HashTable,key中存储对象指针,value中存储对象在堆空间中的内存地址,在我们程序中对象引用的就是我们的字符串常量池中的对象指针。!!!在每一次创建这种静态声明形式会先在常量池中遍历一遍找到了与他相同的对象就直接引用、没有找到就直接创建(这个创建又会遍历一次堆内存发现又相同的对象就直接引用到字符串常量池中,没有就自己创建)!!!

结合示例

String a = "aa";
// 这一行在JVM编译的时候会对其进行优化变为:String b = "aa"
String b = "a"+"a";

在第一行执行的时候JVM中的字符串常量池还没有这个aa值的对象,那么在第一行会在堆内存中创建一个aa的字符串对象并将他记录到字符串常量池中

第二行执行的时候JVM先遍历字符串常量池,发现找到了那么就直接指向字符串常量池

我们可以发现他们引用的都是一个对象所以a、b等值

Stirng a = "a"
String b = "a" + a;

这一种形式JVM没有办法在编译的时候优化
在执行b的时候JVM会底层调用创建一个StringBuile然后将两个字符串都append进去然后再toString所以a、b不等值

String a = new StringBuilder("aa").toString();
String b = "aa";
sout(a == b); //false

在这个理解起来会复杂一点,但是也非常简单

第一行:
    1.首先发现我们有一个字符串静态声明,这里会记入字符串常量池中
    2.new StringBuilder()会创建一个aa的StringBuilder对象
    3.使用StringBuild.toString()在底层会使用new String()所以这里又创建了一个变量
    
第二行:这里直接拿到我们字符串常量池中的数据

到这里我们就很好理解了a为toString后的值,b为字符串常量池中的值,所以a、b不等值
String a = new StringBuild("aa").append("bb").toString();
String b = "aabb";
sout(a == b); //true

这个就然大家来判断一下吧
主要就是,在创建添加字符串常量池中会寻找堆中有没有相同的对象如果有就直接引用

后言

其他的基本类型的常量池就很简单了

Boolean在程序中就定义好了

image.png

Integer 在创建的时候就存储-128~127的值

image.png

intern

比如StringA.intern()

这个方法就是从字符串常量池中获取与这个String值相同的key如果找到了就返回字符串常量池的引用,如果没有找到就会创建(创建流程与上文讲述相似)