String 作为最基础的引用数据类型,日常的开发中被大量的使用。基于不可变的特性,一旦被过度地使用,堆内存就会负荷不堪,甚至影响性能,为此,Java 设计者特意为 String 在方法区中开辟了字符串常量池,以减少 String 的实例创建,然而,在面对大数量的情况下,字符串常量池也未必能解决问题,因此,AbstractStringBuilder 应运而生,就是为了解决 String频繁创建而引发的内存性能下降的问题。
带着两个问题,去看看String / StringBuffer / StringBuilder 的区别
String vs AbstractStringBuilder
StringBuffer vs StringBuilder
String / StringBuffer / StringBuilder 的使用策略
String vs AbstractStringBuilder
-
扩容机制
-
String
不可变性:重新创建一个对象
String 底层代码实现:
String 类被 final 修饰,该类不能被继承
value[] 属性 被final 修饰 ,引用不能修改
-
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[]; //
/** Cache the hash code for the string */
private int hash; // Default to 0```
//other codes
测试代码:
String str = new String("a");
str = str + “b” ;
图示:
-
AbstractStringBuilder
可变性
AbstractStringBuilder 底层代码实现:
value[] 相对于 String ,没有被final修饰
append("String") 返回时对象本身,不会创建新的对象
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
// other codes
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;
}
//other codes
}
测试代码:
StringBuffer sb = new StringBuffer("a");
sb.append("b");
图示:
性能比较
public class StringBufferWithStringBuilder {
public void testString() {
long start = System.currentTimeMillis();
String str = null;
for (int i = 0; i < 20000; i++) {
str = str + i + ",";
}
System.out.println(System.currentTimeMillis() - start);
}
public void testStringBuffer() {
long start = System.currentTimeMillis();
StringBuffer sbuf = new StringBuffer();
for (int i = 0; i < 20000; i++) {
sbuf.append(i + ",");
}
System.out.println(System.currentTimeMillis() - start);
}
public void testStringBulider() {
long start = System.currentTimeMillis();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 20000; i++) {
builder.append(i + ",");
}
System.out.println(System.currentTimeMillis() - start);
}
@Test
public void test(){
testString();
testStringBuffer();
testStringBulider();
}
}
通过测试数据得知,在性能和效率上:StringBuilder>StringBuffer>String
原因在于:
String 每执行一次 + 重载运算符,必须创建一个新的对象
StringBuilder 与 StringBuffer相比,少了同步锁
StringBuffer vs StringBuilder
-
线程安全
StringBuffer 是线程安全的
StringBuilder 是线程不安全
底层实现: StringBuffer 通过 synchronized 关键字的修饰,保证了资源不会被抢占,从而确保了线程安全
/**
* @since 1.5
*/
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}
String / StringBuffer / StringBuilder 的使用策略
基本原则:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer
不要使用String类的"+"来进行频繁的拼接,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则
为了获得更好的性能,在构造 StringBuffer 或 StringBuilder 时应尽可能指定它们的容量。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能
StringBuilder一般使用在方法内部来完成类似"+"功能,因为是线程不安全的,所以用完以后可以丢弃。StringBuffer主要用在全局变量中
相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且确定你的模块不会运行在多线程模式下,才可以采用StringBuilder;否则还是用StringBuffer
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。