热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在Java中连接字符串时是使用+号还是使用StringBuilderStringBuffer加号

字符串是Java程序中最常用的一种数据结构之一。在Java中的String类已经重载的。也就是说,字符串可以直接使用进行连接,如下面代码所示&#

字符串是Java程序中最常用的一种数据结构之一。在Java中的String类已经重载的"+"。也就是说,字符串可以直接使用"+"进行连接,如下面代码所示:


String s = "abc" + "ddd";


但这样做真的好吗?当然,这个问题不能简单地回答yes or no。要根据具体情况来定。在Java中提供了一个StringBuilder类(这个类只在J2SE5及以上版本提供,以前的版本使用StringBuffer类),这个类也可以起到"+"的作用。那么我们应该用哪个呢?


下面让我们先看看如下的代码:




package
 string;


  


  
public
 
class
 TestSimplePlus


  {


      
public
 
static
 
void
 main(String[] args)


      {


          String s = "abc";


          String ss = "ok" + s + "xyz" + 5;


          System.out.println(ss);


      }


  }



上面的代码将会输出正确的结果。从表面上看,对字符串和整型使用"+"号并没有什么区别,但事实真的如此吗?下面让我们来看看这段代码的本质。

我们首先使用反编译工具(如jdk带的javap、或jad)将TestSimplePlus反编译成Java Byte Code,其中的奥秘就一目了然了。在本文将使用jad来反编译,命令如下:

jad -o -a -s d.java TestSimplePlus.class

反编译后的代码如下:




package
 string;




import
 java.io.PrintStream;




public
 
class
 TestSimplePlus


{


    
public
 TestSimplePlus()


    {


    
//
    0    0:aload_0         
    

//
    1    1:invokespecial   #8   
    

//
    2    4:return          


    }




    
public
 
static
 
void
 main(String args[])


    {


      String s = "abc";


    
//
    0    0:ldc1            #16  
    

//
    1    2:astore_1        


      String ss = (
new
 StringBuilder("ok")).append(s).append("xyz").append(5).toString();


    
//
    2    3:new             #18  
    

//
    3    6:dup             
    

//
    4    7:ldc1            #20  
    

//
    5    9:invokespecial   #22  
    

//
    6   12:aload_1         
    

//
    7   13:invokevirtual   #25  
    

//
    8   16:ldc1            #29  
    

//
    9   18:invokevirtual   #25  
    

//
   10   21:iconst_5        
    

//
   11   22:invokevirtual   #31  
    

//
   12   25:invokevirtual   #34  
    

//
   13   28:astore_2        


      System.out.println(ss);


    
//
   14   29:getstatic       #38  
    

//
   15   32:aload_2         
    

//
   16   33:invokevirtual   #44  
    

//
   17   36:return          


    }


}

读者可能看到上面的Java字节码感到迷糊,不过大家不必担心。本文的目的并不是讲解Java Byte Code,因此,并不用了解具体的字节码的含义。

使用jad反编译的好处之一就是可以同时生成字节码和源代码。这样可以进行对照研究。从上面的代码很容易看出,虽然在源程序中使用了"+",但在编译时仍然将"+"转换成StringBuilder。因此,我们可以得出结论,Java中无论使用何种方式进行字符串连接,实际上都使用的是StringBuilder

那么是不是可以根据这个结论推出使用"+"StringBuilder的效果是一样的呢?这个要从两个方面的解释。如果从运行结果来解释,那么"+"StringBuilder是完全等效的。但如果从运行效率和资源消耗方面看,那它们将存在很大的区别。


    当然,如果连接字符串行表达式很简单(如上面的顺序结构),那么"+"StringBuilder基本是一样的,但如果结构比较复杂,如使用循环来连接字符串,那么产生的Java Byte Code就会有很大的区别。先让我们看看如下的代码:




 
package
 string;


  


  
import
 java.util.*;


  


  
public
 
class
 TestComplexPlus


  {


      
public
 
static
 
void
 main(String[] args)


      {


          String s = "";


          Random rand = 
new
 Random();


          
for
 (
int
 i &#61; 0; i < 10; i&#43;&#43;)


          {


              s &#61; s &#43; rand.nextInt(1000) &#43; " ";


          }


          System.out.println(s);


      }


  }




 
    上面的代码返编译后的
Java Byte Code
如下&#xff1a;





package
 string;




import
 java.io.PrintStream;


import
 java.util.Random;




public
 
class
 TestComplexPlus


{




    
public
 TestComplexPlus()


    {


    
//
    0    0:aload_0         
    

//
    1    1:invokespecial   #8   
    

//
    2    4:return          


    }




    
public
 
static
 
void
 main(String args[])


    {


        String s &#61; "";


    
//
    0    0:ldc1            #16  
    

//
    1    2:astore_1        


        Random rand &#61; 
new
 Random();


    
//
    2    3:new             #18  
    

//
    3    6:dup             
    

//
    4    7:invokespecial   #20  
    

//
    5   10:astore_2        


        
for
(
int
 i &#61; 0; i < 10; i&#43;&#43;)


    
//
*   6   11:iconst_0        
    

//
*   7   12:istore_3        
    

//
*   8   13:goto            49


         s &#61; (
new
 StringBuilder(String.valueOf(s))).append(rand.nextInt(1000)).append(" ").toString();


    
//
    9   16:new             #21  
    

//
   10   19:dup             
    

//
   11   20:aload_1         
    

//
   12   21:invokestatic    #23  
    

//
   13   24:invokespecial   #29  
    

//
   14   27:aload_2         
    

//
   15   28:sipush          1000
    

//
   16   31:invokevirtual   #32  
    

//
   17   34:invokevirtual   #36  
    

//
   18   37:ldc1            #40  
    

//
   19   39:invokevirtual   #42  
    

//
   20   42:invokevirtual   #45  
    

//
   21   45:astore_1        

    

//
   22   46:iinc            3  1
    

//
   23   49:iload_3         
    

//
   24   50:bipush          10
    

//
   25   52:icmplt          16


        System.out.println(s);


    
//
   26   55:getstatic       #49  
    

//
   27   58:aload_1         
    

//
   28   59:invokevirtual   #55  
    

//
   29   62:return          


    }


}




    大家可以看到&#xff0c;虽然编译器将
"&#43;"
转换成了
StringBuilder
&#xff0c;但创建
StringBuilder
对象的位置却在
for
语句内部。这就意味着每执行一次循环&#xff0c;就会创建一个
StringBuilder
对象&#xff08;对于本例来说&#xff0c;是创建了
10

StringBuilder
对象&#xff09;&#xff0c;虽然
Java
有垃圾回收器&#xff0c;但这个回收器的工作时间是不定的。如果不断产生这样的垃圾&#xff0c;那么仍然会占用大量的资源。解决这个问题的方法就是在程序中直接使用
StringBuilder
来连接字符串&#xff0c;代码如下&#xff1a;





package
 string;




import
 java.util.*;




public
 
class
 TestStringBuilder


{


    
public
 
static
 
void
 main(String[] args)


    {


        String s &#61; "";


        Random rand &#61; 
new
 Random();


        StringBuilder result &#61; 
new
 StringBuilder();


        
for
 (
int
 i &#61; 0; i < 10; i&#43;&#43;)


        {


            result.append(rand.nextInt(1000));


            result.append(" ");


        }


        System.out.println(result.toString());


    }


}



上面代码反编译后的结果如下&#xff1a;




package
 string;




import
 java.io.PrintStream;


import
 java.util.Random;




public
 
class
 TestStringBuilder


{




    
public
 TestStringBuilder()


    {


    
//
    0    0:aload_0         
    

//
    1    1:invokespecial   #8   
    

//
    2    4:return          


    }




    
public
 
static
 
void
 main(String args[])


    {


        String s &#61; "";


    
//
    0    0:ldc1            #16  
    

//
    1    2:astore_1        


        Random rand &#61; 
new
 Random();


    
//
    2    3:new             #18  
    

//
    3    6:dup             
    

//
    4    7:invokespecial   #20  
    

//
    5   10:astore_2        


        StringBuilder result &#61; 
new
 StringBuilder();


    
//
    6   11:new             #21  
    

//
    7   14:dup             
    

//
    8   15:invokespecial   #23  
    

//
    9   18:astore_3        


        
for
(
int
 i &#61; 0; i < 10; i&#43;&#43;)


    
//
*  10   19:iconst_0        
    

//
*  11   20:istore          4
    

//
*  12   22:goto            47


        {


            result.append(rand.nextInt(1000));


    
//
   13   25:aload_3         
    

//
   14   26:aload_2         
    

//
   15   27:sipush          1000
    

//
   16   30:invokevirtual   #24  
    

//
   17   33:invokevirtual   #28  
    

//
   18   36:pop             


            result.append(" ");


    
//
   19   37:aload_3         
    

//
   20   38:ldc1            #32  
    

//
   21   40:invokevirtual   #34  
    

//
   22   43:pop             


        }




    
//
   23   44:iinc            4  1
    

//
   24   47:iload           4
    

//
   25   49:bipush          10
    

//
   26   51:icmplt          25


        System.out.println(result.toString());


    
//
   27   54:getstatic       #37  
    

//
   28   57:aload_3         
    

//
   29   58:invokevirtual   #43  
    

//
   30   61:invokevirtual   #47  
    

//
   31   64:return          


    }


}



从上面的反编译结果可以看出&#xff0c;创建StringBuilder的代码被放在了for语句外。虽然这样处理在源程序中看起来复杂&#xff0c;但却换来了更高的效率&#xff0c;同时消耗的资源也更少了。

在使用StringBuilder时要注意&#xff0c;尽量不要"&#43;"StringBuilder混着用&#xff0c;否则会创建更多的StringBuilder对象&#xff0c;如下面代码所&#xff1a;


    
for
 (
int
 i &#61; 0; i < 10; i&#43;&#43;)


    {


        result.append(rand.nextInt(1000));


        result.append(" ");


    }

改成如下形式&#xff1a;


for
 (
int
 i &#61; 0; i < 10; i&#43;&#43;)


{


     result.append(rand.nextInt(1000) &#43; " ");


}

则反编译后的结果如下&#xff1a;





 



   
for
(
int
 i &#61; 0; i < 10; i&#43;&#43;)


  
//
*  10   19:iconst_0        
  

//
*  11   20:istore          4
  

//
*  12   22:goto            65


   {


    result.append((
new
 StringBuilder(String.valueOf(rand.nextInt(1000)))).append(" ").toString());


  
//
   13   25:aload_3         
  

//
   14   26:new             #21  
  

//
   15   29:dup             


  

 




从上面的代码可以看出&#xff0c;Java编译器将"&#43;"编译成了StringBuilder&#xff0c;这样for语句每循环一次&#xff0c;又创建了一个StringBuilder对象。
    如果将上面的代码在JDK1.4下编译&#xff0c;必须将StringBuilder改为StringBuffer&#xff0c;而JDK1.4"&#43;"转换为StringBuffer&#xff08;因为JDK1.4并没有提供StringBuilder类&#xff09;。StringBufferStringBuilder的功能基本一样&#xff0c;只是StringBuffer是线程安全的&#xff0c;而StringBuilder不是线程安全的。因此&#xff0c;StringBuilder的效率会更高。


推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • (三)多表代码生成的实现方法
    本文介绍了一种实现多表代码生成的方法,使用了java代码和org.jeecg框架中的相关类和接口。通过设置主表配置,可以生成父子表的数据模型。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 面向对象之3:封装的总结及实现方法
    本文总结了面向对象中封装的概念和好处,以及在Java中如何实现封装。封装是将过程和数据用一个外壳隐藏起来,只能通过提供的接口进行访问。适当的封装可以提高程序的理解性和维护性,增强程序的安全性。在Java中,封装可以通过将属性私有化并使用权限修饰符来实现,同时可以通过方法来访问属性并加入限制条件。 ... [详细]
author-avatar
罢脑货_246
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有