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

简介: 手把手实例对比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,可以提前预估好字符串的长度,进一步减少扩容带来的额外开销。

 


目录
相关文章
|
2月前
|
安全 Java
Java StringBuffer 和 StringBuilder 类
Java StringBuffer 和 StringBuilder 类
16 0
|
6天前
|
存储 编解码 算法
Java 的 String StringBuilder StringBuffer(上)
Java 的 String StringBuilder StringBuffer
25 0
|
21天前
|
移动开发 安全 Java
String、StringBuffer 、StringBuilder、StringJoiner
String、StringBuffer 、StringBuilder、StringJoiner
|
2月前
|
存储 算法 安全
【数据结构与算法初学者指南】【冲击蓝桥篇】String与StringBuilder的区别和用法
【数据结构与算法初学者指南】【冲击蓝桥篇】String与StringBuilder的区别和用法
|
20天前
|
Java API 索引
Java基础—笔记—String篇
本文介绍了Java中的`String`类、包的管理和API文档的使用。包用于分类管理Java程序,同包下类无需导包,不同包需导入。使用API时,可按类名搜索、查看包、介绍、构造器和方法。方法命名能暗示其功能,注意参数和返回值。`String`创建有两种方式:双引号创建(常量池,共享)和构造器`new`(每次新建对象)。此外,列举了`String`的常用方法,如`length()`、`charAt()`、`equals()`、`substring()`等。
15 0
|
2月前
|
Java
【Java】如果一个集合中类型是String如何使用拉姆达表达式 进行Bigdecimal类型计算?
【Java】如果一个集合中类型是String如何使用拉姆达表达式 进行Bigdecimal类型计算?
25 0
|
2月前
|
Java
Java String split()方法详细教程
Java String split()方法详细教程
25 0
|
2月前
|
存储 缓存 安全
【Java】Java中String不可变性的底层实现
【Java】Java中String不可变性的底层实现
18 0
|
2月前
|
Java 索引
Java中String方法学习总结_kaic
Java中String方法学习总结_kaic
|
2天前
|
存储 安全 Java
聊聊Java中的常用类String
聊聊Java中的常用类String
6 0