前言 碎碎念
之前一直没有写博客的习惯,也导致自己很长时间内并没有建立属于自己的知识库。因为需要,计划构建一个有体系的知识库,后续可能有构建个人网站的想法,但苦于并未理清多方面知识的体系架构,避免后期大量的删改和重构,决定先使用CSDN的博客练手,并积累一些文档以便后续使用
一、初识
1.简介
StringBuffer和StringBuilder都是用来处理字符串的常用封装类。
前者自 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作为新数组的长度。但为什么要这样扩容呢?
首先,如果我们刚好扩容添加内容的长度,就意味着我们每次添加新内容都要进行扩容,那么在大量操作下,对系统的性能是有压力的。源码中的方法考虑到了冗余量,使得在添加新内容时,如果我的数组长度足够,那么我不进行扩容,只有容量不够时我才去进行扩容操作。这样做的好处就是为系统省下了很多麻烦,也降低了我们在多次添加新内容时的扩容压力。
总结
通过这篇文章,我们可以初步地认识StringBuffer和StringBuilder,并简单地了解它区别于 String 的扩容机制。在日后的应用中,会经常使用这两种类和其方法,勤加练习,在不断的实操中深入理解并融会贯通吧。
以上文字内容仅作为参考,如有错误请指正,笔者会及时修改
vol1.0
最后修改时间 2024/05/15