String 、StringBuilder 与 StringBuffer 之间的区别

程序员大白

共 9177字,需浏览 19分钟

 · 2021-04-22


点击上方“程序员大白”,选择“星标”公众号

重磅干货,第一时间送达

String

初始化字符串:

String str = "so easy!";

这是字符串初始化的 “简介版本”,通常字符串的初始化有三种方式:

// 1.最常见,简短
String str1 = "Hello wrold!";
// 2.构造函数初始化
String str2 = new String("Hello World!");
// 3.字符数组初始化
char[] charArray = {'H''e''l''l''o'' ''W''o''r''l''d'};
String str3 = new String(charArray);

然后我们一起看一下 String 类的源码,再做分析。

public final class String
    implements java.io.SerializableComparable<String>, CharSequence 
{
    /** The value is used for character storage. */
    private final char value[];

    /**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */

    public String() {
        this.value = new char[0];
    }

    /**
     * Allocates a new {@code String} so that it represents the sequence of
     * characters currently contained in the character array argument. The
     * contents of the character array are copied; subsequent modification of
     * the character array does not affect the newly created string.
     *
     * @param  value
     *         The initial value of the string
     */

    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    
    ...
}

private final char value[]; ,可以观察字符串本身是保存在 char 数组中的,换句话说,String 对象其实就是一个字符数组。

这里需要注意的一件非常重要的事情是,String 类被 final 关键字所修饰,public final class String{}, 这意味着 String 是不可变(immutable)类型。

不可变(immutable) 意味着什么呢?

String str1 = "Hello World!";
str1.substring(1,4).concat("cbl").toLowerCase().trim().replace('b''a');
System.out.println(str1); // Hello World!

上面这段程序的输出依旧是 Hello World!

因为字符串是 final 的,所以对 str1 所进行的一系列操作并不会改变 str1 本身。这一系列操作返回的是更改后的新字符串,并不会改变 str1 本身。

我们可以看一下 concat() 的源码:

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

原始字符串永远不会更改。而是复制一份,并将要连接的文本被添加到复制的字符串之后,最后返回一个新的字符串。

我们用一个变量接收一下返回值,然后输出,发现返回了一个新的字符串 local

String str1 = "Hello World!";
String str2 = str1.substring(3,5).concat("cbl").toLowerCase().trim().replace('b''a');
System.out.println(str2); // local

JVM 的原生内存模型为:

方便我们对 String 、StringBuffer 和 StringBuilder 的理解,我们仅关心下面图示中的内存区域:

其中 Stack 表示栈区,Heap 表示堆区,String Pool 表示字符串池。

我们再来看看这两个字符串:

String str1 = "algorithm"
String str2 = "algorithm"

实例化的字符串 str1str2 的值保存在Java 堆内存中,堆内存用于为所有 Java 对象动态分配内存。虽然  str1str2 是两个不同的引用变量,但它们都指向 Java 堆内存中的同一块内存位置。

虽然看起来有两个不同的 String 对象,但实际上只有一个。str2 从未被实例化为对象,而是引用了在堆内存中分配给 str1 的对象。

这是由于 Java 针对字符串进行优化的方式造成的。每次实例化字符串对象时,都会将添加到堆内存的值与之前已添加的值进行比较。如果堆内存中已存在相等的值,则不会初始化该对象,而是将该值赋给引用变量。

str1 这样的常量值保存在一个称为常量字符串的池中,简称 String Pool,其中包含所有的常量字符串。不过,使用关键字 new 创建的字符串 “偷偷” 绕过了 String Pool,而是直接存储在 Java Heap 当中。

再看一个简单的例子:

String str1 = "algorithm";
String str2 = "algorithm";
String str3 = new String("algorithm");

System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
System.out.println(str1.equals(str2)); // true
System.out.println(str1.equals(str3)); // false

因为 str1str2 指向内存中的同一对象, str1 == str2 返回为 truestr1.equals(str2) 也返回 true

因为 str3  使用关键字 new 显式实例化,所以 String Pool 中虽然已存在字符串文本,依旧会使用构造函数创建一个新对象。

equals() 方法比较的两个变量的值,而不是它们所指向的内存地址,这就是 str1.equals(str3)str1.equals(str2) 都返回 true 的原因。而 == 判断的是两个变量所指向的内存地址,比较的是真正意义上的指针操作,所以 str1 == str2 返回 true ,而 str1 == str3 返回 false

PS:毫不夸张地说,我的一个在阿里实习过的大佬同学,在面试某国企时就跪在了 equals 和 == 的区别上

需要注意 substring()concat() 方法返回一个新的 String 对象,并将其保存在字符串池中。

我们举的例子都比较简单,但如果我们考虑一些使用数百个字符串变量和数千个操作(如 substring() 或concat())的大型项目,仅使用 String 类可能会导致严重的内存泄漏和时间延迟。

这正是为什么还要设计 StringBuffer 或 StringBuilder 类的原因。

StringBuffer & StringBuilder

可变性(mutablity)

StringBufferStringBuilder 对象与 String 对象一样,三者都是字符序列。但是 StringBufferStringBuilder 是可变的(mutable),String 是不可变的(immutable),这意味着一旦我们为 StringBufferStringBuilder 初始化一个值,该值就会作为 StringBufferStringBuilder 对象的属性进行处理。

无论我们修改 StringBufferStringBuilder 的值多少次,都不会创建新的 String、StringBuffer 或StringBuilder 对象。StringBufferStringBuilder 的时间效率更高,资源消耗更少。

StringBuilder vs StringBuffer

这两个类几乎完全相同,它们使用同名的方法,返回相同的结果。但它们之间有两个主要区别:

  • 线程安全

    StringBuffer 类是同步的(Synchronized),一次只能有一个线程调用 StringBuffer 实例的方法。StringBuilder 类是不同步的,多个线程可以调用 StringBuilder 类中的方法,而不会被阻塞。因此,StringBuffer 是线程安全的,而 StringBuffer 不是。如果我们开发的是多线程的应用程序,那么使用StringBuilder  可能会有风险。

  • 效率 StringBuffer 实际上比 StringBuilder 慢 2~3 倍。原因是 StringBuffer 线程安全(一次只允许一个线程在一个对象上执行),会导致代码执行速度慢得多。

方法

StringBuilderStringBuffer 拥有相同的方法(除 StringBuffer 的方法都是 synchronized 的)。两者常用的几个方法:append()insertreplace()delete()reverse()

String vs StringBuilder vs StringBuffer


StringStringBuilderStringBuffer
可变性不可变可变可变
线程安全
时间效率较低
内存效率

注意:从上表中看到的,String 类在时间和内存上的效率都较低,但这并不意味着我们不应该再使用它。

事实上,String 可以非常方便地使用,因为它可以快速编写,我在实际开发经常使用 public static final String 来定义字符串常量。

String concatString = "concatString";
StringBuffer appendBuffer = new StringBuffer("appendBuffer");
StringBuilder appendBuilder = new StringBuilder("appendBuilder");
long timerStarted;

timerStarted = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
    concatString += " another string";
}
System.out.println("50000次String concat 操作的时间:" + (System.currentTimeMillis() - timerStarted) + "ms");

timerStarted = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
    appendBuffer.append(" another string");
}
System.out.println("50000次 StringBuffer 的 append 操作时间:" + (System.currentTimeMillis() - timerStarted) + "ms");
        
timerStarted = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
    appendBuilder.append(" another string");
}
System.out.println("50000次 StringBuilder 的 append 操作时间:" + (System.currentTimeMillis() - timerStarted) + "ms");

输出:

50000次String concat 操作的时间:18108ms
50000次 StringBuffer 的 append 操作时间:7ms
50000次 StringBuilder 的 append 操作时间:3ms

根据您的Java虚拟机的不同,此输出可能会有所不同。仅从这个测试结果可以看出,StringBuilder 在字符串操作方面是最快的。速度次快的是 StringBuffer ,它比 StringBuilder 慢 2~3 倍。Stringconcat 操作最慢。

连接同样多的字符串,StringBuilderString 快约 6000 倍,换句话说,StringBuilder 在 1 秒内可以连接的字符串,String 需要 1.6 小时。

总结

可变性:String 是不可变类型,如果试图更改 String 对象的值,则会在 String Pool 中创建另一个对象,而 StringBuffer 和StringBuilder 是可变的,其值可以更改。

线程安全:StringBuffer 和 StringBuilder 之间的区别就在于 StringBuffer 是线程安全的。因此,当应用程序只需要在单线程中运行时,最好使用 StringBuilder ,因为 StringBuilder 比 StringBuffer 更高效。

使用建议:

  • 如果程序中的字符串不会更改,比如 Java 中常量字符串的定义( public static final String ),建议使用 String 类,因为 String 对象本身就是 final 的。
  • 如果程序中字符串需要改变,且仅是单线程访问,那么使用 StringBuilder 就足够了。
  • 如果程序中字符串需要改变,且为多个线程访问,建议使用 StringBuffer,因为 StringBuffer 是同步的,可以保证线程安全。


国产小众浏览器因屏蔽视频广告,被索赔100万(后续)

年轻人“不讲武德”:因看黄片上瘾,把网站和786名女主播起诉了

中国联通官网被发现含木马脚本,可向用户推广色情APP

张一鸣:每个逆袭的年轻人,都具备的底层能力




西[]


浏览 21
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报