String,StringBuffer和StringBuilder之我的理解

String和StringBuffer都是JDK 1.0提出来的,StringBuilder是JDK 1.5才有的,StringBuffer和StringBuilder都继承AbstractStringBuilder,下面分别对这三个类的效率进行描述
1 String是不可变对象,对String对象的改变分几种情况

String s = "a" + "b";这种情况等价于 String s = "ab";

看一下虚拟机的指令:
Code:
0: ldc #2 // String ab
2: astore_1
3: return
直接就是一个字符串ab,并没有重新new一个对象

另外一种情况:

String s1 = "a";
String s2 = "b";
String s3 = s1 + s2;

来看一下虚拟机指令:
Code:
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder.””:()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: return
可以看到,经过优化的虚拟机的执行过程其实是new 了一个StringBuilder,然后分别把a和b append进去,最后调用toString()赋值给s3
如果像这样写程序:

String s = “”;  
for(int i = 0; i < 100; i++) { s += "a"; }

那么就会new很多个StringBuilder,最后再由垃圾回收器回收,这样的话,效率非常低下,所以引入了StringBuffer和StringBuilder

2 StingBuffer是JDK1.0就有的,这个类是线程同步的,可以保证安全性,看源码

@Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

源码中很多方法都加上了synchronize锁,而StringBuilder是JDK1.5才被提出,这个类是非线程安全的,在单线程的环境下,效率会比StringBuffer高很多,锁机制虽然带来了安全性,但是也同样带来了效率低下的问题,另外我们在一般项目中,大多数都是在单线程下执行某一个任务,这个时候就推荐使用StringBuilder了,StringBuffer,StringBuilder和String不同,这两个都号称是可变的对象,也就是说增加一个字符串在末尾或者插入一个字符串,不需要重新new一个对象,只需要append或者insert即可,那么他们实现的原理是怎样的呢,接下来通过源码来解析,通过上面的代码,我们看到其实是调用了父类的append,接下来我们分析他们的父类
3 AbstractStringBuilder 直接看append方法

char[] value;
AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

这里我们看到了一个char类型的数组value,初始大小是传进来的capacity,那么这个capacity是多少呢,看StringBuffer的构造方法:

public StringBuffer() {
        super(16);
    }

很清楚,一开始初始化的大小是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;
    }

ensureCapacityInternal这个方法是关键,看下怎么实现的

private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }

    /** * This implements the expansion semantics of ensureCapacity with no * size check or synchronization. */
    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);
    }

到这里应该很明确了,其实就是个char类型的数组,会根据append进来的字符串的长度来进行扩展,如果当前append的字符串长度(包括前面已append的)大于本来char的大小了,那么就需要扩容,最终调用
value = Arrays.copyOf(value, newCapacity);

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;
    }

其实也是需要重新new一个char类型的数组来进行扩容的。

总结:String是不可变对象,除常量字符串或者数字相加,虚拟机不重新new之外,其他的改动虚拟机需要new StringBuilder对象来进行处理(我用的jdk 1.8,早期jdk可能是StringBuffer),比较耗时,StringBuffer和StringBuilder都是可变对象,实现原理是通过一个char类型的数组,StringBuffer是线程安全的,StringBuilder是非线程安全的,但更高效。至于里面的一些接口,这里就不谈了,具体可以看api文档,都很好理解。

你可能感兴趣的:(String,StringBuilder,StringBuffer)