Bootstrap

java StringBuilder 和 StringBuffer 万字详解(深度讲解)

目录

一、前言

二、StringBuffer类介绍和溯源

1.介绍 :

2.溯源 :

三、StringBuffer类常用构造器

1.StringBuffer()

2.StringBuffer(int capacity)

3.StringBuffer(String str)

4.演示

5.Debug

四、StringBuffer VS String类(重要)

1.StringBuffer类与String类的比较 :

2.StringBuffer类与String类的相互转化 :

五、StringBuffer类常用方法

0.前言

1.int length()

2.int capacity()

3.StringBuffer append(...)

4.StringBuffer delete(int start, int end)

5.StringBuffer replace(int start, int end, String str)

6.StringBuffer reverse()

7.StringBuffer insert(int offset, String str)

8.演示

六、StringBuilder介绍和溯源

1.介绍

2.溯源

七、StringBuilder类常用构造器

1.StringBuilder()

2.StringBuilder(int capacity)

3.StringBuilder(String str)

4.演示

八、StringBuilder类常用方法

0.前言

1.演示 :

九、String类,StringBuffer类,StringBuilder类总比较

十、总结


一、前言

  • 本节内容是我们《API-常用类》专题的第三小节了。本节内容主要讲StringBuffer类和StringBuilder类, 内容包括但不限于 StringBuffer介绍和溯源StringBuffer类构造器和常用方法StringBuffer类和String类的比较及相互转化,以及 StringBuilder类和StringBuffer类的比较等等。up希望通过这篇博文的知识分享,能够帮助大家快速上手并理解java StringBuffer类和StringBuilder类。
  • 注意事项 : 代码中的注释也很重要不要眼高手低,自己敲一遍才能知道怎么用点击侧边栏目录或者文章开头的目录可以跳转
  • 良工不示人以朴,up所有文章都会适时改进。大家如果有什么问题,都可以在评论区一块儿交流,或者私信up。 感谢阅读!

二、StringBuffer类介绍和溯源

        1.介绍 :

                在上一小节的String类中,我们提到,每个字符串对象都是常量。当我们创建一个字符串对象,并试图对其内容进行“增”,“删”,或者“改”的操作时,实际上原来的字符串对象已经丢弃了。jvm会重新创建一个字符串对象,并令其指向常量池中新的数据空间。所以,如果多次进行这些“增删改”的操作,会导致大量副本字符串对象遗留在内存中,降低效率。那我们如何解决这个问题?这便要引出我们的StringBuffer类和StringBuilder类。

                StringBuffer类,指可变字符序列,用于构造字符串对象。其内部使用自动扩容的数组来操作字符串数据StringBuffer类属于java.base模块,java.lang包下,如下图所示 :

        2.溯源 :

                我们先来看看StringBuffer类的源码,试试能不能从中找出一些蛛丝马迹。如下 :

                可以看到,同String类一样,StringBuffer类也用了final关键字修饰,因此,StringBuffer类也不可被继承。我们再来看一下StringBuffer类的类图,如下 :

                可以看到,StringBuffer类并没有像String类一样直接继承了Object类,而是直接继承自AbstractStringBuilder类。但它也像String类一样实现了多个接口,其中Serializable接口的实现使得StringBuffer类的对象可以串行化,串行化后对象可以进行网络传输,也可以保存到文件

                但是,这时候可能就要有p小将(Personable小将,指风度翩翩的人)出来bb问了:你丫的,之前在String类的源码中,可以明明白白地看到 “private final byte[] value” 属性,并且源码中给出了注释——字符串在底层就是用这个字节数组来存储的。那你这StringBuffer类也没有见数组啥的属性,你上哪儿存储捏?

                不愧是p小将,6。是的,与String类一个较大的不同点在于,StringBuffer类本身并没有用来存储字符串的容器。不急,刚刚在类图中我们也看见了,StringBuffer类直接继承自AbstractStringBuilder类,java这么牛逼的语言,不会让你凭空去继承这么一个类的。来看看父类的源码,如下 :

                一看父类源码咱就懂了。唉哟,藏的还挺深儿滴。没错,父类AbstractStringBuilder源码中有byte[] value属性,并且源码中也明确给出了注释 “The value is used for character storage.”,但与String类不同的是,该数组无final修饰!  因此,StringBuffer字符串实际存放的位置是在堆内存中。这也从根本上解释了为什么StringBuffer是可变字符序列。

                当然,我们也可以通过Debug找到更令人信服的证据,如下图所示 :

                AbstractStringBuilder类中的byte[] value只是定义了一个字节数组,数组属于引用类型,默认指向为空(即null),但是当我们通过构造器 "StringBuffer(String str)" 来初始化一个非空的StringBuffer类对象时,很明显在底层有一个”new“的操作(即上图中)。在java面向对象专题我们说过,new出来的对象都在堆内存中。

                不止于此,如果我们是使用空参构造初始化StringBuffer类对象,底层会通过 "super(16);" 调用父类的一个带参构造[由此可知,使用空参构造初始化StringBuffer类对象时,底层的byte[]数组默认初始容量 = 16],如下图所示 :

                大家有兴趣可以自己下来去Debug一下。只要你能大致的看懂源码,明白它是干什么的,你就能对外面显式的一些功能理解地更深,更透彻。因此,Debug这时候便显得越来越关键。(PS : 大家有兴趣可以去看看up 的Debug入门教学)。等我们到了下一专题:《API-常用工具》专题的集合篇章,up会带大家将经典的几个集合实现类例如ArrayList,HashSet等一一进行Debug调试,分析它们的底层机制。


三、StringBuffer类常用构造器

        1.StringBuffer()

                构造一个不带字符的字符串缓冲区,其初始容量为16个字符。(这里提一嘴,“buffer”本身就是缓冲区,缓冲器,缓冲物“的意思。)

        2.StringBuffer(int capacity)

                构造一个不带字符,但具有指定初始容量的字符串缓冲区。即可对byte[] value的大小进行指定。

        3.StringBuffer(String str)

                构造一个字符串缓冲区,并将其内容初始化为指定字符串的内容。

        4.演示

                up以Constructor_类为演示类,代码如下 :


package csdn.knowledge.api.builder_buffer;

public class Constructor_ {
    public static void main(String[] args) {
    //演示 : 演示StringBuffer类的常用构造器
        //1.StringBuffer()
        StringBuffer stringBuffer_0 = new StringBuffer();
        System.out.println(stringBuffer_0.length());
        System.out.println(stringBuffer_0);
        System.out.println("----------");

        //2.StringBuffer(int capacity)
        StringBuffer stringBuffer_1 = new StringBuffer(141);
        System.out.println(stringBuffer_1.length());
        System.out.println(stringBuffer_1);
        System.out.println("----------");

        //3.StringBuffer(String str)
        StringBuffer stringBuffer_2 = new StringBuffer("CSDN yyds!");
        System.out.println(stringBuffer_2.length());
        System.out.println(stringBuffer_2);
    }
}

                运行结果 :

        5.Debug

                诚然,光看上面那破代码和一张糊弄人的输出结果出,我们无法直观看出三个构造器的区别,接下来up就以上面的代码为例,在第7行下一个断点,给大家把每个构造器的执行流程都Debug一下。注意:想想上面对每个构造器性质的描述,你应该知道你想在Debug过程中看到什么。

                ①第一个构造器Debug演示GIF图如下 :

                ②第二个构造器Debug演示GIF图如下 :

                ③第三个构造器Debug演示GIF图如下 :


四、StringBuffer VS String类(重要)

        1.StringBuffer类与String类的比较 :

         ①String类保存的是 字符串常量,无法直接更改字符串本身的值。String类的每次更新实际上就是更改引用指向的地址,效率较低。

                up给大家画了一张String类的内存图解,我们以下面代码为例 :


//仅作演示用,无实际意义
public static void main(String[] args) {
    String str_0 = new String("CSDN yyds");
    str_0 = new String("666");
    str_0 = "Cyan";
}

                内存图解如下 :(注意,图示main函数中,最后应该改为“str_0 => 0x0099”🙏

         StringBuffer保存的是 字符串变量,可以直接更改字符串本身的值。因为字符串 变量堆内存中,StringBuffer的每次更新实际上可以直接更新字符串的内容,不用每次更新地址,效率较高。只有在某些特殊情况下,比如说该数组预存的空间不足,需要扩容时,才创建新的对象。

                up给大家画了一张StringBuffer类的内存图解,我们以下面代码为例 :


//仅作演示用,无实际意义
public static void main(String[] args) {
    StringBuffer sf = new StringBuffer("csdnNB");
}

内存图解如下 :

        2.StringBuffer类与String类的相互转化 :

            ①String ——> StringBuffer

                方式一:

                利用上面的第三个构造器——StringBuffer(String str)

                eg :

                StringBuffer stringBuffer_0 = new StringBuffer("CSDN yyds");

                方式二:

                利用上面的第一个构造器——StringBuffer(),再利用append方法向容器中添加字符(串)

                eg :

                StringBuffer stringBuffer_1 = new StringBuffer();

                stringBuffer_1.append("Cyan_RA9");

                Δ演示 :

                up以Exchange_0类为演示类,代码如下 :


package csdn.knowledge.api.builder_buffer;

public class Exchange_0 {
    public static void main(String[] args) {
    //演示 : String ——> StringBuffer
        //方式一 : 
        StringBuffer stringBuffer_0 = new StringBuffer("CSDN yyds!");
        System.out.println(stringBuffer_0);
        //方式二 :
        StringBuffer stringBuffer_1 = new StringBuffer();
        stringBuffer_1.append("Cyan_RA9");
        System.out.println(stringBuffer_1);
    }
}

                运行结果 :

            ②StringBuffer ——> String

                方式一:

                利用StringBuffer类提供的toString方法

                eg :

                StringBuffer stringBuffer_0 = new StringBuffer("CSDN yyds");

                String str_0 = stringBuffer.toString();

                方式二:

                利用String类提供的构造器,在形参列表中直接传入一个StringBuffer类对象

                eg :

                StringBuffer stringBuffer_1 = new StringBuffer();

                String str_1 = new String(stringBuffer_1);

                Δ演示 :

                up以Exchange_1类为演示类,代码如下 :


package csdn.knowledge.api.builder_buffer;

public class Exchange_1 {
    public static void main(String[] args) {
    //演示 : StringBuffer ——> String
        //方式一 :
        StringBuffer stringBuffer = new StringBuffer("感谢大家阅读!");
        String str_0 = stringBuffer.toString();
        System.out.println(str_0);
        //方式二 :
        String str_1 = new String(stringBuffer);
        System.out.println(str_1);
    }
}

                运行结果 :


五、StringBuffer类常用方法

        0.前言

                我们可以先在IDEA的类图中查看一下StringBuffer类中的方法,看看是个什么情况。如下GIF图所示 :

                可以看到,光StringBuffer类中的方法就是巨**多了,而且旁边它爹的方法看着更多。因此,还是老规矩,up就把一些比较常见的,常用的方法比如说crud(增删改查)给大家分享出来,并给大家演示一下就好了。

        1.int length()

                该方法可以获取到当前StringBuffer容器中字符串的有效长度。

        2.int capacity()

                该方法可以返回当前容器的容量。

        3.StringBuffer append(...)

                该方法可以将传入的形参对应的字符串加入到当前容器中。(返回值为StringBuffer类型,可不做接收。)

        4.StringBuffer delete(int start, int end)

                该方法可以删除当前容器中指定序列部分的内容。传入的两个形参代表了删除的区间——[start, end),仍然是熟悉的前闭后开。(返回值为StringBuffer类型,可不做接收。)

        5.StringBuffer replace(int start, int end, String str)

                该方法可以将当前容器中指定序列部分的字符串替换为传入的str字符串。前两个形参的作用同delete方法的形参。最后一个形参代表你想最终替换成的字符串。(返回值为StringBuffer类型,可不做接收。)

        6.StringBuffer reverse()

                该方法可以将当前容器中的字符串反转顺序后再返回。(返回值为StringBuffer类型,可不做接收。)

        7.StringBuffer insert(int offset, String str)

                该方法可以在当前容器中字符串的指定索引处插入一段字符串,原字符串中的内容从该索引处自动后移。(返回值为StringBuffer类型,可不做接收。)

        8.演示

                up以Method_类为例,代码如下 :


package csdn.knowledge.api.builder_buffer;

public class Method_ {
    public static void main(String[] args) {
    //演示 : StringBuffer类常用方法
        //1 —— int length()
        StringBuffer strBuffer_0 = new StringBuffer("CSDN yyds!");
        System.out.println("当前字符串 = " + strBuffer_0);
        System.out.println("当前容器中字符串的有效长度为:" + strBuffer_0.length());
        System.out.println("============================================");

        //2 —— int capacity()
        StringBuffer strBuffer_1 = new StringBuffer(141);
        System.out.println("当前容器的容量是:" + strBuffer_1.capacity());
        System.out.println("============================================");

        //3 —— StringBuffer append(...)
        StringBuffer strBuffer_2 = new StringBuffer("大家好,");
        strBuffer_2.append("我是练习时长两年半的java博主——");
        strBuffer_2.append("Cyan_RA9——");
        strBuffer_2.append(6666);
        strBuffer_2.append(2333.333333);
        System.out.println("strBuffer_2容器中字符串的内容 = " + strBuffer_2);
        System.out.println("============================================");

        //4 —— StringBuffer delete(int start, int end)
        StringBuffer strBuffer_3 = new StringBuffer("小米,小红,小兰,小黑");
        System.out.println("当前字符串 = " + strBuffer_3);
        strBuffer_3.delete(0, 3);
        System.out.println("删去索引为[0, 3)的字符串后,现在的字符串 = " + strBuffer_3);
        System.out.println("============================================");

        //5 —— StringBuffer replace(int start, int end, String str)
        StringBuffer strBuffer_4 = new StringBuffer("大白 大黄 大哥 大狗");
        System.out.println("当前字符串 = " + strBuffer_4);
        strBuffer_4.replace(9, 11, "大猫");
        System.out.println("将\"大狗\"替换成\"大猫\"后,现在的字符串 = " + strBuffer_4);
        System.out.println("============================================");

        //6 —— StringBuffer reverse()
        StringBuffer strBuffer_5 = new StringBuffer("123456789");
        System.out.println("当前字符串 = " + strBuffer_5);
        strBuffer_5.reverse();
        System.out.println("颠倒字符串的顺序后,现在的字符串 = " + strBuffer_5);
        System.out.println("============================================");

        //7 —— StringBuffer insert(int offset, String str)
        StringBuffer strBuffer_6 = new StringBuffer("我叫,喜欢吃水果");
        System.out.println("当前字符串 = " + strBuffer_6);
        strBuffer_6.insert(2, "Cyan_RA9");
        System.out.println("在索引为2处插入一段字符串后,现在的字符串 = " + strBuffer_6);
    }
}

                运行结果 :


六、StringBuilder介绍和溯源

        1.介绍

                同StringBuffer一样,StringBuilder类也是一个可变的字符序列StringBuilder类提供与StringBuffer类兼容的API,因此两者在使用功能上非常相似,但是StringBuilder类不保证同步,因此StringBuilder类不是线程安全的

                StringBuilder类被设计用作StringBuffer类的一个简易替换,用在字符缓冲区被单个线程使用的时候。但在实际开发中,由于StringBuilder类效率比StringBuffer类还要高。因此,建议在满足单线程的基础上,优先使用StringBuilder类。

                StringBuilder类也属于java.base模块,java.lang包下,如下图所示 :

        2.溯源

                我们先来看看StringBuilder类的源码,看看有什么线索,如下所示 :

                可以看到,StringBuilder类也被final关键字修饰,因此StringBuilder类不可被继承。我们再来看看StringBuilder类的类图,如下 :

                大家可以通过侧边栏跳转回StringBuffer类的类图看看,up表示,不能说一模一样,但至少是完全相同😋。很明显,这俩是难兄难弟。同样的,StringBuilder类也实现了Serializable接口,使得StringBuilder类对象串行化,串行化后,对象可以进行网络传输,也可以保存到文件。同样的,StringBuilder类也继承了AbstractStringBuilder类,那自然也是在AbstractStringBuilder类中的byte[] value中来保存字符串的。


七、StringBuilder类常用构造器

        1.StringBuilder()

                构造一个不带字符的字符串缓冲区,其初始容量为16个字符

        2.StringBuilder(int capacity)

                构造一个不带字符,但具有指定初始容量的字符串缓冲区。即可对byte[] value的大小进行指定。

        3.StringBuilder(String str)

                构造一个字符串缓冲区,并将其内容初始化为指定字符串的内容。

        4.演示

                up以Constructor_EX类为演示类,代码如下 :


package csdn.knowledge.api.builder_buffer.builder;

public class Constructor_EX {
    public static void main(String[] args) {
    //演示 : StringBuilder类常用构造器
        //1 —— StringBuilder()
        StringBuilder sb_0 = new StringBuilder();
        System.out.println("当前sb_0容器的容量 = " + sb_0.capacity());
        System.out.println("当前sb_0容器内字符串的有效长度 = " + sb_0.length());
        System.out.println("---------------------");
        
        //2 —— StringBuilder(int capacity)
        StringBuilder sb_1 = new StringBuilder(141);
        System.out.println("当前sb_1容器的容量 = " + sb_1.capacity());
        System.out.println("当前sb_1容器内字符串的有效长度 = " + sb_1.length());
        System.out.println("---------------------");
        
        //3 —— StringBuilder(String str)
        StringBuilder sb_2 = new StringBuilder("CSDN yyds!");
        System.out.println("当前sb_2容器的容量 = " + sb_2.capacity());
        System.out.println("当前sb_2容器内字符串的有效长度 = " + sb_2.length());
    }
}

                运行结果 :


八、StringBuilder类常用方法

        0.前言

                由于StringBuilder类使用和StringBuffer类兼容的API,因此,这两者的常用方法基本相同。至少上文中StringBuffer类的7个常用方法均可以在StringBuilder类的API文档中查找到。而且,有些眼尖的小伙伴儿刚刚可能已经发现了,StringBuilder的三个常用构造器与StringBuffer类的如出一辙。这也是up为什么没有再给出StringBuilder类构造器的Debug测试。因为就算你Debug一下,也会发现它们底层其实都一样。有兴趣的小伙伴儿们可以自己下去Debug一下。

                因为两者的常用方法都一样,基本上就换了个名字,因此up也不全演示一遍了,就挑几个典型的给大家演示一下,过过眼就行,以免影响大家阅读体验。(绝b不是因为我懒!

        1.演示 :

                up以Method_EX为演示类,代码如下 :


package csdn.knowledge.api.builder_buffer.builder;

public class Method_EX {
    public static void main(String[] args) {
    //演示 : StringBuilder类常用方法
        StringBuilder sb = new StringBuilder("12345");
        System.out.println("当前字符串 = " + sb);

        sb.reverse();
        System.out.println("颠倒后的字符串 = " + sb);

        sb.append(123);
        sb.append("哈哈哈");
        sb.append(666.666);
        sb.append("牛逼!");
        System.out.println("增加后的字符串 = " + sb);

        sb.delete(0, sb.length());
        System.out.println("全部删光光!当前字符串 = " + sb);
    }
}

                运行结果 :


九、String类,StringBuffer类,StringBuilder类总比较

                特性对比: 

String : 不可变字符序列,效率低,但是复用率高。
StringBuffer : 可变字符序列,效率较高,且线程安全。
StringBuilder : 可变字符序列,效率最高,但线程不安全。

                使用场景对比:  

String : 适用于字符串很少被修改,且被多个对象引用的情况,比如定义数据库的IP信息,配置信息等。
StringBuffer : 适用于存在大量修改字符串的情况,且满足 多线程条件。
StringBuilder : 适用于存在大量修改字符串的情况,且满足 单线程条件。

十、总结

  • 🆗,以上就是关于StringBuffer类和StringBuilder类的全部内容了。希望这篇博文的内容分享,可以帮助大家对StringBuffer 和 StringBuilder有进一步的认识。
  • 回顾一下,up从“介绍和溯源”,“常用构造器”,“常用方法”三个主要方面分别对StringBuffer 和 StringBuilder作了解释和讲解;其中在StringBuffer部分,我们还着重介绍了String类和StringBuffer类的比较;最后,我们还对String类,StringBuffer类和StringBuilder类这三个作了比较性的总结。
  • 《API-常用类》专题下一小节——up准备来讲讲常用类Math类 和 System类,我们不见不散。感谢阅读!

        System.out.println("END--------------------------------------"); 

;