概述
阅读 Java 版本为 1.8.0.25。
先看 StringBuffer
和 StringBuilder
的继承关系图。
StringBuffer
和 StringBuilder
都继承了 AbstractStringBuilder ,都是通过 char[] 数组实现,因为未加 final 修饰符,所以可修改。
两者简单来说:
- StringBuffer :是一个线程安全类,方法使用
synchronized
修饰加锁,执行速度稍微慢点。 - StringBuilder :线程不安全,执行速度快。单线程情况下,拼接次数多使用。
- String :少量字符串可以直接使用 + 拼接。
设计目的
在拼接字符串的时候,我们先需要创建字符串 String
String 也是一个对象,它的底层是一个 final
的数组:
private final char value[];
所以,String 是不可变的,每次操作一个字符串都会创建一个新的字符串,大大影响了运行效率。
例如以下示例中:
public static void main(String[] args) {
long begin = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 100000; i++) {
s += i;
}
System.out.println(System.currentTimeMillis() - begin);
begin = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append(i);
}
System.out.println(System.currentTimeMillis() - begin);
}
运行时间如下:
24504
2
这简直差的不是一点半点啊。。。
所以这就是StringBuffer
和 StringBuilder
的设计目的,它的效率确实要比 +
拼接高多了。
PS:在连续的确定字符串拼接中,编译器会帮你优化。
例如以下拼接:
public static void main(String[] args) {
String last = "http://" + "shiva.show" + "/test?params=666";
System.out.println(last);
}
在 class 文件中,是这样的:
public static void main(String[] args) {
String last = "http://shiva.show/test?params=666";
System.out.println(last);
}
当然,这种情况确实写在一起就行了,也没必要再拼接。。。。
如果中间有个变量,那编译器就优化不了了。
实现原理
其实我们在使用 StringBuffer
或 StringBuilder
时,基本都是这种模式:
StringBuilder sb = new StringBuilder();
sb.append("http://").append(host)
.append(":").append(port)
.append("/").append(name)
.append("params=").append(params);
return sb.toString();
从这段代码来看:
构造方法
StringBuffer
或 StringBuilder
都继承自 AbstractStringBuilder ,并且没有对构造方法进行太多改造。
public StringBuilder() {
super(16);
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
默认数据长度为16。
对已知较长的字符串拼接可以提前设置较大的初始长度,原因下面就讲。
数组扩容
在 append() 方法中,如果长度超出数组长度,将进行数组扩容:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
数组扩容为双倍扩容,扩容会进行数组复制,所以也会进行资源消耗。因此,构造时可以事先确定一个较长的数组。
当然,这点消耗简直可以忽略。。。。
StringBuffer 粗锁化
首先来说下,啥是粗锁化。
Hotspot 确实进行了锁粗化优化,可以有效合并几个相邻同步块,从而降低锁开销。能够把下面的代码:
synchronized (obj) {
// 语句 1
}
synchronized (obj) {
// 语句 2
}
转化为:
synchronized (obj) {
// 语句 1
// 语句 2
}
And Then。。。意思就是,把上面的代码一改:
StringBuffer sb = new StringBuffer();
sb.append("http://").append(host)
.append(":").append(port)
.append("/").append(name)
.append("params=").append(params);
return sb.toString();
中间一段 synchronized 会进行粗锁化,合并为一个大的同步块,这样就减少了加锁解锁的次数,有效地提升了代码执行的效率
呃呃呃呃呃呃
也就是说,其实性能差距并没有想象中那么大。
扩展内容
Java9 改进了字符串(包括String、StringBuffer、StringBuilder)的实现。
在 Java9 以前字符串采用 char[] 数组来保存字符,因此字符串的每个字符占2字节;而 Java9 的字符串采用 byte[]数组再加一个 encoding-flag 字段来保存字符,因此字符串的每个字符只占1字节。
所以 Java9 的字符串更加节省空间,字符串的功能方法也没有受到影响。
这些是看到的别的博客,公司一直用的 JDK 8,我也就没看过更高版本。
参考文章
https://blog.csdn.net/weixin_41101173/article/details/79677982