【Java】StringBuffer 和 StringBuilder 的入门认识和理解


前言 碎碎念

之前一直没有写博客的习惯,也导致自己很长时间内并没有建立属于自己的知识库。因为需要,计划构建一个有体系的知识库,后续可能有构建个人网站的想法,但苦于并未理清多方面知识的体系架构,避免后期大量的删改和重构,决定先使用CSDN的博客练手,并积累一些文档以便后续使用


一、初识

1.简介

StringBufferStringBuilder都是用来处理字符串的常用封装类。
前者自 JKD1.0 便存在,而后者是 JDK1.5 后才加入的新特性
它们与Java初学者最早接触的String不同,这两者可以更加灵活的进行一些字符串处理的操作。

2.为什么要使用

下边是Buffer和Builder的源码截取:

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

上述代码说明两者都继承自相同的类 AbstractStringBuilder ,且实现了相同的接口,说明两者可以实现的功能是大体相同的

我们再来看Stringd的源码:


public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
----------------------------------------------------------------------------------
    abstract class AbstractStringBuilder implements Appendable, CharSequence {
	char[] value;

由上图可知,String创建的是被 final 修饰的,不可变的对象。但 2B兄弟不是。

如果我们像下图这样操作一下,并输出str,看看结果

String str = "哈哈";
str = "嘻嘻"System.out.println(str); //  嘻嘻

输出的确实是改变后的“嘻嘻”,但实际上,通过这样简单的赋值操作,只是新建了一个字符串,并将变量str的指向改变到了“嘻嘻”上,而不是说对“哈哈” 本身进行了操作,“哈哈“并没有消失,依旧存在着,只是成为任何变量的指向罢了。

简而言之,String所创造的对象,在创建之初,它的长度、内容和引用地址都已经被确定了,而String 中的方法往往会在程序运行过程中产生大量新的String,在执行各种方法后,将指针指向新的String对象。因此会产生大量不必要的冗余,也会对我们的性能产生影响
与String不同,2B兄弟在创建字符串时,并没有被 final 所修饰,这也就意味着通过2B兄弟创建的字符串对象,是可以进行修改的。各种方法都是对 2B对象 本身进行操作,而不是生成新的对象并改变对象引用。所以在性能上是优于String的


二、StringBuffer 和 StringBuilder异同

1.不同

总结,2B 兄弟的不同仅仅在于线程安全上的区别,其余的东西就是一个模子刻出来的,纯纯的亲兄弟。

// StringBufferd 的一些方法
 @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
 @Override
    public synchronized StringBuffer append(CharSequence s) {
        toStringCache = null;
        super.append(s);
        return this;
    }
// StringBuilder 的一些方法
 @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
 @Override
    public StringBuilder append(CharSequence s) {
        super.append(s);
        return this;
    }

我们可以看到,StringBuffer 的方法都被 synchronized 修饰了,而 StringBuilder 没有,该关键字决定了 StringBuffer 只能同时被一个线程使用,在多线程的情况下拥有较高的安全性,但为此付出了性能上的代价,比 StringBuilder 要略差一些。

2.扩容机制

1.初始化

二者的扩容机制是相同的,下面以StringBuffer的源码来进行解读
首先是初始化

public StringBuffer() {
        super(16);
    }
-----------------------------------------------------
AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

上面是StringBuffer的无参构造函数,当我们使用它创建对象时,会默认为我们提供了一个容量(capacity)大小为16的给其父类。在其父类中,收到子类StringBuilder传来的capacity值为16,然后创建一个长度为16的 char[ ] 数组,并赋值给成员变量,这样就完成了一个StringBuffer对象的创建。

public StringBuffer(int capacity) {
        super(capacity);
    }
---------------------------------------------------------------------
AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

接着来看它的有参构造函数,我们手动传入一个capaity参数,然后它会调用父类的有参构造方法,以我们给定的值来创建一个 char[ ] 数组,完成对StringBuilder的对象的创建。

2.扩容

我们以append( )方法来进行说明2B兄弟的扩容机制。

public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
---------------------------------------------------------------
//-------1
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;
     }
// ------2
private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

// -----3
private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2; // 扩容
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }


二者进行追加内容的时候都会调用父类的方法,而在 AbstractStringBuilder 的append()方法中,扩容的核心在于 ensureCapacityInternal( ) 方法,我们可以看到它将原数组的内容复制到了一个长度更大的新数组中,舍弃旧的数组,及完成了所谓的扩容。
进入到该方法中我们再进入到newCapacity ( ) 方法中就可以看到它是如何进行扩容的。它将原数组内容的长度扩大两倍,再加2作为新数组的长度。但为什么要这样扩容呢?
首先,如果我们刚好扩容添加内容的长度,就意味着我们每次添加新内容都要进行扩容,那么在大量操作下,对系统的性能是有压力的。源码中的方法考虑到了冗余量,使得在添加新内容时,如果我的数组长度足够,那么我不进行扩容,只有容量不够时我才去进行扩容操作。这样做的好处就是为系统省下了很多麻烦,也降低了我们在多次添加新内容时的扩容压力。


总结

通过这篇文章,我们可以初步地认识StringBufferStringBuilder,并简单地了解它区别于 String 的扩容机制。在日后的应用中,会经常使用这两种类和其方法,勤加练习,在不断的实操中深入理解并融会贯通吧。

以上文字内容仅作为参考,如有错误请指正,笔者会及时修改

vol1.0
最后修改时间 2024/05/15

  • 21
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值