1 字符串存储的内存原理
- 直接赋值会复用字符串常量池中的内存,比较节约内存
- new 出来不会复用,而是开辟一个新的空间
2 ==号比较的到底是什么?
- 基本数据类型比较数据值
- 引用数据类型 比较地址值
3 字符串拼接的底层原理
3.1 没有变量参与的拼接
package com.bjpowernode;
public class Test {
public static void main(String[] args) {
String s = "a" + "b" + "c";
System.out.println(s);
}
}
拼接的时候没有变量,都是字符串
触发字符串的优化机制,
在编译的时候就已经是最终的结果了
3.2 有变量参与的拼接
package com.bjpowernode;
public class Test {
public static void main(String[] args) {
String s1 = "a" ;
String s2 = s1 + "b";
String s3 = s2 + "c";
System.out.println(s3);
}
}
在拼接的时候有变量
3.2.1 JDK8 以前底层会使用 StringBuilder
一个加号,堆内存中俩对象
-
String s2 = s1 + "b"; 会创建 StringBuuilder 对象 s1,和串池中的 "b" 拼接以后,在 toStriing 转成 String 对象 "ab"
-
String s3 = s2 + "c"; 会创建 StringBuuilder 对象 s2,和串池中的 "c" 拼接以后,在 toStriing 转成 String 对象 "abc"
3.2.2 JDK8 以后
字符串拼接的时候有变量参与:
- 在内存中创建了很多对象
- 浪费空间,时间也非常慢
3.3 总结
- 如果没有变量参与,都是字符串直接相加,编译之后就是拼接之后的结果会复用串池中的字符串。
- 如果有变量参与每一行拼接的代码,都会在内存中创建新的字符串,浪费内存。
- 如果很多字符串变量拼接,不要直接+。在底层会创建多个对象,浪费时间,浪费性能。
4 StringBuilder提高效率原理图
4.1 面试水题
package com.bjpowernode;
public class Test {
public static void main(String[] args) {
String s1 = "abc" ;
String s2 = "ab";
String s3 = s2 + "c";
System.out.println(s1 == s3);
}
}
运行结果是什么? false
分析:
s1 是直接赋值,所以记录的是串池中的地址值
s3 是 + 拼接操作,且有变量参与,是新 new 出来的对象
- JDK8 以前:系统底层会自动创建一个 StringBuilder 对象,然后再调用其 append 方法完成拼接。拼接后,再调用其 toString 方法转换为 String 类型,而 toString 方法的底层是直接 new 了一个字符串对象。
- JDK8 版本:系统会预估要字符串拼接之后的总大小,把要拼接的内容都放在数组中,此时也是产生一个新的字符串。
4.2 面试水题
package com.bjpowernode;
public class Test {
public static void main(String[] args) {
String s1 = "abc" ;
String s2 = "a" + "b" + "c";
System.out.println(s1 == s2);
}
}
运行结果是什么? true
分析:
s1 是直接赋值,所以记录的是串池中的地址值
s2 是没有变量参与的拼接,所以在编译成 .class 之后,就相当于是直接赋值为 "abc",而在串池中,如果字符串已经存在,就会复用(在串池中,只有字符串不存在,才会新创建)
4.3 总结
所有要拼接的内容都会往 StringBuilder 中放,不会创建很多无用的空间,节约内存
5 StringBuilder 源码分析
StringBuilder 在刚开始创建的时候,底层会创建一个字节数组,默认的容量是 16(容量即最多能装多少,长度是指实际装了多少)
如果添加的长度超过了 16,则会扩容,默认扩容:老容量 * 2 + 2 = 34
如果超出了 *2 + 2 的容量,则会以实际需要的大小为准
5.1 总结
- 默认创建一个长度为16的字节数组
- 添加的内容长度小于16,直接存
- 添加的内容大于16会扩容(原来的容量*2+2)
- 如果扩容之后还不够,以后都会以实际长度为准