面试官:请聊一聊String、StringBuilder、StringBuffer三者的区别

简介: 面试官:请聊一聊String、StringBuilder、StringBuffer三者的区别

面试官:“小伙子,在日常的写代码过程中,使用过String,StringBuilder和StringBuffer没?”
我:“用过的呀!”
面试官:“那你就来聊一聊,他们之间有什么区别,不同场景下如何选择吧”
我:“好嘞!”
在Java的开发过程中,使用频率最高的就是String字符串,但由于在字符串存储和拼接的过程中,涉及到很多场景需要因地制宜的选用StringBuider与StringBuffer。
我们先来聊一聊String,由源码引入话题:
Java8中的String源码

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
   
    private final char value[];
  //...
}

我们从源码中可以总结出如下几点内容:

  1. String类被final关键字修饰,意味着它不可被继承;
  2. String的底层采用了final修饰的char数组,意味着它的不可变性;
  3. 实现了Serializable接口意味着它支持序列化;
  4. 实现了Comparable接口,意味着字符串的比较可以采用compareTo()方法,而不是==号,并且Sring类内部也重写了Object的equals()方法用来比较字符串相等。

Java9以后String的底层数据由char类型改为了byte类型,看源码:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
   
    @Stable
    private final byte[] value;
    private final byte coder;
    private int hash;
}

这样做的目的是为了节省字符串占用的内存空间,在大字符串处理中提升效率,同时也降低了垃圾回收的次数,提升性能。
面试官:“String类设置为不可变对象的话,岂不是每次创建或者字符串拼接的时候都会创建对象,增加开销?那为什么还要用final修饰呢?”
我:“嗯~这个问题非常好呀,我理解的是两个原因”

1、String 类是最常用的类之一,为了效率,禁止被继承和重写
2、为了安全。String 类中有很多调用底层的本地方法,调用了操作系统的API,
如果方法可以重写,可能被植入恶意代码,破坏程序。其实Java 的安全性在这里就有一定的体现啦。

基于以上String的特点,Java官方自然也知道它的弊端,所以为了全场景的最优方案适配,便产生了StringBuilder和StringBuffer这两个String的近亲字符串处理类,他们都实现了CharSequence 接口。
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 final 和 private 关键字修饰,所以可以解决在字符串拼接时的性能问题,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法,比如 append 方法。
源码:

abstract class AbstractStringBuilder implements Appendable, CharSequence {
   
    char[] value;
    public AbstractStringBuilder append(String str) {
   
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    //...
}

StringBuilder源码

public final class StringBuilder extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
   
    // ...

    public StringBuilder append(String str) {
   
        super.append(str);
        return this;
    }

    public String toString() {
   
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

    // ...
}

StringBuilder替代String的字符串拼接案例

Java 是一门解释型的编程语言,所以当编译器遇到 + 号这个操作符的时候,会将

new String("Hello") + new String("World")

解释为

new StringBuilder().append("Hello").append("World").toString();

并且在循环字符串拼接的时候,String的“+”拼接方式,会在循环内部多次创建StringBuilder对象,白白浪费了资源,如:

String[] arr = {
   "he", "llo", "world"};
String s = "";
for (int i = 0; i < arr.length; i++) {
   
    s += arr[i];
}
System.out.println(s);

这一段代码完全就可以直接使用StringBuilder调用append实现方式来替代:

String[] arr = {
   "he", "llo", "world"};
StringBuilder s = new StringBuilder();
for (String value : arr) {
   
    s.append(value);
}
System.out.println(s);

StringBuffer源码

public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {
   

    public StringBuffer() {
   
        super(16);
    }

    public synchronized StringBuffer append(String str) {
   
        super.append(str);
        return this;
    }

    public synchronized String toString() {
   
        return new String(value, 0, count);
    }

    // 其他方法
}

从它的源码我们可以看出,内部的实现与StringBuilder相差无机,无非是在方法的前面加了synchronized 关键字进行了同步,以确保在多线程场景下的线程安全,但在大部分的非多线程场景下,由于同步锁的存在会导致执行效率过低,并且即便在多线程情况下,依旧可以通过ThreadLocal搭配StringBuilder的方式来安全的修改字符串,所以,实际开发中,StringBuilder 的使用频率也是远高于 StringBuffer,甚至可以这么说,StringBuilder 完全取代了 StringBuffer。

三者异同点总结

相同点:
1、都可以储存和操作字符串
2、都使用 final 修饰,不能被继承
3、提供的 API 相似
异同点:
1、String 是只读字符串,String 对象内容是不能被改变的
2、StringBuffer 和 StringBuilder 的字符串对象可以对字符串内容进行修改,在修改后的内存地址不会发生改变
3、StringBuilder 线程不安全;StringBuffer 线程安全
目录
相关文章
|
存储 安全 Java
String StringBuffer StringBuilder 区别详解与对比分析
本文详细解析了Java中String、StringBuffer和StringBuilder的区别,从可变性、线程安全性和性能三个方面进行对比,并结合具体应用场景分析了三者的适用范围。通过性能测试示例展示了它们在字符串拼接时的效率差异,同时提供了实际代码案例帮助理解。总结指出,String适合少量操作或线程安全场景,StringBuffer适用于多线程环境,而StringBuilder则在单线程下性能最优。开发者应根据需求选择合适的类以优化程序性能。文末还附有相关面试资料供参考。
1418 2
|
存储 算法 架构师
阿里面试:PS+PO、CMS、G1、ZGC区别在哪?什么是卡表、记忆集、联合表?问懵了,尼恩来一个 图解+秒懂+史上最全的答案
阿里面试:PS+PO、CMS、G1、ZGC区别在哪?什么是卡表、记忆集、联合表?问懵了,尼恩来一个 图解+秒懂+史上最全的答案
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
734 9
|
安全 Java 程序员
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
1174 12
|
编译器 Android开发 开发者
Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
Lambda表达式和匿名函数都是Kotlin中强大的特性,帮助开发者编写简洁而高效的代码。理解它们的区别和适用场景,有助于选择最合适的方式来解决问题。希望本文的详细讲解和示例能够帮助你在Kotlin开发中更好地运用这些特性。
500 9
|
11月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
566 5
|
存储 编译器 C语言
关于string的‘\0‘与string,vector构造特点,反迭代器与迭代器类等的讨论
你真的了解string的'\0'么?你知道创建一个string a("abcddddddddddddddddddddddddd", 16);这样的string对象要创建多少个对象么?你知道string与vector进行扩容时进行了怎么的操作么?你知道怎么求Vector 最大 最小值 索引 位置么?
356 0
|
缓存 安全 Java
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
472 11
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、&quot;+&quot;操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
511 9