昨天还在for循环里写加号拼接字符串的那个同事,今天已经不在了

共 9291字,需浏览 19分钟

 ·

2023-08-22 10:31

点击关注公众号:互联网架构师,后台回复   2T 获取 2TB 学习资源!

上一篇:2T架构师学习资料干货分享

作者:后青春期的 Keats

来源:cnblogs.com/keatsCoder/p/13212289.html


引言


都说 StringBuilder 在处理字符串拼接上效率要强于 String,但有时候我们的理解可能会存在一定的偏差。最近我在测试数据导入效率的时候就发现我以前对 StringBuilder 的部分理解是错误的。后来我通过实践测试 + 找原理 的方式搞清楚了这块的逻辑。现在将过程分享给大家。


测试用例


我们的代码在循环中拼接字符串一般有两种情况:
  • 第一种就是每次循环将对象中的几个字段拼接成一个新字段,再赋值给对象
  • 第二种操作是在循环外创建一个字符串对象,每次循环向该字符串拼接新的内容。循环结束后得到拼接好的字符串

对于这两种情况,我创建了两个对照组。


第一组:


在每次 For 循环中拼接字符串,即拼即用、用完即毁。分别使用 String 和 StringBuilder 拼接

        
          
            /**
          
        
        
          
             * 循环内 String 拼接字符串,一次循环后销毁
          
        
        
          
             */
          
        
        
          public static void useString(){
        
        
            for (int i = 0; i < CYCLE_NUM_BIGGER; i++) {
        
        
              String str = str1 + i + str2 + i + str3 + i + str4 ;
        
        
            }
        
        
          }
        
        
          
            
/** * 循环内 使用 StringBuilder 拼接字符串,一次循环后销毁 */ public static void useStringBuilder(){ for (int i = 0; i < CYCLE_NUM_BIGGER; i++) { StringBuilder sb = new StringBuilder(); String s = sb.append(str1).append(i).append(str2).append(i).append(str3).append(i).append(str4).toString(); } }
      

第二组:


多次 For 循环拼接一个字符串,循环结束后使用字符串,使用后由垃圾回收器回收。也是分别使用 String 和 StringBuilder 拼接。

        
          /**
        
        
           * 多次循环拼接成一个字符串 用 String
        
        
           */
        
        
          public static void useStringSpliceOneStr (){
        
        
            String str = "";
        
        
            for (int i = 0; i < CYCLE_NUM_LOWER; i++) {
        
        
              str += str1 + str2 + str3 + str4 + i;
        
        
            }
        
        
          }
        
        
          
            
/** * 多次循环拼接成一个字符串 用 StringBuilder */ public static void useStringBuilderSpliceOneStr(){ StringBuilder sb = new StringBuilder(); for (int i = 0; i < CYCLE_NUM_LOWER; i++) { sb.append(str1).append(str2).append(str3).append(str4).append(i); } }
      

为了保证测试质量,在每个测试项目进行前。线程休息 2s,之后空跑 5 次热身。最后执行 5 次求平均时间的方式计算时间

        
          public static int executeSometime(int kind, int num) throws InterruptedException {
        
        
            Thread.sleep(2000);
        
        
            int sum = 0;
        
        
            for (int i = 0; i < num + 5; i++) {
        
        
              long begin = System.currentTimeMillis();
        
        
          
            
switch (kind){ case 1: useString(); break; case 2: useStringBuilder(); break; case 3: useStringSpliceOneStr(); break; case 4: useStringBuilderSpliceOneStr(); break; default: return 0; }
long end = System.currentTimeMillis();
if(i > 5){ sum += (end - begin); } } return sum / num; }
      

主方法

        
          public class StringTest {
        
        
              public static final int CYCLE_NUM_BIGGER = 10_000_000;
        
        
              public static final int CYCLE_NUM_LOWER = 10_000;
        
        
              public static final String str1 = "张三";
        
        
              public static final String str2 = "李四";
        
        
              public static final String str3 = "王五";
        
        
              public static final String str4 = "赵六";
        
        
          
            

public static void main(String[] args) throws InterruptedException { int time = 0; int num = 5;
time = executeSometime(1, num); System.out.println("String拼接 "+ CYCLE_NUM_BIGGER +" 次," + num + "次平均时间:" + time + " ms");
time = executeSometime(2, num); System.out.println("StringBuilder拼接 "+ CYCLE_NUM_BIGGER +" 次," + num + "次平均时间:" + time + " ms");
time = executeSometime(3, num); System.out.println("String拼接单个字符串 "+ CYCLE_NUM_LOWER +" 次," + num + "次平均时间:" + time + " ms");
time = executeSometime(4, num); System.out.println("StringBuilder拼接单个字符串 "+ CYCLE_NUM_LOWER +" 次," + num + "次平均时间:" + time + " ms");
} }


测试结果


下:

79c9e52899e2e85b94b98dd34dc5d0f9.webp


结果分析


第一组


10_000_000 次循环拼接,在循环内使用 String 和 StringBuilder 的效率是一样的!为什么呢?


使用 javap -c StringTest.class 反编译查看两个方法编译后的文件:

26462d56a8f30e021f614cac913772e2.webp


可以发现 String 方法拼接字符串编译器优化后使用的就是 StringBuilder、因此用例 1 和用例 2 的效率是一样的。


第二组


第二组的结果就是大家喜闻乐见的了,由于 10_000_000 次循环 String 拼接实在太慢所以我采用了 10_000 次拼接来分析。


分析用例 3:虽然编译器会对 String 拼接做优化,但是它每次在循环内创建 StringBuilder 对象,在循环内销毁。下次循环他有创建。相比较用例 4 在循环外创建,多了 n 次 new 对象、销毁对象的操作、n - 1 次将 StringBuilder 转换成 String 的操作 。效率低也是理所应当了。


扩展


第一组的测试还有一种写法:
        
          
            /**
          
        
        
          
             * 循环内 使用 StringBuilder 拼接字符串,一次循环后销毁
          
        
        
          
             */
          
        
        
          public static void useStringBuilderOut(){
        
        
            StringBuilder sb = new StringBuilder();
        
        
            for (int i = 0; i < CYCLE_NUM_BIGGER; i++) {
        
        
          
            //            sb.setLength(0);
          
        
        
              sb.delete(0, sb.length());
        
        
              String s = sb.append(str1).append(i).append(str2).append(i).append(str3).append(i).append(str4).toString();
        
        
            }
        
        
          }
        
      
      

循环外创建 StringBuilder 每次循环开始的时候清空 StringBuilder 的内容然后拼接。这种写法无论使用 sb.setLength(0); 还是 sb.delete(0, sb.length()); 效率都比直接在循环内使用 String / StringBuilder 慢。奈何才疏学浅我一直想不明白为什么他慢。我猜测是 new 对象的速度比重置长度慢,于是这样测试了以下:

        
          public static void createStringBuider() {
        
        
            for (int i = 0; i < CYCLE_NUM_BIGGER; i++) {
        
        
              StringBuilder sb = new StringBuilder();
        
        
            }
        
        
          }
        
        
          
            
public static void cleanStringBuider() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < CYCLE_NUM_BIGGER; i++) { sb.delete(0, sb.length()); } }
      

但是结果是 cleanStringBuider 更快。让我摸不着头脑。


如果有大神看到希望可以帮忙分析分析


结论


  • 编译器会将 String 拼接优化成使用 StringBuilder,但是还是有一些缺陷的。主要体现在循环内使用字符串拼接,编译器不会创建单个 StringBuilder 以复用
  • 对于多次循环内拼接一个字符串的需求:StringBuilder 很快,因为其避免了 n 次 new 对象、销毁对象的操作,n - 1 次将 StringBuilder 转换成 String 的操作
  • StringBuilder 拼接不适用于循环内每次拼接即用的操作方式。因为编译器优化后的 String 拼接也是使用 StringBuilder 两者的效率一样。后者写起来还方便...


最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。


正文结束


推荐阅读 ↓↓↓

1.JetBrains 如何看待自己的软件在中国被频繁破解?

2.无意中发现了一位清华妹子的资料库!

3.程序员一般可以从什么平台接私活?

4.40岁,刚被裁,想说点啥。

5.为什么国内 996 干不过国外的 955呢?

6.中国的铁路订票系统在世界上属于什么水平?                        

7.15张图看懂瞎忙和高效的区别!

6bf40fd830c69b7b70fd95a55cafeb33.webp
浏览 27
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报