深入分析Java使用+和StringBuilder进行字符串拼接的差异

本文转载自深入分析Java使用+和StringBuilder进行字符串拼接的差异 - 临渊羡鱼不如退而结网 - ITeye技术网站,并对原文有一定的删改,我觉得对于很多人来说没有必要的部分进行的删除。

部分原文:

今天看到有网友在我的博客留言,讨论java中String在进行拼接时使用+和StringBuilder和StringBuffer中的执行速度差异很大,而且之前看的书上说java在编译的时候会自动将+替换为StringBuilder或StringBuffer,但对于这些我都没有做深入的研究,今天准备花一点时间,仔细研究一下。
       首先看一下java编译器在编译的时候自动替换+为StringBuilder或StringBuffer的部分,代码如下。
       测试环境为win764位系统,8G内存,CPU为 i5-3470,JDK版本为32位的JDK1.6.0_38
       第一次使用的测试代码为:

  public static void main(String[] args) {
       // TODO Auto-generated method stub
       String demoString="";
       int execTimes=10000;
       if(args!=null&&args.length>0)
       {
           execTimes=Integer.parseInt(args[0]);
       }
       System.out.println("execTimes="+execTimes);
       long starMs=System.currentTimeMillis();
       for(int i=0;i<execTimes;i++)
       {
           demoString=demoString+i;
       }
       long endMs=System.currentTimeMillis();
       System.out.println("+ exec millis="+(endMs-starMs));
    }
输入不同参数时的执行时间如下:

C:\>java StringAppendDemo 100
execTimes=100
+ exec millis=0
C:\>java StringAppendDemo 1000
execTimes=1000
+ exec millis=6
C:\>java StringAppendDemo 10000
execTimes=10000
+ exec millis=220
C:\>java StringAppendDemo 100000
execTimes=100000
+ exec millis=44267

可以看到,输入的参数为10000和100000时,其执行时间从0.2秒到了44秒。
我们先使用javap命令看一下编译后的代码:
javap –c StringAppendDemo
这里我摘录了和循环拼接字符串有关的那部分代码,具体为:

51:  lstore_3
  52:  iconst_0
  53:  istore  5
  55:  iload   5
  57:  iload_2
  58:  if_icmpge       87
  61:  new     #5; //class java/lang/StringBuilder
  64:  dup
  65:  invokespecial   #6; //Method java/lang/StringBuilder."<init>":()V
  68:  aload_1
  69:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  72:  iload   5
  74:  invokevirtual   #9; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  77:  invokevirtual   #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  80:  astore_1
  81:  iinc    5, 1
  84:  goto    55

可以看到,之前的+的确已经被编译为了StringBuilder对象的append方法。通过这里的字节码可以看到,对于每一个+都将被替换为一个StringBuilder而不是我所想象的只生成一个对象。也就是说,如果有10000个+号就会生成10000个StringBuilder对象。具体参看上面字节码的第84行,此处是执行完一次循环以后,再次跳转到55行去执行。
接着,我们把再写一个使用StringBuilder直接实现的方式,看看有什么不一样。
具体代码为:

public class StringBuilderAppendDemo {
       public static void main(String[] args) {
       // TODO Auto-generated method stub
       String demoString="";
       int execTimes=10000;
       if(args!=null&&args.length>0)
       {
           execTimes=Integer.parseInt(args[0]);
       }
       System.out.println("execTimes="+execTimes);
       long starMs=System.currentTimeMillis();
       StringBuilder strBuilder=new StringBuilder();
       for(int i=0;i<execTimes;i++)
       {
           strBuilder.append(i);
       }
       long endMs=System.currentTimeMillis();
       System.out.println("StringBuilder exec millis="+(endMs-starMs));
    }
}

和上次一样的参数,看看执行时间的差异

C:\>java StringBuilderAppendDemo 100
execTimes=100
StringBuilder exec millis=0
C:\>java StringBuilderAppendDemo 1000
execTimes=1000
StringBuilder exec millis=1
C:\>java StringBuilderAppendDemo 10000
execTimes=10000
StringBuilder exec millis=1
C:\>java StringBuilderAppendDemo 100000
execTimes=100000
StringBuilder exec millis=5

可以看到,这里的执行次数上升以后,执行时间并没有出现大幅度的增加,那我们在看一下编译后的字节码。

51:  lstore_3
 52:  new     #5; //class java/lang/StringBuilder
 55:  dup
 56:  invokespecial   #6; //Method java/lang/StringBuilder."<init>":()V
 59:  astore  5
 61:  iconst_0
 62:  istore  6
 64:  iload   6
 66:  iload_2
 67:  if_icmpge       84
 70:  aload   5
 72:  iload   6
 74:  invokevirtual   #9; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
 77:  pop
 78:  iinc    6, 1
 81:  goto    64

通过字节码可以看到,整个循环拼接过程中,只在56行对StringBuilde对象进行了一次初始化,以后的拼接操作的循环都是从64行开始,然后到81行进行goto 64再次循环。
为了证明我们的推断,我们需要看看虚拟机中是否是这么实现的。
参考代码: http://www.docjar.com/html/api/com/sun/tools/javac/jvm/Gen.java.html
具体的方法,标红的地方就是在语法树处理过程中的一个用来处理字符串拼接“+”号的例子,其他部分进行的处理也类似,我们只保留需要的部分
 综上所述,如果在编写代码的过程中大量使用+进行字符串评价还是会对性能造成比较大的影响,但是使用的个数在1000以下还是可以接受的,大于10000的话,执行时间将可能超过1s,会对性能产生较大影响。如果有大量需要进行字符串拼接的操作,最好还是使用StringBuffer或StringBuilder进行。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 非常感谢您的提问。我了解到您使用的是CSDN上开发的C知道,并且我是一个类似于ChatGPT的AI模型,我们之间没有直接的联系。 关于您的问题,使用引号将空格括起来是一种常见的字符串表示方法,它可以将一个由多个单词组成的字符串表示为一个整体。在某些情况下,这种表示方法可以方便地处理字符串,但并不是所有情况都适用。在for循环中使用引号括起来的字符串可能会引起语法错误,因为编译器无法识别它们。因此,使用for循环时,通常建议不要使用引号来表示空格。 如果您有其他问题,我很乐意尝试回答它们。请随时向我提问! ### 回答2: 在for循环中使用“+”进行字符串拼接耗费大量的时间和资源。这是因为每次拼接字符串时,都会创建一个新的字符串对象,并且将原始字符串和要拼接的部分复制到新的字符串中。由于字符串是不可变的,每次拼接都需要创建一个新的字符串对象,这将导致频繁的内存分配和复制操作,严重影响性能。 相比之下,使用“+”进行字符串拼接的时间复杂度为O(n^2),其中n是要拼接的字符串的长度。在for循环中进行重复的字符串拼接操作时,会导致时间复杂度呈指数级增长,非常低效。 为了避免这种性能问题,推荐使用StringBuilder类或StringBuffer类来进行字符串拼接。这两个类都提供了高效的字符串拼接方法,通过追加字符串的方式来实现拼接,避免了创建新的字符串对象和频繁的复制操作。StringBuilder相比于StringBuffer具有更高的性能,但是不是线程安全的。 综上所述,为了提高性能,避免不必要的内存分配和复制操作,不建议在for循环中使用“+”进行字符串拼接,而是应该使用StringBuilderStringBuffer类来进行字符串拼接。 ### 回答3: 在使用 for 循环时,不建议使用“+”进行字符串拼接的主要原因是字符串是不可变的。每次进行字符串拼接时,都会创建一个新的字符串对象,而且字符串对象无法修改,只能重新创建一个包含新内容的字符串对象。 这样的操作会消耗大量的系统资源和时间。当循环次数较多时,每次拼接字符串都会引发一次创建对象的开销,导致性能下降,程序执行时间变长。 相比之下,在大量字符串拼接的情况下,使用“+”运算符拼接字符串效率较低。而使用StringBuilder类或StringBuffer类来进行字符串拼接,会更高效。这两个类都是基于可变字符序列的,可以直接在原来的字符串上进行修改和添加,避免了创建多个字符串对象的开销。 因此,在循环中如果需要进行字符串拼接操作,建议使用StringBuilder类或StringBuffer类,先创建一个StringBuilder(或StringBuffer)对象,然后在循环中通过调用其append()方法来添加字符串,最后使用toString()方法转换为String类型输出。 总之,不建议在for循环中使用“+”进行字符串拼接,应该使用StringBuilderStringBuffer类来提高程序性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值