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

StringVSStringBuilderVSStringBufferInJava

nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd

简单说说 String

String 是不可变的,一旦定义了,就不能再去修改字符串的内容。

先看下面两行代码:

String a = "Hello";
a = a + " world"

通常情况下很容易误解为修改了字符串对象的内容。其实不然,真实的操作则是

  1. "Hello" 是一个字符串对象,被赋给了引用 a;
  2. " world" 也是一个字符串对象,和 "Hello" 拼接生成一个新的字符串对象又被赋给了 a;

并不是 "Hello" 改变了,而是指向 "Hello" 的引用 a重新指向了新对象。

StringBuilder

StringBuilder 在很大程度上类似 ArrayList:

StringBulder ArrayList

维护了一个 char 数组

(其实这个数组属于它的父类 AbstractStringBuilder)

维护了一个 Object 数组
append 方法向后面增加新元素 add(E e) 方法向后面增加新元素
insert 方法向中间某位置插入新元素 add(int index, E e) 向某位置增加新元素
deleteCharAt(int index) 删除某位置的元素 remove(int index) 删除某位置的元素
添加元素时候空间不够会动态扩容
就罗列这么多吧~

很明显如果需要连续拼接很多字符串的话 StringBuilder 比 String 更加方便。而且在性能方面也有考究这点我们稍后再说。

StringBuffer

StringBuffer 基本上和 StringBuilder 完全一样了。明显的不同就是 StringBuffer 是线程安全的,除了构造方法之外的所有方法都用了 synchronized 修饰。

相对来说安全一些,但是性能上要比 StringBuilder 差一些了。

字符串拼接探索

先看一段代码:

public class Test {
    public static void main(String[] args) {
        String aa = "33";
        aa = aa + 3 + 'x' + true + "2";
        aa = aa + 8;
        String bb = aa + "tt";
        System.out.println(bb);
    }
}

使用编译工具编译

Javac Test.java

同级目录会生成 Test.class,我们再对 Test.class 进行反汇编

javap Test.class

得到下面的代码:

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String 33
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."":()V
      10: aload_1
      11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      14: iconst_3
      15: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      18: bipush        120
      20: invokevirtual #7                  // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
      23: iconst_1
      24: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
      27: ldc           #9                  // String 2
      29: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      32: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      35: astore_1
      36: new           #3                  // class java/lang/StringBuilder
      39: dup
      40: invokespecial #4                  // Method java/lang/StringBuilder."":()V
      43: aload_1
      44: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      47: bipush        8
      49: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      52: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      55: astore_1
      56: new           #3                  // class java/lang/StringBuilder
      59: dup
      60: invokespecial #4                  // Method java/lang/StringBuilder."":()V
      63: aload_1
      64: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      67: ldc           #11                 // String tt
      69: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      72: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      75: astore_2
      76: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
      79: aload_2
      80: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      83: return
} 
  1. 在第一次拼接的时候(连续加号),先创建了一个 StringBuilder 对象,然后 append 当前的字符串对象 aa,接着连续 append 将要拼接的元素,最后 toString() 返回拼接后的字符串对象赋给 aa;
  2. 第二次拼接,同样是创建一个 StringBuilder 对象,然后 append 当前的字符串对象 aa,接着 append 8,最后 toString() 返回拼接后的字符串对象赋给 aa;
  3. 第三次拼接,同样是创建一个 StringBuilder 对象,然后 append 当前的字符串对象 aa,接着 append "tt",最后 toString() 返回拼接后的字符串对象赋给 bb;

我们把每出现一次 “=” 算成一次拼接,那么每次拼接都会创建一个 StringBuilder 对象。

当遇到大规模的场景中,比如循环次数很多,就像下面的例子:

public class Test1 {
    public static void main(String[] args) {
        Test1 test = new Test1();
        System.out.println(test.testString());
        System.out.println(test.testStringBuilder());
    }
    
    public long testString(){
        String a = "";
        long start = Calendar.getInstance().getTimeInMillis();
        for(int i=0;i<100000;i++){
            a += i;
        }
        long end = Calendar.getInstance().getTimeInMillis();
        return end-start;
    }
    
    public long testStringBuilder(){
        StringBuilder a = new StringBuilder();
        long start = Calendar.getInstance().getTimeInMillis();
        for(int i=0;i<100000;i++){
            a.append(i);
        }
        long end = Calendar.getInstance().getTimeInMillis();
        return end-start;
    }
}

输出:

22243
16

耗时比较,前者呈指数级增长,而后者是线性增长。性能上相差甚远。

甚至如果我们已经知道了容量,还可以继续优化,一次性分配一个 StringBuilder,避免扩容时候的开销。参考下面例子。

public class Test2 {
    public static void main(String[] args) {
        Test2 test = new Test2();
        for(int i=0;i<5;i++){
            System.out.println(test.testStringBuilder() + "---" + test.testStringBuilder2());
        }
    }
    
    public long testStringBuilder(){
        StringBuilder a = new StringBuilder();
        long start = Calendar.getInstance().getTimeInMillis();
        for(int i=0;i<10000000;i++){
            a.append(1);
        }
        long end = Calendar.getInstance().getTimeInMillis();
        return end-start;
    }
    
    public long testStringBuilder2(){
        StringBuilder a = new StringBuilder(10000000);
        long start = Calendar.getInstance().getTimeInMillis();
        for(int i=0;i<10000000;i++){
            a.append(1);
        }
        long end = Calendar.getInstance().getTimeInMillis();
        return end-start;
    }
}

输出:

78---16
62---31
47---15
63---31
47---31

提前分配,耗时更短~

原则很简单:不要使用字符串连接操作符来合并多个字符串,除非性能无关紧要。应该使用 StringBuilder 的 append 方法。


推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
author-avatar
手机用户2702937647
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有