Java中字符串的那些事儿

本文详细探讨了Java中字符串常量池的工作原理,比较了与newString()的创建方式,解析了+拼接与StringBuilder的底层机制,并剖析了StringBuilder与StringBuffer的异同,包括面试中常见的相关问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符串常量池

首先我们要明白,当我们创建一个字符串对象时,这个字符串会储存到jvm中的字符串常量池中。jdk1.7中字符串常量池在方法区中,然而从jdk1.8开始,字符串常量池在堆内存中。String内部是用final修饰的数组,所以它一旦创建值是不可变的。

""与new String()

String str1 = "123";
String str2 = new String("123");
System.out.println(str1 == str2);

这两种方式创建有什么区别呢?str1引用地址直接为字符串常量区中"123"的引用地址,而str2的引用地址则为堆内存中创建的String对象的引用地址,而这个String对象中的value值的引用地址则为字符串常量区中"123"的引用地址。 所以结果为false。用一张图表示如下。
在这里插入图片描述

字符串使用+拼接

  • 两个字符串常量拼接
String str1 = "123" + "456";

如果字符串是常量拼接,jvm其实做了优化,字符串常量池中的数据直接为"123456",通过反汇编class我们可以得出该结论。
在这里插入图片描述
在这里插入图片描述

  • 两个变量的拼接
String a = "123";
String b = "456";
String c = a + b;

这样拼接,底层实际上会创建一个StringBuilder对象,通过append()方法对字符串a和b进行追加,最后再通过toString方法得到字符串c。
在这里插入图片描述

StringBuild底层

+拼接与append()

String str = "";
for (int i = 0; i < 60000; i++) {
   str += i;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 60000; i++) {
    sb.append(i);
}

上面两段代码执行起来速度差异很大。前面我们说过字符串变量追加底层默认使用的是StringBuilder,但是他没完成一次追加都会执行toString()方法转换成字符串,而这一操作也会将结果添加到字符串常量池中,因为toString()方法直接上就是创建一个字符串对象。这样操作会在字符串常量池中添加很多没用的数据。浪费内存的同时也减慢速度,因为要不断的创建StringBuilder对象追加,转换成字符串再放到字符串常量池中。那么直接创建一个StringBuilder对象,进行追加只是对其value数组进行尾插,并不会将结果存入字符串常量池中去。

初始化

StringBuilder无参构造默认是创建一个大小为16的字符数组,而有参构造则是创建一个当前字符串长度+16的字符数组
在这里插入图片描述
在这里插入图片描述

追加数据

在这里插入图片描述
count:当前数组长度
可以看到追加的思路要先确保当前数组空间满足要追加的字符串长度,然后使用getChars进行数组copy,最后count加上追加的字符串长度。

在这里插入图片描述
这是实际上是将追加的字符串copy到StringBuilder数组中去。
value:要追加字符串的字符数组
srcBegin:追加字符串的开始位置
dest:StringBuilder的字符数组
destBegin:StringBuilder字符数组开始追加的位置

数组扩容

在这里插入图片描述
我们可以看到如果当前容量小于追加字符串后的容量时,那么就要进行数组扩容。
在这里插入图片描述
再获取新数组长度的时候,首先默认会将当前数组容量*2+2,如果这个容量比追加字符串之后的容量小,那么新的容量为追加字符串之后的容量。

StringBuilder与StringBuffer的区别

StringBuffer线程安全,StringBuilder线程不安全。多线程时要使用StringBuffer。
在这里插入图片描述
可以看到StringBuffer中append方法上了锁,并且将toStringCache置为null,toStringCache也是一个字符数组,我们在toString()方法中可以看到它的身影。
在这里插入图片描述
StringBuffer在调用toString()方法时首先会判断toStringCache是否为null,为null会将字符数组copy到toStringCache中去,最后通过toStringCache去创建字符串对象。

常见面试题

有了上面的基础,看下面的面试题应该会轻松很多。

1、判断以下代码结果

String s1="abc";
String s2="abc";
System.out.println(s1==s2); 

答:结果为true,"abc"存放到字符串常量池中。s1,s2引用地址都指向字符串常量池中的"abc"

2、下面代码创建了几个对象

String str =new String("abc");

答:创建了两个对象,String对象在堆内存中,"abc"在字符串常量池中

3、判断以下代码结果

String s1=new String("abc");
String s2="abc";
System.out.println(s1==s2); 

答:结果为false,s1指向的是string对象在堆内存的地址,s2指向的是字符串常量池中"abc"的地址

4、判断以下代码结果

String s1="a"+"b"+"c";
String s2="abc";
System.out.println(s1==s2); //true,java中有常量优化机制

答:结果为ture,因为jvm对字符串常量拼接有优化,所以s1和s2引用地址都为字符串常量池中的"abc"地址

5、判断以下代码结果

String s1="ab";
String s2="abc";
String s3=s1+"c"; //s10是指向对内存中的地址
System.out.println(s2==s3); //false,

答:结果为false,s3引用地址为堆内存用String实例的地址,而s2的引用地址为字符串常量池中"abc"的地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值