String、StringBuilder、StringBuffer

165 阅读3分钟

实现原理

String和StringBuilder、StringBuffer的内部实现都是通过数组数据结构实现的,并且在Java9之前它们底层存储都是用的char数组,在Java9之后都改为byte数组。

Java9之后用byte存储数据就涉及到了字符编码问题,一个byte是无法存储一个汉字的,因为一个汉字占两个字节,byte占一个字节。查看Java9的源码可以发现,在原来的实现中不止修改了数据存储类型,还增加了一个成员属性“coder”。

image-20220627200859157.png

可以看到,在coder属性上面可以看到有编码“LATIN1”和“UTF16”,也就是说在Java9以后字符串有两种实现,一种是“LATIN1”和“UTF16”,“LATIN1”用于一个字符一个字节的英文字符这种,查看源码:

image-20220627200956898.png

在创建String的时候会尝试压缩字符串中的值,如果压缩成功也就是可以理解为字符串中没有中文字符串,则直接byte数组长度等于字符串长度;如果压缩失败,则使用UTF16,跟踪StringUTF16的源码,可以看到它将会申请2*字符串长度的空间用于存放字符串。

image-20220627201036928.png

String和StringBuilder及StringBuffer在Java9之后均采用这种方式存储数据。

默认长度为16,扩容大小为2*n+2扩容,原理调用Arrays.copyOf将数据复制到新的数组中。

String

String的编译优化以及常量池

String类型变量在接收一个常量字符串的时候,会创建一个String对象并放入常量池,但是如果是两个常量用+连接的时候编译的时候会优化成一个String对象放入常量池。

Stirng s = "ab"; 这种就是常量字符串赋值创建字符串对象,这种会放入常量池

String s = new String("ab"); 这种是通过构造函数创建字符串对象,这种创建对象不会放在常量池,每次new都是一个新的对象

如:

 String s1 = "ab";
 String s2 = "a" + "b";
 System.out.println(s1==s2);// 结果为true

通过字节码反编译结果为;

 String s1 = "ab";
 String s2 = "ab";

非常量池:

 String s1 = new String("ab");
 String s2 = new String("ab");
 System.out.println(s1==s2);// 结果为false

那么如果我们想把通过new创建String对象放入常量池怎么做呢,String提供了一个方法intern(),可以将通过new创建的String对象放入常量池,如果常量池中有则不放入直接返回常量池该String的地址,如果常量池没有则放入,并返回常量池中的地址。

 String s1 = new String("ab");
 String s2 = "ab";
 System.out.println(s1==s2);// 结果为false

将s1放入常量池

 String s1 = new String("ab");
 s1 = s1.intern();// 放入常量池
 String s2 = "ab";// 直接从常量池中取
 System.out.println(s1==s2);// 结果为true

如果两个对象使用+号连接,则会创建一个新的对象

如:

 String s2 = "a" + "b";
 String s3 = "a";
 String s4 = "b";
 String s5 = s3 + s4;
 System.out.println(s2==s5);// 结果为false

StringBuilder和StringBuffer

StringBuilder和StringBuffer它们都是AbstractStringBuilder的实现,它们两个大多数方法都是父类实现的,区别在于StringBuilder是线程不安全的,StringBuffer是线程安全的,通过synchronized关键字来保证线程安全。

初始值大小都是16

它们的扩容都是AbstractStringBuilder实现的,扩容机制为:

int newCapacity = (value.length << 1) + 2;

为什么要加2 :考虑一下长度为0的情况,0*2为0,StringBuilder和StringBuffer有传容量大小的构造器。

另外StringBuilder和StringBuffer的append方法是一种建造者的设计模式运用。