- 源码分析
- 字符串修改的性能问题
- 字符串、数组的相互转换
- String、StringBuilder、StringBuffer的比较|区别
源码分析
String部分源码
public final class String implements java.io.Serializable, Comparable, CharSequence { //存储字符串 private final char value[]; private int hash; public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); //并不是修改内置的char[],而是直接返回一个新的String对象,使用新的char[] return new String(buf, true); } }
AbstractStringBuilder封装了修改字符串的常用操作,部分源码如下
abstract class AbstractStringBuilder implements Appendable, CharSequence {
//存储字符串
char[] value;
//char[]中存储的字符数量
int count;
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
//先确保数组容量足够。扩容时会复制到新的char[]中,会改变value的指向
ensureCapacityInternal(count + len);
//再在内置的char[]上修改内容。容量保证够的,直接在char[]上进行修改,不改变value指向
str.getChars(0, len, value, count);
count += len;
return this;
}
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
if (srcBegin < 0)
throw new StringIndexOutOfBoundsException(srcBegin);
if ((srcEnd < 0) || (srcEnd > count))
throw new StringIndexOutOfBoundsException(srcEnd);
if (srcBegin > srcEnd)
throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
//源数组、目标数组都是value,value的内存地址不变,只是修改其上存储的内容
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
private void ensureCapacityInternal(int minimumCapacity) {
//容量不足则自动扩容
if (minimumCapacity - value.length > 0) {
//value指向新的char[]数组
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
//新容量 = 原容量*2 + 2
int newCapacity = (value.length << 1) + 2;
//之所以有这个判断,是因为可能存在这种情况:目前容量较小,将要存储的字符串很多,超过了 原容量*2 + 2,则取实际要存储的字符个数
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
}
StringBuffer、StringBuilder未设置char[]的初始容量时,默认取16
public StringBuffer() {
super(16);
}
public StringBuilder() {
super(16);
}
都是使用char[ ]存储字符串
- 但String不可变,字符串长度就是char[ ]的长度,所以不需要使用一个成员变量标识字符串长度;
- StringBuilder、StringBuffer可变,char[ ]只是容器,需要用一个int型的成员变量标识实际存储的字符个数,即字符串长度
append()的操作流程
- 先确保char[ ]容量足够。容量不足时自动扩容,扩容时会复制到新的char[ ]中,然后改变value的指向
- 再在内置的char[ ]上修改内容。此时容量是保证够的,直接在char[ ]上进行修改内容,不改变value指向
内置char[ ]的扩容机制
如果所需容量大于 原容量*2 + 2,则扩容为实际所需容量(将要存储的字符长度),否则扩容为 原容量*2 + 2
字符串修改的性能问题
String str = "xxx"; str += "xxx1"; str += "xxx2";
String不可变,每次修改字符串都是创建新串返回,性能是不是比StringBuffer、StringBuilder差得多?
并不是,编译器会自动优化代码,把修改String的操作转换为:创建一个StringBuilder,使用StringBuilder进行字符串的(1个或多个)修改操作,修改完成后将StringBuilder转换为String返回。
这样看String的修改性能也不错,但编译器不够智能,优化有限。比如这段代码
String str = "xxx";
for (int i = 0; i < 10; i++) {
str += "xxx";
}
编译器会优化为:在循环体中,创建一个StringBuilder,使用StringBuilder进行字符串的修改,转为String返回。每次循环都会创建一个新的StringBuilder对象,性能显然不行。
不要太依赖编译器的优化,尽量自己优化代码
StringBuilder sb = new StringBuilder("xxx");
for (int i = 0; i < 10; i++) {
sb.append("xxx");
}
字符串、数组的相互转换
//字符串切割为数组,参数指定分割符
String str = "张三&&李四&&王五";
String[] arr = str.split("&&");
//数组连接成字符串,第二个参数指定连接符
//方式一,使用 org.apache.commons.lang.StringUtils
String str1 = StringUtils.join(arr, "&&");
//方式二、使用 org.apache.poi.util.StringUtil
String str2 = StringUtil.join(arr, "&&");
String、StringBuilder、StringBuffer的比较|区别
1、整体结构
- 都实现了Serializable、CharSequence接口
- String还实现了Comparable
接口,可用于比较、排序 - StringBuilder、StringBuffer还继承了抽象类AbstractStringBuilder
2、可变性
- String不可变,修改字符串是在副本上操作,以新串形式返回,原串不变。
- StringBuffer、StringBuilder都是可变的,修改字符串是在原串上操作,原串改变。
可变、不可变指的是对象本身,变量本身持有的引用都是可变的。
3、线程安全
- String、StringBuilder没有使用任何保证线程安全的机制,线程不安全
- StringBuffer:操作字符串的方法都使用synchronized修饰,线程安全,但为保证线程安全,有额外的开销,性能比如String、StringBuilder
使用建议
- 需要考虑线程安全的用StringBuffer
- 不需要考虑线程安全、需要频繁修改字符串的用StringBuilder
- 不需要考虑线程安全、也不需要频繁修改字符串的用String



