实例的对比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.确认是否需要进行扩容操作

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的末尾

效率高的原因

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

原文地址:https://www.cnblogs.com/lingyejun/p/11407615.html

时间: 2024-11-10 20:28:38

实例的对比String、StringBuilder字符串的连接效率及StringBuilder和StringBuffer线程安全的比较的相关文章

python字符串连接效率问题

在python效率的讨论问题中字符串的连接效率有提过,多数建议使用join来代替"+"进行字符串连接 python中一切皆对象 字符串对象就是c api中得stringobject.c看python源码的objects文件可以找到 1."+".使用加号连接2个字符串会调用静态函数string_concat(register PyStringObject *a ,register PyObject * b),在这个函数中会开辟一块大小是a+b的内存的和的存储单元,然后

java StringBuffer,StringBuilder,String自身连接效率对比

当我们仅仅需要a+b 的时候,两个字符串链接任何方法的效率基本一样,都在0.0001毫秒内就可以完成.不过如果需要1万次,10000万次,就会发现string自身的join速度显著下降 package com.java.lang; public class StringTest { int MAX = 10000; //1万次累加 public String Buffer(){ StringBuffer sb = new StringBuffer(); for(int i = 0; i < MA

从String类型字符串的比较到StringBuffer和StringBuilder

1. String类型 String类源码 为了从本质上理解String类型的特性所在,我们从String类型的源码看起,在源码中String类的注释中存在以下: /**Strings are constant; their values cannot be changed after they * are created. String buffers support mutable strings. * Because String objects are immutable they ca

StringBuilder String string.Concat 字符串拼接速度

首先看测试代码: public class StringSpeedTest { private readonly static string _testStr = "0123456789"; public string StringAdd(int count) { string str = string.Empty; for (int i = 0; i < count; i++) { str += _testStr; } return str; } public string U

C# 数据类型之 String(字符串)

?  前言 在开发中最常见的数据类型就是 String 类型,即字符串类型.为什么要单独讨论下这个类型,是因为:它是系统内置的基础数据类型:它的使用频率非常高:它是一个特殊的引用类型.其实大家都会使用它,但可能或多或少了解不够全面,本文主要是抱着:学习.巩固.总结的目的去加深对它的了解,主要学习一下几点: 1.   什么是 string 类型 2.   创建 string 对象的方式 3.   String 的常用静态方法 4.   string 的常用实例方法 5.   string 的常用扩

String【字符串】

属性 public final class String 字符串在java中是一个类,然后这个类是一个太监类 类面里面肯定有属性 private final char value[]; final 这个变量value的引用只能被初始化一次,而且引用没法进行修改 但是数组中的值还是可以被改变的 private 就保证这个char类型数组只能被当前类所使用 而且string类中没有提供设置数组的方法,禁止外部的修改 对于我们的字符串来讲,如果我们对字符串做了拼接,删除的操作,会重新创建新的字符串 内

SSH深度历险(十一) AOP原理及相关概念学习+xml配置实例(对比注解方式的优缺点)

接上一篇 SSH深度历险(十) AOP原理及相关概念学习+AspectJ注解方式配置spring AOP,本篇我们主要是来学习使用配置XML实现AOP 本文采用强制的CGLB代理方式 SecurityHandler这个通知类可以换成安全性检测.日志管理等等. <span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18

关于JS知识,string(字符串对象)

2016年11月23日,星期三 1.增加型: String.concat:将数据连接到字符串末尾,原字符串不变,返回新的字符串. 表示: <script> var varString = "abcdef": var b = varString.concat("123"): document.write(b): </script> 2.修改型: String.toUpperCase:把整个字符串改成大写. 表示: <script>

使用strcpy和strcat实现字符串的连接

最为简单的代码方式一 首先是实现字符串的连接然后实现字符串的拷贝 #include <stdio.h> #include <string.h> int main(void) { char s[10]="abc"; char t[]="def"; char r[20]=""; strcat(s,t);         //实现字符串的连接 strcpy(r,s); //实现字符串的拷贝 printf("%s&quo