手把手实例对比String、StringBuilder字符串的链接效率及StringBuilder和StringBuffer线程安全的比较

1、字符串链接的效率问题

使用String链接字符串时为何慢?

小知识点

java中对数组进行初始化后,该数组所占的内存空间、数组长度都是不可变的。java

建立一个字符串,为字符串对象分配内存空间,会耗费掉必定的时间(CPU)与空间(内存)代价,做为最基础的数据类型,大量频繁的建立字符串,极大程度地影响程序的性能。数组

过多无用的中间对象

每次链接字符串时都会建立一个新的String对象,随着拼接次数的增多,这个对象会愈来愈大。 如,进行100次拼接须要建立100个String对象才可以达到目的。a安全

StringBuilder在链接时为何效率更高?

字符数组的扩容机制:

private void ensureCapacityInternal(int minimumCapacity) {
		 // 最小所需容量minimumCapacity是否比原数组长度要长
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
    
private int newCapacity(int minCapacity) {
		 // 计算扩容以后的容量newCapacity
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        // 扩容后还小于所需的最小容量
        if (newCapacity - minCapacity < 0) {
        	// 设置新容量为最小所需容量minimumCapacity
            newCapacity = minCapacity;
        }
        // newCapacity是否溢出,newCapacity是否比数组所能分配的最大容量 MAX_ARRAY_SIZE 还要大。
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

    private int hugeCapacity(int minCapacity) {
    	// 最小所需容量minCapacity大于Integer.MAX_VALUE时抛出内存溢出异常
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        // 若是minCapacity介于MAX_ARRAY_SIZE和Integer.MAX_VALUE之间,则新的容量为minCapacity,不然直接使用MAX_ARRAY_SIZE做为新的容量。
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }
复制代码

向原StringBuilder对象中追加字符串时:bash

1.追加对象str为null时追加'null'字符并发

2.确认是否须要进行扩容操做app

2.1 最小所需容量minimumCapacity是否比原数组长度要长,即当原数组长度不能知足所需最小容量时进行扩容操做。  
2.2 计算扩容以后的容量newCapacity,newCapacity = (value.length * 2) + 2。  
2.3 扩容后是否还小于所需的最小容量,若是小于则直接设置新容量为最小所需容量minimumCapacity。  
2.4 newCapacity是否溢出,newCapacity是否比数组所能分配的最大容量 MAX_ARRAY_SIZE 还要大。若是是的话则判断,最小所需容量minCapacity大于Integer.MAX_VALUE时抛出内存溢出异常,若是minCapacity介于MAX_ARRAY_SIZE和Integer.MAX_VALUE之间,则新的容量为minCapacity,不然直接使用MAX_ARRAY_SIZE做为新的容量。  
复制代码

3.str.getChars()将str追加到value的末尾ide

效率高的缘由

  1. 扩容机制保证了,只有在知足扩容条件 minimumCapacity - value.length > 0 时才会进行扩容生成新的数组,因此大部分状况都是在对原数组进行操做,避免了产生过多的无用char[]对象,节省了系统资源的开销。

代码

/**
 * 比较字符串链接速度
 *
 * @Author: lingyejun
 * @Date: 2019/8/17
 * @Describe:
 * @Modified By:
 */
public class LinkCompare {

    /**
     * 原始字符串链接
     *
     * @param times
     */
    public static void linkByString(int times) {

        Long startTime = System.currentTimeMillis();

        String initStr = "";
        for (int i = 0; i < times; i++) {
            initStr = initStr + i;
        }

        Long endTime = System.currentTimeMillis();

        System.out.println("String 链接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");
    }

    /**
     * 使用StringBuilder链接字符串
     *
     * @param times
     */
    public static void linkByStringBuilder(int times) {

        Long startTime = System.currentTimeMillis();

        StringBuilder initStr = new StringBuilder();
        for (int i = 0; i < times; i++) {
            initStr.append(i);
        }

        Long endTime = System.currentTimeMillis();

        System.out.println("StringBuilder 链接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");
    }


    /**
     * 使用StringBuffer链接字符串
     *
     * @param times
     */
    public static void linkByStringBuffer(int times) {

        Long startTime = System.currentTimeMillis();

        StringBuffer initStr = new StringBuffer();
        for (int i = 0; i < times; i++) {
            initStr.append(i);
        }

        Long endTime = System.currentTimeMillis();

        System.out.println("StringBuffer 链接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");
    }


    public static void main(String[] args) {

        // 100000000
        linkByStringBuilder(40000);
        //-XX:+PrintGCDetails
        //linkByString(40000);

    }
}

复制代码

2、StringBuilder和String Buffer的线程安全比较

验证StringBuffer的线程安全性

线程不安全的缘由

public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    
public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
复制代码

测试代码

import java.util.ArrayList;
import java.util.List;

/**
 * StringBuilder和StringBuffer的并发测验
 *
 * @Author: lingyejun
 * @Date: 2019/8/17
 * @Describe:
 * @Modified By:
 */
public class SecurityCompare {

    public void stringBuilderTest() {

        // 初始化StringBuilder
        StringBuilder stringBuilder = new StringBuilder();

        // joinList
        List<StringBuilderThread> joinList = new ArrayList<>();

        // 模拟并发场景
        for (int i = 0; i < 1000; i++) {
            StringBuilderThread sbt = new StringBuilderThread(stringBuilder);
            sbt.start();
            joinList.add(sbt);
        }

        // 等待append线程执行完毕后再执行主线程
        for (StringBuilderThread thread : joinList) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 打印最终的结果
        System.out.println("StringBuilder 并发append的结果: " + stringBuilder.length());
    }

    public void stringBufferTest() {

        // 初始化StringBuffer
        StringBuffer stringBuffer = new StringBuffer();

        // joinList
        List<StringBufferThread> joinList = new ArrayList<>();

        // 模拟并发场景
        for (int i = 0; i < 1000; i++) {
            StringBufferThread sbf = new StringBufferThread(stringBuffer);
            sbf.start();
            joinList.add(sbf);
        }

        // 等待append线程执行完毕后再执行主线程
        for (StringBufferThread thread : joinList) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 打印最终的结果
        System.out.println("StringBuffer 并发append的结果: " + stringBuffer.length());
    }


    public static void main(String[] args) {

        SecurityCompare securityCompare = new SecurityCompare();

        securityCompare.stringBuilderTest();
        securityCompare.stringBufferTest();

    }

    public static class StringBuilderThread extends Thread {

        private StringBuilder stringBuilder;

        public StringBuilderThread(StringBuilder stringBuilder) {
            this.stringBuilder = stringBuilder;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stringBuilder.append("a");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    private static class StringBufferThread extends Thread {

        private StringBuffer stringBuffer;

        public StringBufferThread(StringBuffer stringBuffer) {
            this.stringBuffer = stringBuffer;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stringBuffer.append("a");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

复制代码

3、结论

1.String为固定长度的字符串,StringBuilder和StringBuffer为变长字符串。
2.StringBuffer是线程安全的,StringBuilder是非线程安全的。
3.StringBuilder和StringBuffer的默认初始容量是16,能够提早预估好字符串的长度,进一步减小扩容带来的额外开销。性能

相关文章
相关标签/搜索