在 Java 编码中会非常频繁的使用字符串。字符串对应的操作类有3个,分别是String
、StringBuffer
和StringBuilder
。
类型 | final | 变量/常量 | 线程安全 | 效率 |
---|---|---|---|---|
String | 是 | 常量 | - | 低 |
StringBuffer | 是 | 变量 | 线程安全 | 高 |
StringBuilder | 是 | 变量 | 线程不安全 | 高 |
下图是三种类型的UML类图:
从图片可以看出,StringBuffer
和StringBuilder
非常相似,其实两者的区别也仅仅在于StringBuffer
中的方法是用synchronized
修饰的,即通过锁来实现线程安全。
上表中已经介绍了三种类型的效率,下边通过代码,直观的看一下效果。
public class TestDemo
{
private final int LOOP_NUM = 20000;
private final String STRING_ITEM = "test_string_time";
public static void main(String[] args) {
new TestDemo().test();
}
public void testString(){
String string = "";
long startTime = System.currentTimeMillis();
for(int i = 0; i < LOOP_NUM; i++){
string += STRING_ITEM;
}
long endTime = System.currentTimeMillis();
System.out.print("String:" + (endTime - startTime) + " ");
}
public void testStringBuffer(){
StringBuffer sbf = new StringBuffer();
long startTime = System.currentTimeMillis();
for(int i = 0; i < LOOP_NUM; i++){
sbf.append(STRING_ITEM);
}
sbf.toString();
long endTime = System.currentTimeMillis();
System.out.print("StringBuffer : " + (endTime - startTime) + " ");
}
public void testStringBuilder(){
StringBuilder sbu = new StringBuilder();
long startTime = System.currentTimeMillis();
for(int i = 0; i < LOOP_NUM; i++){
sbu.append(STRING_ITEM);
}
sbu.toString();
long endTime = System.currentTimeMillis();
System.out.print("StringBuilder : " + (endTime - startTime) + " ");
}
public void test(){
for(int i = 0; i < 5; i++){
testString();
testStringBuffer();
testStringBuilder();
System.out.println();
}
}
}
运行结果如下:
String:6300 | StringBuffer : 2 | StringBuilder : 2
String:5555 | StringBuffer : 2 | StringBuilder : 0
String:5563 | StringBuffer : 2 | StringBuilder : 2
String:5553 | StringBuffer : 2 | StringBuilder : 2
String:5565 | StringBuffer : 1 | StringBuilder : 1
可以看到String
的效率远远低于StringBuffer
和StringBuilder
。
1)StringBuffer
的构造器
使用StringBuffer
的无参构造器,则会分配一个长度为16的字符串缓冲区。如果使用String参数构造器,则是分配长度为入参长度+16的字符串缓冲区。
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
2)append
实现
在StringBuffer
中的实现如下。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
可见,此处是使用synchronized
关键字进行修饰的,其实际逻辑是在父类中的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;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
从代码可知,当append
进来新的字符串的时候,会判断当前缓冲区是否能够容纳完整字符串,如果不能,则调用Arrays.copyOf
方法通过数组复制实现缓冲区的扩展。
此处可能会有疑问,频繁调用Arrays.copyOf
方法进行数组复制,不会产生性能问题吗?其实Arrays.copyOf
的性能消耗是比较小的,其内部是调用System.arraycopy
方法来实现的。而System.arraycopy
是Jni调用本地方法,速度非常快。
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
参考资料