StringBuilder_学习笔记

参考:https://www.jianshu.com/p/160c9be0b132java

链接符号 "+" 本质

字符串变量(非final修饰)经过 "+" 进行拼接,在编译过程当中会转化为StringBuilder对象的append操做,注意是编译过程,而不是在JVM中。数组

public class StringTest {
    public static void main(String[] args) {
        String str1 = "hello ";
        String str2 = "java";
        String str3 = str1 + str2 + "!";
        String str4 = new StringBuilder().append(str1).append(str2).append("!").toString();
    }
}

上述 str3 和 str4 的执行效果实际上是同样的,不过在for循环中,千万不要使用 "+" 进行字符串拼接。并发

public class test {
    public static void main(String[] args) {
        run1();
        run2();
    }   

    public static void run1() {
        long start = System.currentTimeMillis();
        String result = "";
        for (int i = 0; i < 10000; i++) {
            result += i;
        }
        System.out.println(System.currentTimeMillis() - start);
    }
    
    public static void run2() {
         long start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            builder.append(i);
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}

 

在for循环中使用 "+" 和StringBuilder进行1万次字符串拼接,耗时状况以下:
一、使用 "+" 拼接,平均耗时 250ms;
二、使用StringBuilder拼接,平均耗时 1ms;app

for循环中使用 "+" 拼接为何这么慢?下面是run1方法的字节码指令ide

5 ~ 34 行对应for循环的代码,能够发现,每次循环都会从新初始化StringBuilder对象,致使性能问题的出现。性能

性能问题

StringBuilder内部维护了一个char[]类型的value,用来保存经过append方法添加的内容,经过 new StringBuilder() 初始化时,char[]的默认长度为16,若是append第17个字符,会发生什么?测试

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

 

若是value的剩余容量,没法添加所有内容,则经过expandCapacity(int minimumCapacity)方法对value进行扩容,其中minimumCapacity = 原value长度 + append添加的内容长度。
一、扩大容量为原来的两倍 + 2,为何要 + 2,而不是恰好两倍?
二、若是扩容以后,仍是没法添加所有内容,则将 minimumCapacity 做为最终的容量大小;
三、利用 System.arraycopy 方法对原value数据进行复制;ui

在使用StringBuilder时,若是给定一个合适的初始值,能够避免因为char[]数组屡次复制而致使的性能问题。spa

不一样初始容量的性能测试:code

public class StringBuilderTest {
    public static void main(String[] args) {
        int sum = 0;
        final int capacity = 40000000;
        for (int i = 0; i < 100; i++) {
            sum += cost(capacity);
        }
        System.out.println(sum / 100);
    }

    public static long cost(int capacity) {
        long start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder(capacity);
        for (int i = 0; i < 10000000; i++) {
            builder.append("java");
        }
        return System.currentTimeMillis() - start;
    }
}

 

执行一千万次append操做,不一样初始容量的耗时状况以下:
一、容量为默认16时,平均耗时110ms;
二、容量为40000000时,不会发生复制操做,平均耗时85ms;

经过以上数据能够发现,性能损耗不是很严重。

内存问题

一、StringBuilder内部进行扩容时,会新建一个大小为原来两倍+2的char数组,并复制原char数组到新数组,致使内存的消耗,增长GC的压力。
二、StringBuilder的toString方法,也会形成char数组的浪费。

public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

 

String的构造方法中,会新建一个大小相等的char数组,并使用 System.arraycopy() 复制StringBuilder中char数组的数据,这样StringBuilder的char数组就白白浪费了。

重用StringBuilder

public class StringBuilderHolder {
    private final StringBuilder sb;
    public StringBuilderHolder(int capacity) {
        sb = new StringBuilder(capacity);
    }

    public StringBuilder resetAndGet() {
        sb.setLength(0);
        return sb;
    }
}

 

经过 sb.setLength(0) 方法能够把char数组的内存区域设置为0,这样char数组重复使用,为了不并发访问,能够在ThreadLocal中使用StringBuilderHolder,使用方式以下:

private static final ThreadLocal<StringBuilderHolder> stringBuilder= new ThreadLocal<StringBuilderHolder>() {
    @Override
    protected StringBuilderHolder initialValue() {
        return new StringBuilderHolder(256);
    }
};
 
StringBuilder sb = stringBuilder.get().resetAndGet();

 

不过这种方式也存在一个问题,该StringBuilder实例的内存空间一直不会被GC回收,若是char数组在某次操做中被扩容到一个很大的值,可能以后很长一段时间都不会用到如此大的空间,就会形成内存的浪费。

总结

虽然使用默认的StringBuilder进行字符串拼接操做,性能消耗不是很严重,但在高性能场景下,仍是推荐使用ThreadLocal下可重用的StringBuilder方案。

相关文章
相关标签/搜索