随笔 - 312, 文章 - 14, 评论 - 1393, 引用 - 0

导航

<2008年5月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

公告

关注我的新浪微博

我的著作









常用链接

留言簿(126)

我参与的团队

随笔分类(818)

随笔档案(310)

文章分类(1)

文章档案(8)

相册

ADSL、3G查询

CSDN

eclipse

ibm

Java EE

Linux

Web

云服务

代理网站

关注的网站

协议

喜欢的Blog

国内广告平台

图书出版

在线培训

开发工具

微博客户端

手机铃声

操作系统

  • ReactOS
  • 一个与windowXP/2003兼容的操作系统

数学

文件格式

源码资源

移动(Mobile)

编程语言

英语学习

最新随笔

搜索

  •  

积分与排名

  • 积分 - 1969740
  • 排名 - 6

最新评论

阅读排行榜

评论排行榜

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

本文为原创,如需转载,请注明作者和出处,谢谢!

   
字符串是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   <Method void Object()>
    
//    2    4:return          
    }

    
public static void main(String args[])
    {
      String s 
= "abc";
    
//    0    0:ldc1            #16  <String "abc">
    
//    1    2:astore_1        
      String ss = (new StringBuilder("ok")).append(s).append("xyz").append(5).toString();
    
//    2    3:new             #18  <Class StringBuilder>
    
//    3    6:dup             
    
//    4    7:ldc1            #20  <String "ok">
    
//    5    9:invokespecial   #22  <Method void StringBuilder(String)>
    
//    6   12:aload_1         
    
//    7   13:invokevirtual   #25  <Method StringBuilder StringBuilder.append(String)>
    
//    8   16:ldc1            #29  <String "xyz">
    
//    9   18:invokevirtual   #25  <Method StringBuilder StringBuilder.append(String)>
    
//   10   21:iconst_5        
    
//   11   22:invokevirtual   #31  <Method StringBuilder StringBuilder.append(int)>
    
//   12   25:invokevirtual   #34  <Method String StringBuilder.toString()>
    
//   13   28:astore_2        
      System.out.println(ss);
    
//   14   29:getstatic       #38  <Field PrintStream System.out>
    
//   15   32:aload_2         
    
//   16   33:invokevirtual   #44  <Method void PrintStream.println(String)>
    
//   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 = 0; i < 10; i++)
          {
              s 
= s + rand.nextInt(1000+ " ";
          }
          System.out.println(s);
      }
  }

     上面的代码返编译后的Java Byte Code如下:


package string;

import java.io.PrintStream;
import java.util.Random;

public class TestComplexPlus
{

    
public TestComplexPlus()
    {
    
//    0    0:aload_0         
    
//    1    1:invokespecial   #8   <Method void Object()>
    
//    2    4:return          
    }

    
public static void main(String args[])
    {
        String s 
= "";
    
//    0    0:ldc1            #16  <String "">
    
//    1    2:astore_1        
        Random rand = new Random();
    
//    2    3:new             #18  <Class Random>
    
//    3    6:dup             
    
//    4    7:invokespecial   #20  <Method void Random()>
    
//    5   10:astore_2        
        for(int i = 0; i < 10; i++)
    
//*   6   11:iconst_0        
    
//*   7   12:istore_3        
    
//*   8   13:goto            49
         s = (new StringBuilder(String.valueOf(s))).append(rand.nextInt(1000)).append(" ").toString();
    
//    9   16:new             #21  <Class StringBuilder>
    
//   10   19:dup             
    
//   11   20:aload_1         
    
//   12   21:invokestatic    #23  <Method String String.valueOf(Object)>
    
//   13   24:invokespecial   #29  <Method void StringBuilder(String)>
    
//   14   27:aload_2         
    
//   15   28:sipush          1000
    
//   16   31:invokevirtual   #32  <Method int Random.nextInt(int)>
    
//   17   34:invokevirtual   #36  <Method StringBuilder StringBuilder.append(int)>
    
//   18   37:ldc1            #40  <String " ">
    
//   19   39:invokevirtual   #42  <Method StringBuilder StringBuilder.append(String)>
    
//   20   42:invokevirtual   #45  <Method String StringBuilder.toString()>
    
//   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  <Field PrintStream System.out>
    
//   27   58:aload_1         
    
//   28   59:invokevirtual   #55  <Method void PrintStream.println(String)>
    
//   29   62:return          
    }
}

    大家可以看到,虽然编译器将"+"转换成了StringBuilder,但创建StringBuilder对象的位置却在for语句内部。这就意味着每执行一次循环,就会创建一个StringBuilder对象(对于本例来说,是创建了10StringBuilder对象),虽然Java有垃圾回收器,但这个回收器的工作时间是不定的。如果不断产生这样的垃圾,那么仍然会占用大量的资源。解决这个问题的方法就是在程序中直接使用StringBuilder来连接字符串,代码如下:

package string;

import java.util.*;

public class TestStringBuilder
{
    
public static void main(String[] args)
    {
        String s 
= "";
        Random rand 
= new Random();
        StringBuilder result 
= new StringBuilder();
        
for (int i = 0; i < 10; i++)
        {
            result.append(rand.nextInt(
1000));
            result.append(
" ");
        }
        System.out.println(result.toString());
    }
}

上面代码反编译后的结果如下:


package string;

import java.io.PrintStream;
import java.util.Random;

public class TestStringBuilder
{

    
public TestStringBuilder()
    {
    
//    0    0:aload_0         
    
//    1    1:invokespecial   #8   <Method void Object()>
    
//    2    4:return          
    }

    
public static void main(String args[])
    {
        String s 
= "";
    
//    0    0:ldc1            #16  <String "">
    
//    1    2:astore_1        
        Random rand = new Random();
    
//    2    3:new             #18  <Class Random>
    
//    3    6:dup             
    
//    4    7:invokespecial   #20  <Method void Random()>
    
//    5   10:astore_2        
        StringBuilder result = new StringBuilder();
    
//    6   11:new             #21  <Class StringBuilder>
    
//    7   14:dup             
    
//    8   15:invokespecial   #23  <Method void StringBuilder()>
    
//    9   18:astore_3        
        for(int i = 0; i < 10; i++)
    
//*  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  <Method int Random.nextInt(int)>
    
//   17   33:invokevirtual   #28  <Method StringBuilder StringBuilder.append(int)>
    
//   18   36:pop             
            result.append(" ");
    
//   19   37:aload_3         
    
//   20   38:ldc1            #32  <String " ">
    
//   21   40:invokevirtual   #34  <Method StringBuilder StringBuilder.append(String)>
    
//   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  <Field PrintStream System.out>
    
//   28   57:aload_3         
    
//   29   58:invokevirtual   #43  <Method String StringBuilder.toString()>
    
//   30   61:invokevirtual   #47  <Method void PrintStream.println(String)>
    
//   31   64:return          
    }
}

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

在使用StringBuilder时要注意,尽量不要"+"StringBuilder混着用,否则会创建更多的StringBuilder对象,如下面代码所:

    for (int i = 0; i < 10; i++)
    {
        result.append(rand.nextInt(
1000));
        result.append(
" ");
    }

改成如下形式:

for (int i = 0; i < 10; i++)
{
     result.append(rand.nextInt(
1000+ " ");
}

则反编译后的结果如下:


 
   
for(int i = 0; i < 10; i++)
  
//*  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  <Class StringBuilder>
  
//   15   29:dup             
   

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








Android开发完全讲义(第2版)(本书版权已输出到台湾)

http://product.dangdang.com/product.aspx?product_id=22741502



Android高薪之路:Android程序员面试宝典 http://book.360buy.com/10970314.html


新浪微博:http://t.sina.com.cn/androidguy   昵称:李宁_Lining

posted on 2008-05-07 16:04 银河使者 阅读(25487) 评论(15)  编辑  收藏 所属分类: java 原创

评论

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

good
2008-05-07 17:44 | 云淡风清

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder[未登录]  回复  更多评论   

分析的真仔細!受益了!
2008-05-08 08:38 | jezz

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

对于 String s = "abc";
String ss = "ok" + s + "xyz" + 5;
System.out.println(ss);
这种“字符串常量+字符串引用+字符串常量”的形式,楼主的结论“在Java中无论使用何种方式进行字符串连接,实际上都使用的是StringBuilder。”可以成立,但此结论具有片面性。
如果换成这种情况:
String ss = "ok" + "xyz" + 5;
System.out.println(ss);

下面是它的JVM指令:
public class com.zte.TestJP extends java.lang.Object{
public com.zte.TestJP();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return

public static void main(java.lang.String[]);
Code:
0: ldc #16; //String okxyz5
2: astore_1
3: getstatic #18; //Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
10: return

}
这里并没有产生StringBulider对象,因为JVM编译器对于"ok" + s + "xyz" + 5;的处理方式和对于"ok" + "xyz" + 5;不一样,后者在编译期的值就可以确定下来,因为都是字符串常量。故在程序运行期不会产生StringBulider对象,而前者不同,s的值在编译期无法确定,所以JVM就会在运行期产生StringBulider对象来进行append。

2008-05-08 10:12 | cheng

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

变量和常量都是压栈,这在bytecode中没有区别,就象汇编语言,根本就没有变量和常量之分。

如下面的代码

String s = "dd";
String ss = "ok" + s + "xyz" + 5;
System.out.println(ss);

对应的bytecode是

String s = "dd";
// 0 0:ldc1 #32 <String "dd">
// 1 2:astore_1
String ss = (new StringBuilder("ok")).append(s).append("xyz").append(5).toString();
// 2 3:new #16 <Class StringBuilder>
// 3 6:dup
// 4 7:ldc1 #34 <String "ok">
// 5 9:invokespecial #20 <Method void StringBuilder(String)>
// 6 12:aload_1
// 7 13:invokevirtual #25 <Method StringBuilder StringBuilder.append(String)>
// 8 16:ldc1 #36 <String "xyz">
// 9 18:invokevirtual #25 <Method StringBuilder StringBuilder.append(String)>
// 10 21:iconst_5
// 11 22:invokevirtual #38 <Method StringBuilder StringBuilder.append(int)>
// 12 25:invokevirtual #29 <Method String StringBuilder.toString()>
// 13 28:astore_2
System.out.println(ss);
// 14 29:getstatic #41 <Field PrintStream System.out>
// 15 32:aload_2
// 16 33:invokevirtual #47 <Method void PrintStream.println(String)>
// 17 36:return


仍然使用了StringBuilder.append(s)
2008-05-08 10:22 | 银河使者

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

仍然使用了StringBuilder.append(s) 不是因为“变量和常量都是压栈,这在bytecode中没有区别,就象汇编语言,根本就没有变量和常量之分”。

只所以会使用append,是因为其中有字符串引用的存在。例如:对于new操作而言,其引用和对象的值是在运行期来动态分配堆空间和栈空间的。

String ss = "ok" + s + "xyz" + 5; 这其中"ok" ,"xyz"和5的值在JVM编译器j将java源文件编译为class文件时就已经确定下来了,但s在编译期间是无法确定的,只能在class文件装载到虚拟机,在准备阶段来动态获得其所指对象的值,即"dd"。





2008-05-08 11:13 | cheng

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

@银河使者
好像没有明白cheng的意思

String ss = "ok" + "xyz" + 5;
System.out.println(ss);

这种情况,是不需要new StringBuilder.
2008-05-08 17:12 | east

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

如果连接字符串不在循环中,当然不需要new StringBuilder了。我的意思是说尽量让jvm少建立StringBuilder对象。
2008-05-08 18:24 | 银河使者

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

请问结果是什么?说了半天都没说
2008-05-28 15:43 | 懒人

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

要什么结果。 就是在循环里连接字符串时,最好要在循环外建立一个StringBuilder对象,然后在循环中使用StringBuilder.append方法连接,而不用直接使用“+”进行连接字符串
2008-05-28 15:53 | 银河使者

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

@银河使者
2008-05-29 11:07 | 懒人

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

LZ你的分析看上去没问题,但是忽略了一个Copy-On-Write-Semantic的原理。为什么要用StringBuilder,相信是为了性能上的考虑。既然要提高性能,Copy-On-Write是必不可缺的。cheng说的例子,正好就是一个编译器进行优化的特例。LZ,编译码可以帮助分析,但是别光测试一些情况,就轻易下结论。
2008-05-29 18:09 | stanley_xu

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

一个题外话,LZ你贴了很多文章(标题都蛮吸引),但你有没有发现,往往你转载的文章评论很少,你标出原创的文章却评论一堆呢?
2008-05-29 18:13 | stanley_xu

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

还是原创的比贴的文章多。贴的都是转载的文章,很多都是非计算机的,总不能给它们也加上原创吧。^-^。只是感觉有意思。哈哈,至于评论吗?多少也无所谓。只是做个备份。

编译码是可以帮助分析,但是有时编译码是很愚蠢的。就象编译器虽然可以优化代码,但是使用编译器生成的汇编代码永远不可能和手工编写的代码相比。

还有就是为什么有的基于java的软件,如某些桌面软件,office等,的速度或性能不如同类的软件,虽然它们实现的功能类似,但是如果不注意这些细节的地方。就会量变引起质变。从而将自己的程序拖跨。学习一门语言很容易,但要是充分了解这门语言,也许要花很多精力和时间。
2008-05-29 18:53 | 银河使者

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

还有在循环里尽量不要使用“+”连接字符串,而要在循环外使用StringBuilder,并且在循环里使用append来连接字符串的结论,并不是我首先提出来的,而是由某位大师提出的。我只是在这里分析了一下这个结论,并验证它的正确性。

2008-05-29 18:57 | 银河使者

# re: 在Java中连接字符串时是使用+号还是使用StringBuilder  回复  更多评论   

请问,使用jad反编译*.class时,发现有些字符串不能正常显示。例如:
static
{
String as[] = new String[7];
// 0 0:bipush 7
// 1 2:anewarray String[]
// 2 5:dup
// 3 6:iconst_0
as;
as;
// 4 7:ldc1 #67 <String "P{\f||Es\033rzFi\032a">
0;
"P{\f||Es\033rzFi\032a";
6;
// 5 9:bipush 6
goto _L1
//* 6 11:goto 86
_L14:
PLUGIN_ID;
// 7 14:putstatic #454 <Field String PLUGIN_ID>
"p{\f||es\033rzFI\032aO^o\030|q\034~\032f|@s\017av]t";
// 8 17:ldc1 #77 <String "p{\f||es\033rzFI\032aO^o\030|q\034~\032f|@s\017av]t">
-1;
// 9 19:bipush -1
goto _L1
//* 10 21:goto 86
_L7:

请问怎么能正确反编译吗?
2008-12-11 19:02 | xu_cq

只有注册用户登录后才能发表评论。


网站导航: