Java中的StringBuilder类功能详解

来源:互联网 发布:c语言获取无范围随机数 编辑:程序博客网 时间:2024/05/28 11:29

字符串连接可以通过两种方法实现,其中一种是在Java中提供的一个StringBuilder类(这个类只在J2SE5及以上版本提供,以前的版本使用StringBuffer类)。

AD: 2013云计算架构师峰会精彩课程曝光


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

    1. String s = "abc" + "ddd";

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

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

    1. package string;
    2. public class TestSimplePlus
    3. {
    4. public static void main(String[] args)
    5. {
    6. String s = "abc";
    7. String ss = "ok" + s + "xyz" + 5;
    8. System.out.println(ss);
    9. }
    10. }

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

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

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

    反编译后的代码如下:

    1. package string;
    2. import java.io.PrintStream;
    3. public class TestSimplePlus
    4. {
    5. public TestSimplePlus()
    6. {
    7. // 0 0:aload_0
    8. // 1 1:invokespecial #8 < Method void Object()>
    9. // 2 4:return
    10. }
    11. public static void main(String args[])
    12. {
    13. String s = "abc";
    14. // 0 0:ldc1 #16 < String "abc">
    15. // 1 2:astore_1
    16. String ss = (new StringBuilder("ok")).append(s).append("xyz").append(5).toString();
    17. // 2 3:new #18 < Class StringBuilder>
    18. // 3 6:dup
    19. // 4 7:ldc1 #20 < String "ok">
    20. // 5 9:invokespecial #22 < Method void StringBuilder(String)>
    21. // 6 12:aload_1
    22. // 7 13:invokevirtual #25 < Method StringBuilder StringBuilder.append(String)>
    23. // 8 16:ldc1 #29 < String "xyz">
    24. // 9 18:invokevirtual #25 < Method StringBuilder StringBuilder.append(String)>
    25. // 10 21:iconst_5
    26. // 11 22:invokevirtual #31 < Method StringBuilder StringBuilder.append(int)>
    27. // 12 25:invokevirtual #34 < Method String StringBuilder.toString()>
    28. // 13 28:astore_2
    29. System.out.println(ss);
    30. // 14 29:getstatic #38 < Field PrintStream System.out>
    31. // 15 32:aload_2
    32. // 16 33:invokevirtual #44 < Method void PrintStream.println(String)>
    33. // 17 36:return
    34. }
    35. }

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

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

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

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

    1. package string;
    2. import java.util.*;
    3. public class TestComplexPlus
    4. {
    5. public static void main(String[] args)
    6. {
    7. String s = "";
    8. Random rand = new Random();
    9. for (int i = 0; i < 10; i++)
    10. {
    11. s = s + rand.nextInt(1000) + " ";
    12. }
    13. System.out.println(s);
    14. }
    15. }

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

    1. package string;
    2. import java.io.PrintStream;
    3. import java.util.Random;
    4. public class TestComplexPlus
    5. {
    6. public TestComplexPlus()
    7. {
    8. // 0 0:aload_0
    9. // 1 1:invokespecial #8 < Method void Object()>
    10. // 2 4:return
    11. }
    12. public static void main(String args[])
    13. {
    14. String s = "";
    15. // 0 0:ldc1 #16 < String "">
    16. // 1 2:astore_1
    17. Random rand = new Random();
    18. // 2 3:new #18 < Class Random>
    19. // 3 6:dup
    20. // 4 7:invokespecial #20 < Method void Random()>
    21. // 5 10:astore_2
    22. for(int i = 0; i < 10; i++)
    23. //* 6 11:iconst_0
    24. //* 7 12:istore_3
    25. //* 8 13:goto 49
    26. s = (new StringBuilder(String.valueOf(s))).append(rand.nextInt(1000)).append(" ").toString();
    27. // 9 16:new #21 < Class StringBuilder>
    28. // 10 19:dup
    29. // 11 20:aload_1
    30. // 12 21:invokestatic #23 < Method String String.valueOf(Object)>
    31. // 13 24:invokespecial #29 < Method void StringBuilder(String)>
    32. // 14 27:aload_2
    33. // 15 28:sipush 1000
    34. // 16 31:invokevirtual #32 < Method int Random.nextInt(int)>
    35. // 17 34:invokevirtual #36 < Method StringBuilder StringBuilder.append(int)>
    36. // 18 37:ldc1 #40 < String " ">
    37. // 19 39:invokevirtual #42 < Method StringBuilder StringBuilder.append(String)>
    38. // 20 42:invokevirtual #45 < Method String StringBuilder.toString()>
    39. // 21 45:astore_1
    40. // 22 46:iinc 3 1
    41. // 23 49:iload_3
    42. // 24 50:bipush 10
    43. // 25 52:icmplt 16
    44. System.out.println(s);
    45. // 26 55:getstatic #49 < Field PrintStream System.out>
    46. // 27 58:aload_1
    47. // 28 59:invokevirtual #55 < Method void PrintStream.println(String)>
    48. // 29 62:return
    49. }
    50. }

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

    1. package string;
    2. import java.util.*;
    3. public class TestStringBuilder
    4. {
    5. public static void main(String[] args)
    6. {
    7. String s = "";
    8. Random rand = new Random();
    9. StringBuilder result = new StringBuilder();
    10. for (int i = 0; i < 10; i++)
    11. {
    12. result.append(rand.nextInt(1000));
    13. result.append(" ");
    14. }
    15. System.out.println(result.toString());
    16. }
    17. }

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

    1. package string;
    2. import java.io.PrintStream;
    3. import java.util.Random;
    4. public class TestStringBuilder
    5. {
    6. public TestStringBuilder()
    7. {
    8. // 0 0:aload_0
    9. // 1 1:invokespecial #8 < Method void Object()>
    10. // 2 4:return
    11. }
    12. public static void main(String args[])
    13. {
    14. String s = "";
    15. // 0 0:ldc1 #16 < String "">
    16. // 1 2:astore_1
    17. Random rand = new Random();
    18. // 2 3:new #18 < Class Random>
    19. // 3 6:dup
    20. // 4 7:invokespecial #20 < Method void Random()>
    21. // 5 10:astore_2
    22. StringBuilder result = new StringBuilder();
    23. // 6 11:new #21 < Class StringBuilder>
    24. // 7 14:dup
    25. // 8 15:invokespecial #23 < Method void StringBuilder()>
    26. // 9 18:astore_3
    27. for(int i = 0; i < 10; i++)
    28. //* 10 19:iconst_0
    29. //* 11 20:istore 4
    30. //* 12 22:goto 47
    31. {
    32. result.append(rand.nextInt(1000));
    33. // 13 25:aload_3
    34. // 14 26:aload_2
    35. // 15 27:sipush 1000
    36. // 16 30:invokevirtual #24 < Method int Random.nextInt(int)>
    37. // 17 33:invokevirtual #28 < Method StringBuilder StringBuilder.append(int)>
    38. // 18 36:pop
    39. result.append(" ");
    40. // 19 37:aload_3
    41. // 20 38:ldc1 #32 < String " ">
    42. // 21 40:invokevirtual #34 < Method StringBuilder StringBuilder.append(String)>
    43. // 22 43:pop
    44. }
    45. // 23 44:iinc 4 1
    46. // 24 47:iload 4
    47. // 25 49:bipush 10
    48. // 26 51:icmplt 25
    49. System.out.println(result.toString());
    50. // 27 54:getstatic #37 < Field PrintStream System.out>
    51. // 28 57:aload_3
    52. // 29 58:invokevirtual #43 < Method String StringBuilder.toString()>
    53. // 30 61:invokevirtual #47 < Method void PrintStream.println(String)>
    54. // 31 64:return
    55. }
    56. }

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

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

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

    改成如下形式:

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

    则反编译后的结果如下:

    1. for(int i = 0; i < 10; i++)
    2. //* 10 19:iconst_0
    3. //* 11 20:istore 4
    4. //* 12 22:goto 65
    5. {
    6. result.append((new StringBuilder(String.valueOf(rand.nextInt(1000)))).append(" ").toString());
    7. // 13 25:aload_3
    8. // 14 26:new #21 < Class StringBuilder>
    9. // 15 29:dup

    从上面的代码可以看出,Java编译器将"+"编译成了StringBuilder类,这样for语句每循环一次,又创建了一个StringBuilder对象。

    如果将上面的代码在JDK1.4下编译,必须将StringBuilder改为StringBuffer,而JDK1.4将"+"转换为StringBuffer(因为JDK1.4并没有提供StringBuilder类)。StringBuffer和StringBuilder的功能基本一样,只是StringBuffer是线程安全的,而StringBuilder不是线程安全的。因此,StringBuilder的效率会更高。

     

    原创粉丝点击
    热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 京东售后不退款怎么办 冲了话费不到账怎么办 币安维护充值怎么办 微信话费未到账怎么办 微信话费交错了怎么办 北京移动查话费余额怎么办 淘宝卖家客服无法联系怎么办? 微信支付月限额怎么办 微信超额20万怎么办 微信支付超额了怎么办 微信零钱超额了怎么办 微信的充值冲错了怎么办 有流量还扣话费怎么办 自动取款机充值到电子账户怎么办 淘宝qb充错了怎么办 q币冲错了人家不给怎么办 qq充值话费错号怎么办 qq充错号码了怎么办 qq交话费不到账怎么办 充错手机号码而且是空号怎么办 微信钱包充错话费怎么办 QQ充值话费充到空号了怎么办 给别人充错话费怎么办 用qq交错话费对方是空号怎么办 号码变成空号了怎么办 qq冲流量冲错了怎么办 流量冲错了套餐怎么办 微信流量充错号码怎么办 微信支付不进账怎么办 充话费充不进去怎么办 用支付宝充话费没到账怎么办 支付宝充话费未到账怎么办 话费充了不到账怎么办 转转买家不确认收货怎么办 充话费错了怎么办啊 淘宝充值流量没到账怎么办 微信手机充错了怎么办 支付宝充话费没到账怎么办 裤子摔了一个洞怎么办 顾客反应衣服质量不好怎么办 淘宝买的衣服味道很大怎么办