Java String拼接性能分析

StringAdd, StringBuffer, StringBuilder, StringBuilderHelper性能对比分析

测试环境

硬件

CPU

Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz

基准速度:   1.80 GHz
插槽: 1
内核: 4
逻辑处理器:  8
虚拟化:    已启用
L1 缓存:  256 KB
L2 缓存:  1.0 MB
L3 缓存:  6.0 MB

内存

8.0 GB

速度: 2133 MHz
已使用的插槽: 2/2
外形规格:   Row of chips
为硬件保留的内存:   154 MB

软件

IDEA ULTIMATE 2018.3
JMH Benchmark框架 1.21

测试类

/**
 * 比较字符串直接相加和StringBuilder的效率
 */
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
@Threads(1)
@Fork(1)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class StringBuilderBenchmark {
    private String repeatItem;
    private int repeatTimes;
    
    @Setup
    public void init() {
        repeatItem = "Benchmark";
        repeatTimes = 100;
    }

    @Benchmark
    public void stringAdd(Blackhole bh) {
        String a = "";
        for (int i = 0; i < repeatTimes; i++) {
            a += repeatItem;
        }
        bh.consume(a);
    }

    @Benchmark
    public void stringBufferAdd(Blackhole bh) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < repeatTimes; i++) {
            sb.append(repeatItem);
        }
        bh.consume(sb.toString());
    }

    @Benchmark
    public void stringBuilderAdd(Blackhole bh) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < repeatTimes; i++) {
            sb.append(repeatItem);
        }
        bh.consume(sb.toString());
    }

    @Benchmark
    public void stringBuilderHelperAdd(Blackhole bh) {
        StringBuilder sb = StringBuilderHelper.getStringBuilder();
        for (int i = 0; i < repeatTimes; i++) {
            sb.append(repeatItem);
        }
        bh.consume(sb.toString());
    }
}

public final class StringBuilderHelper {
    private static final ThreadLocal<StringBuilder> TLSB = ThreadLocal.withInitial(StringBuilder::new);

    private StringBuilderHelper() {
    }

    public static StringBuilder getStringBuilder() {
        StringBuilder stringBuilder = TLSB.get();
        stringBuilder.setLength(0);
        return stringBuilder;
    }
}

生成短字符串

"Benchmark"重复拼接20次

这里取平均耗时,单位微秒,下同。

@Setup
public void init() {
    repeatItem = "Benchmark";
    repeatTimes = 20;
}
 
Benchmark                                      Mode  Cnt  Score   Error  Units
StringBuilderBenchmark.stringAdd               avgt    5  0.406 ± 0.010  us/op
StringBuilderBenchmark.stringBufferAdd         avgt    5  0.262 ± 0.005  us/op
StringBuilderBenchmark.stringBuilderAdd        avgt    5  0.259 ± 0.033  us/op
StringBuilderBenchmark.stringBuilderHelperAdd  avgt    5  0.152 ± 0.020  us/op

"BenchmarkBenchmarkBenchmarkBenchmarkBenchmark"重复拼接20次

@Setup
public void init() {
    repeatItem = "BenchmarkBenchmarkBenchmarkBenchmarkBenchmark";
    repeatTimes = 20;
}

Benchmark                                      Mode  Cnt  Score   Error  Units
StringBuilderBenchmark.stringAdd               avgt    5  1.717 ± 0.010  us/op
StringBuilderBenchmark.stringBufferAdd         avgt    5  0.861 ± 0.007  us/op
StringBuilderBenchmark.stringBuilderAdd        avgt    5  0.846 ± 0.016  us/op
StringBuilderBenchmark.stringBuilderHelperAdd  avgt    5  0.249 ± 0.023  us/op

生成中长字符串

"Benchmark"重复拼接100次

@Setup
public void init() {
    repeatItem = "Benchmark";
    repeatTimes = 100;
}
 
Benchmark                                      Mode  Cnt  Score   Error  Units
StringBuilderBenchmark.stringAdd               avgt    5  8.103 ± 0.128  us/op
StringBuilderBenchmark.stringBufferAdd         avgt    5  1.198 ± 0.010  us/op
StringBuilderBenchmark.stringBuilderAdd        avgt    5  1.113 ± 0.025  us/op
StringBuilderBenchmark.stringBuilderHelperAdd  avgt    5  0.663 ± 0.009  us/op

"BenchmarkBenchmarkBenchmarkBenchmarkBenchmark"重复拼接100次

@Setup
public void init() {
    repeatItem = "BenchmarkBenchmarkBenchmarkBenchmarkBenchmark";
    repeatTimes = 100;
}

Benchmark                                      Mode  Cnt   Score   Error  Units
StringBuilderBenchmark.stringAdd               avgt    5  38.761 ± 1.753  us/op
StringBuilderBenchmark.stringBufferAdd         avgt    5   3.432 ± 0.036  us/op
StringBuilderBenchmark.stringBuilderAdd        avgt    5   3.240 ± 0.043  us/op
StringBuilderBenchmark.stringBuilderHelperAdd  avgt    5   1.367 ± 0.021  us/op

生成长字符串

"Benchmark"重复拼接1000次

@Setup
public void init() {
    repeatItem = "Benchmark";
    repeatTimes = 1000;
}

Benchmark                                      Mode  Cnt    Score    Error  Units
StringBuilderBenchmark.stringAdd               avgt    5  761.627 ± 16.109  us/op
StringBuilderBenchmark.stringBufferAdd         avgt    5   10.885 ±  0.144  us/op
StringBuilderBenchmark.stringBuilderAdd        avgt    5   10.016 ±  0.179  us/op
StringBuilderBenchmark.stringBuilderHelperAdd  avgt    5    7.148 ±  0.107  us/op

"BenchmarkBenchmarkBenchmarkBenchmarkBenchmark"重复拼接1000次

@Setup
public void init() {
    repeatItem = "BenchmarkBenchmarkBenchmarkBenchmarkBenchmark";
    repeatTimes = 1000;
}

Benchmark                                      Mode  Cnt     Score    Error  Units
StringBuilderBenchmark.stringAdd               avgt    5  4035.181 ± 59.044  us/op
StringBuilderBenchmark.stringBufferAdd         avgt    5    35.703 ±  9.170  us/op
StringBuilderBenchmark.stringBuilderAdd        avgt    5    32.452 ±  5.626  us/op
StringBuilderBenchmark.stringBuilderHelperAdd  avgt    5    17.628 ±  5.808  us/op

生成超长字符串

"Benchmark"重复拼接10000次

@Setup
public void init() {
    repeatItem = "Benchmark";
    repeatTimes = 10000;
}
Benchmark                                      Mode  Cnt      Score      Error  Units
StringBuilderBenchmark.stringAdd               avgt    5  78718.895 ± 1009.605  us/op
StringBuilderBenchmark.stringBufferAdd         avgt    5    131.177 ±   37.317  us/op
StringBuilderBenchmark.stringBuilderAdd        avgt    5    119.158 ±   21.876  us/op
StringBuilderBenchmark.stringBuilderHelperAdd  avgt    5     81.608 ±    5.685  us/op

"BenchmarkBenchmarkBenchmarkBenchmarkBenchmark"重复拼接10000次

@Setup
public void init() {
    repeatItem = "BenchmarkBenchmarkBenchmarkBenchmarkBenchmark";
    repeatTimes = 10000;
}
Benchmark                                      Mode  Cnt       Score        Error  Units
StringBuilderBenchmark.stringAdd               avgt    5  704055.930 ± 991698.568  us/op
StringBuilderBenchmark.stringBufferAdd         avgt    5     426.180 ±      1.483  us/op
StringBuilderBenchmark.stringBuilderAdd        avgt    5     420.615 ±      2.914  us/op
StringBuilderBenchmark.stringBuilderHelperAdd  avgt    5     177.804 ±     17.385  us/op

生成超长字符串+多线程

"BenchmarkBenchmarkBenchmarkBenchmarkBenchmark"重复拼接10000次,10个线程

@Threads(10)
@Setup
public void init() {
    repeatItem = "BenchmarkBenchmarkBenchmarkBenchmarkBenchmark";
    repeatTimes = 10000;
}

Benchmark                                       Mode  Cnt        Score       Error  Units
StringBuilderBenchmark.stringAdd                avgt    5  6877607.658 ± 68654.544  us/op
StringBuilderBenchmark.stringBufferAdd          avgt    5     3792.719 ±    33.263  us/op
StringBuilderBenchmark.stringBuilderAdd         avgt    5     3780.851 ±    79.109  us/op
StringBuilderBenchmark.stringBuilderHelperAdd   avgt    5     1387.908 ±    67.224  us/op

结果分析

  • 短字符串,如"Benchmark"重复 20次,String+的性能是其他三种方法的1/2,差距不是太大。
  • 中长字符串开始,如"Benchmark"重复100次,String+的性能被拉开。
  • StringBuffer和StringBuilder在单线程场景下,性能非常接近,后者略优。
  • StringBuilderHelper(ThreadLocal版的StringBuilder)是性能最好的版本,尤其是大颗粒大字符串情况下("BenchmarkBenchmarkBenchmarkBenchmarkBenchmark"重复10000次对比"Benchmark"重复10000次),相较于StringBuilder和StringBuffer的性能,提升一倍多。
  • StringBuilderHelper在线程池下的效率会更高(因为线程不销毁,StringBuilder实例不会被销毁,能够最大程度复用)。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 12月6-7日,团委在相城和东山校区开展了主题为 “青春奉献美丽中国梦,团聚共绘青春苏农图” 特别团日活动。园艺科...
    泗水留年阅读 555评论 0 0
  • 成都,我的第二故乡。我的故乡她不美,该怎么形容她呢? 大概是2005年,第一次来成都。怀着各种心情,除了点点恐惧,...
    了梦一川阅读 197评论 0 1
  • 如果夜色沉寂 是否能够拾起 夕阳里的记忆 字字端祥,事无巨细 如果爱有天意 能否不再背离 倾注一生的意义 故事柔软...
    子觀阅读 232评论 0 3
  • 我是一个九零后,同时我也是个淘宝店主。我从一个连开店流程都要靠百度的小白卖家。到月赚三万多的小资卖家。 对的,我的...
    轻奢女王阅读 566评论 0 0