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

 


目录
打赏
0
0
0
0
12
分享
相关文章
鸿蒙开发:ArkTs字符串string
字符串类型是开发中非常重要的一个数据类型,除了上述的方法概述之外,还有String对象,正则等其他的用处,我们放到以后得篇章中讲述。
77 19
课时16:String字符串
课时16介绍了Java中的String字符串。在Java中,字符串使用`String`类表示,并用双引号定义。例如:`String str = &quot;Hello world!&quot;;`。字符串支持使用“+”进行连接操作,如`str += &quot;world&quot;;`。需要注意的是,当“+”用于字符串与其他数据类型时,其他类型会先转换为字符串再进行连接。此外,字符串中可以使用转义字符(如`\t`、`\n`)进行特殊字符的处理。掌握这些基本概念对Java编程至关重要。
|
5月前
|
String(字符串)
String(字符串)。
90 3
|
6月前
|
Redis 字符串(String)
10月更文挑战第16天
78 4
|
2月前
|
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
58 17
|
2月前
|
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
64 26
|
4月前
|
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
337 2
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
4月前
|
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
81 10
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等