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

 


目录
相关文章
|
6天前
|
安全 Java
String、StringBuffer、StringBuilder的区别
这篇文章讨论了Java中String、StringBuffer和StringBuilder的区别。String是不可变的,每次操作都会产生新的对象,效率低且浪费内存。StringBuilder可以在原字符串基础上进行操作,不开辟额外内存,弥补了String的缺陷。StringBuffer和StringBuilder类似,但StringBuffer的方法是线程安全的。文章还列举了StringBuffer的常用方法,并提供了使用示例代码。最后总结了这三者的主要区别。
String、StringBuffer、StringBuilder的区别
|
3天前
|
索引
Sass String(字符串) 函数
Sass String(字符串) 函数用于处理字符串并获取相关信息。
11 1
|
1月前
|
存储 C++
C++(五)String 字符串类
本文档详细介绍了C++中的`string`类,包括定义、初始化、字符串比较及数值与字符串之间的转换方法。`string`类简化了字符串处理,提供了丰富的功能如字符串查找、比较、拼接和替换等。文档通过示例代码展示了如何使用这些功能,并介绍了如何将数值转换为字符串以及反之亦然的方法。此外,还展示了如何使用`string`数组存储和遍历多个字符串。
|
2月前
|
C# 开发者 UED
WPF开发者必备秘籍:深度解析文件对话框使用技巧,打开与保存文件原来如此简单!
【8月更文挑战第31天】在WPF应用开发中,文件操作是常见需求。本文详细介绍了如何利用`Microsoft.Win32`命名空间下的`OpenFileDialog`和`SaveFileDialog`类来正确实现文件打开与保存功能。通过示例代码展示了如何设置文件过滤器、初始目录等属性,并使用对话框进行文件读写操作。正确使用文件对话框能显著提升用户体验,使应用更友好易用。
51 0
|
2月前
|
API C# 开发者
WPF图形绘制大师指南:GDI+与Direct2D完美融合,带你玩转高性能图形处理秘籍!
【8月更文挑战第31天】GDI+与Direct2D的结合为WPF图形绘制提供了强大的工具集。通过合理地使用这两种技术,开发者可以创造出性能优异且视觉效果丰富的WPF应用程序。在实际应用中,开发者应根据项目需求和技术背景,权衡利弊,选择最合适的技术方案。
46 0
|
2月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
64 1
|
5天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
12天前
|
Java Spring
spring多线程实现+合理设置最大线程数和核心线程数
本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
72 10
spring多线程实现+合理设置最大线程数和核心线程数
|
21天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
36 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
6天前
|
Python
5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...
5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...