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


    一、字符串连接的效率问题

    使用String连接字符串时为什么慢?

    小知识点

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

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

    过多无用的中间对象

    每次连接字符串时都会创建一个新的String对象,随着拼接次数的增多,这个对象会越来越大。 如,进行100次拼接需要创建100个String对象才能够达到目的。

    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对象中追加字符串时: 

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

    2.确认是否需要进行扩容操作

    • 最小所需容量minimumCapacity是否比原数组长度要长,即当原数组长度不能满足所需最小容量时进行扩容操作。
    • 计算扩容之后的容量newCapacity,newCapacity = (value.length * 2) + 2。
    • 扩容后是否还小于所需的最小容量,如果小于则直接设置新容量为最小所需容量minimumCapacity。
    • 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的末尾 

    效率高的原因

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

    二、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();
                }
    
            }
        }
    }

    三、结论

    1. String为固定长度的字符串,StringBuilder和StringBuffer为变长字符串。
    2. StringBuffer是线程安全的,StringBuilder是非线程安全的。
    3. StringBuilder和StringBuffer的默认初始容量是16,可以提前预估好字符串的长度,进一步减少扩容带来的额外开销。
  • 相关阅读:
    bitcoin PoW原理及区块创建过程
    Hyperledger Fabric(v1.1.0)编译时遇到的问题
    Hyperledger Fabic中的Transaction流程
    mint linux 18.3 遇到“已安装的 post-installation 脚本 返回了错误号 127 ”问题的解决
    redis--解析字符串
    golang 统计uint64 数字二进制存储中1的数量
    c++ std 最小堆的使用 (用于实现top100之类的功能)
    Linux 信号signal处理函数
    Linux 信号signal处理机制
    LinuxMint 下 B站 番 blv 缓存 转 mp4
  • 原文地址:https://www.cnblogs.com/lingyejun/p/11407615.html
Copyright © 2020-2023  润新知