StringBuffer与StringBuilder 的区别

  String 字符串常量

  StringBuffer 字符串变量(线程安全)

  StringBuilder字符串变量(非线程安全)

  简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因为String类的声明是:public final,因此在每次对String类型进行改变的时候其实都等同于生成了一个新的String对象,然后将指针指向新的String对象,所以经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后,JVM的GC就会开始工作,那速度是一定会相当慢的。

  StringBuffer 和 StringBuilder 底层是 char[] 数组实现的

  在大部分情况下 StringBuffer > String

  Java.lang.StringBuffer:线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。

  可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。

  StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的位置添加字符。

  例如,如果z引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用z.append("le"),会使字符串缓冲区包含“startle”,而z.insert(4,"ab")将更改字符串缓冲区,使之变为“starabtle”。

  在大部分情况下 StringBuilder > StringBuffer

  java.lang.StringBuilder:一个可变的字符序列,是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

  Java中StringBuilder的用法:

  String对象是不可改变的。每次使用 System.String类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。在需要对字符串执行重复修改的情况下,与创建新的 String对象相关的系统开销可能会非常昂贵。如果要修改字符串而不创建新的对象,则可以使用java.lang.StringBuilder类。例如,当在一个循环中将许多字符串连接在一起时,使用 StringBuilder类可以提升性能。

(一)通过用一个重载的构造函数方法初始化变量,可以创建 StringBuilder类的新实例,比如:

StringBuilder testStringBuilder = new StringBuilder("Hello World!");

(二)设置容量和长度:

  虽然StringBuilder对象是动态对象,允许扩充它所封装的字符串中字符的数量,但是您可以为它可容纳的最大字符数指定一个值。此值称为该对象的容量,不应将它与当前 StringBuilder对象容纳的字符串长度混淆在一起。例如,可以创建StringBuilder类的带有字符串“Hello”(长度为 5)的一个新实例,同时可以指定该对象的最大容量为 25。当修改StringBuilder时,在达到容量之前,它不会为其自己重新分配空间。当达到容量时,将自动分配新的空间且容量翻倍 int newCapacity = value.length * 2 + 2;。可以使用重载的构造函数之一来指定StringBuilder类的容量。比如:以下代码可以将testStringBuilder对象扩充到最大25个空白。

StringBuilder testStringBuilder = new StringBuilder("Hello World!",25);

(三)Append方法:

   将文本或对象的字符串表示形式添加到由当前 StringBuilder对象表示的字符串的结尾处。

(四)Insert方法:

   将字符串或对象添加到当前StringBuilder中的指定位置。

(五)Remove方法:

   从当前StringBuilder中移除指定数量的字符,移除过程从指定的索引处开始。

(六)Replace方法:

   可以用另一个指定的字符来替换StringBuilder对象内的字符。

  如果程序对附加字符串的需求很频繁,不建议使用+来进行字符串的串联。可以考虑使用java.lang.StringBuilder 类使用这个类所产生的对象默认会有16个字符的容量(capacity),您也可以自行指定初始容量。如果附加的字符超出可容纳的长度,则StringBuilder 对象会自动增加容量以容纳被附加的字符。如果有频繁作字符串附加的需求,使用StringBuilder 类能使效率大大提高

  见下面测试案例:

public class testStringBuilder {

    public static void main(String[] args) {
        String text = "";

        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++)
            text = text + i;
        long endTime = System.currentTimeMillis();
        System.out.println("普通字符串直接相加的执行时间:" + (endTime - beginTime));

        StringBuffer sbf = new StringBuffer();
        beginTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++)
            sbf.append(String.valueOf(i));
        endTime = System.currentTimeMillis();
        System.out.println("使用StringBuffer的append()方法的执行时间:"+ (endTime - beginTime));

        StringBuilder sb = new StringBuilder();
        beginTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++)
            sb.append(String.valueOf(i));
        endTime = System.currentTimeMillis();
        System.out.println("使用StringBuilder的append()方法的执行时间:"+ (endTime - beginTime));
    }
}

输出结果:

普通字符串直接相加的执行时间:261
使用StringBuffer的append()方法的执行时间:3
使用StringBuilder的append()方法的执行时间:2

补充:Java中为什么StringBuilder比StringBuffer快?

  首先我们看一下源码:

  java.lang.StringBuilder

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

  java.lang.StringBuffer

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

  可以看到,StringBuffer类的append()方法,都被synchronized所修饰,这就是我们的主角。

  synchronized关键字是做什么的呢?在多线程中非常常见,根据用法不同,可以给方法、代码块或对象加锁。以此处为例,当多个线程访问加锁的方法时,每次只有一个线程获得锁,其他线程必须等待该线程释放锁(一般是执行完这个方法),再由下一个线程获得锁,才允许执行该方法。加锁会带来性能上的损耗(毕竟多执行了一些操作,还包括锁等待时的线程阻塞)。

  为什么StringBuffer要加锁?StringBuffer就是为了多线程而设计的,这样可以防止多个线程同时操作同一个对象,也就是线程安全。而如果使用多线程操作StringBuilder对象,则无法保证每次append进去的字符串保持原样(随线程数量和数据量增大,出现错乱的概率也越大,跟线程切换有关)。

  综上所述,Stringbuilder要比Stringbuffer快。

Question:So,什么时候使用StringBuffer呢?或者StringBuffer的适用场景是什么呢?

  stringbuffer固然是线程安全的,stringbuffer固然是比stringbuilder更慢,固然,在多线程的情况下,理论上是应该使用线程安全的stringbuffer的。然而,然而,然而,有谁给我一个实际的案例来显示你需要一个线程安全的string拼接器?

  对不起,至少在我浅薄的几年编程生涯中还没有遇到过,也许,仅仅是也许,这个地球上的确是存在这样的编程需求的,然而,它至少跟99.99...99%的程序员是无关的。所以,对于题主的问题,他们的适用场景是什么?

  最简单的回答是,stringbuffer基本没有适用场景,你应该在所有的情况下选择使用stringbuiler,除非你真的遇到了一个需要线程安全的场景,如果遇到了,请务必在这里留言通知我。然后,补充一点,关于线程安全,即使你真的遇到了这样的场景,很不幸的是,恐怕你仍然有99.99....99%的情况下没有必要选择stringbuffer,因为stringbuffer的线程安全,仅仅是保证jvm不抛出异常顺利的往下执行而已,它可不保证逻辑正确和调用顺序正确

  大多数时候,我们需要的不仅仅是线程安全,而是锁。最后,为什么会有stringbuffer的存在,如果真的没有价值,为什么jdk会提供这个类?答案太简单了,因为最早是没有stringbuilder的,sun的人不知处于何种愚蠢的考虑,决定让stringbuffer是线程安全的,然后大约10年之后,人们终于意识到这是一个多么愚蠢的决定,意识到在这10年之中这个愚蠢的决定为java运行速度慢这样的流言贡献了多大的力量,于是,在jdk1.5的时候,终于决定提供一个非线程安全的stringbuffer实现,并命名为stringbuilder。顺便,javac好像大概也是从这个版本开始,把所有用加号连接的string运算都隐式的改写成stringbuilder

  如果没有循环的情况下,单行用加号拼接字符串是没有性能损失的,java编译器会隐式的替换成stringbuilder,但在有循环的情况下,编译器没法做到足够智能的替换,仍然会有不必要的性能损耗,因此,用循环拼接字符串的时候,还是老老实实的用stringbuilder吧。

String s1 = "aaaaa"; 
String s2 = "bbbbb"; 
String r = null;
int i = 3694;
r = s1 + i + s2;

  通过查看字节码可以发现,上述代码中,JVM内部使用的是创建了一个StringBuilder完成拼接工作!!!

#Java#
全部评论
讲解清晰,👍
点赞 回复 分享
发布于 2020-12-13 19:35
不可变的原因是因为类被final修饰吗?
点赞 回复 分享
发布于 2020-12-14 10:23
感谢参与牛客创作者计划!欢迎更多牛友来写干货,瓜分5000元奖励~~技术场活动链接:https://www.nowcoder.com/link/czztlqjs
点赞 回复 分享
发布于 2020-12-14 17:49

相关推荐

点赞 评论 收藏
分享
13 42 评论
分享
牛客网
牛客企业服务