【后端面经-Java】String与StringBuffer与StringBuilder的比较

打印 上一主题 下一主题

主题 945|帖子 945|积分 2839

目录

1. String


  • 不可变
    查看String源码如下:
    1. public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
    2.     /** The value is used for character storage. */
    3.     private final char value[];
    4.     /** The offset is the first index of the storage that is used. */
    5.     private final int offset;
    6.     /** The count is the number of characters in the String. */
    7.     private final int count;
    8.     /** Cache the hash code for the string */
    9.     private int hash; // Default to 0
    10.     /** use serialVersionUID from JDK 1.0.2 for interoperability */
    11.     private static final long serialVersionUID = -6849794470754667710L;
    12.    
    13.     ......
    14. }
    复制代码
    由源码可知,String中存储数据的数组被关键字final修饰,因此是不可变的
  • 运算和操作

    • 创建对象
      创建对象有两种方式:
      1. //方式一
      2.   String str = "abc";
      3. //方式二
      4.   String str = new String("abc");
      复制代码
      两种方式都会在栈中创建一个字符串变量str,但它们的内存分配方式是不同的。
      我们可以通过如下代码直观看出两种方式的不同
      1.   String str1 = "abc";
      2.   String str2 = "abc";
      3.   String str3 = new String("abc");
      4.   String str4 = new String("abc");
      5.   System.out.println(str1 == str2); //true
      6.   System.out.println(str3 == str4); //false
      复制代码
      为了理解这部分内容,最好先了解一下Java中的内存分配机制,可参考此篇博客:【后端面经-Java】JVM内存分区详解
      总之,简单来说,内存主要分为栈、堆、方法区等部分,栈中存放局部变量,堆中存放对象实例和数组,方法区中存放类信息和常量等,常量池一开始均在方法区中,后来运行时常量池转移到堆中,下文均按照这种内存分配模型来讨论。
      下图展示了两种创建方式下的内存情况:


      • 方式一
      • 在栈中创建一个变量之后,需要指向具体的值,首先会在常量池中查找abc,如果找到,则指向这个字符串,如果没有找到,在运行时常量池中创建这一字符串,然后指向它。
      • 因此,str1和str2指向的是同一个字符串,即同一个内存单元,所以str1 == str2为true
      • 方式二

        • 在栈中创建一个变量之后,在堆中构造一个新的字符串对象,然后指向它。
        • 因此,str3和str4指向的是两个不同的内存单元,所以str3 == str4为false


    • "+"运算

      • 每次"+"运算虽然看似很简便,实际上需要创建一个新的String对象来接收结果,而作为运算数的String对象依然存在于堆中,成为垃圾占用堆空间,需要Java垃圾回收机制进行处理。(关于Java垃圾回收机制,可参考此篇博文:【后端面经-Java】JVM垃圾回收机制
      • 这种操作是非常低效的,且造成了大量的内存占用,因此在实际开发中,应尽量避免使用"+"运算符来进行字符串拼接,而应该使用StringBuffer或StringBuilder来进行字符串拼接。

    • substring() && replace() && concat()

      • 这些操作的一个特点就是:创建新的String对象承接结果,而原来的String对象依然存在于堆中。


  • 适用场景

    • 适用于字符串不需修改的场景。

2. StringBuffer


  • 可变

    • StringBuffer源代码中数组是可变长度的。

  • 线程安全

    • 在类定义过程中,适用synchronized关键字,保证线程安全。
    • 线程安全与否是StringBuffer和StringBuilder的重要区别之一。

  • 运算和操作

    • append():在字符串末尾添加新字符串;
    • insert():在指定位置插入新字符串;
    • toString():将StringBuffer转换为String;

  • 适用场景

    • 多线程,字符串需要频繁修改

3. StringBuilder


  • 可变

    • 和StringBuffer一样,StringBuilder源代码中数组是可变长度的。

  • 线程不安全

    • 并没有使用synchronized关键字,因此线程不安全。
    • 因为线程不安全,不需要考虑线程安全的处理,所以StringBuilder的性能比StringBuffer略高。

  • 适用场景:

    • 单线程,字符串需要频繁修改

4. 性能提升


  • 为了提升性能,避免在字符串需要修改的场景下使用String类;
  • 在初始定义时预先估计字符串的长度,对StringBuilder和StringBuffer进行初始化,避免频繁扩容,提升性能。
    1. StringBuilder sb = new StringBuilder(100);
    2. StringBuffer sb = new StringBuffer(100);
    3. ``
    复制代码
5. 总结和比较

下图是对三者进行的比较:

面试模拟

Q:简单介绍一下String和StringBuilder的区别
A:首先,String定义的字符串是不可变的,使用拼接函数或者操作符将会创建一个新的String对象,性能不高;而StringBuilder定义的字符串可变,且线程不安全使得其具有较好的性能,在字符串需要频繁修改的场景下,使用StringBuilder会更好。
参考资料


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表