Java 中处理字符串时常常使用的 String 是一个常量,一旦建立后不能被改变。为了提供可修改的操做,引入了 StringBuilder 类,可看前面的文章《从JDK源码看StringBuilder》。但它不是线程安全的,只用在单线程场景下。因此引入了线程安全的 StringBuffer 类,用于多线程场景。java
总的来讲主要是经过在必要的方法上加 synchronized 来实现线程安全。数组
--java.lang.Object
--java.lang.AbstractStringBuilder
--java.lang.StringBuffer
复制代码
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence
复制代码
StringBuffer 类被声明为 final,说明它不能再被继承。同时它继承了 AbstractStringBuilder 类,并实现了 Serializable 和 CharSequence 两个接口。缓存
其中 Serializable 接口代表其能够序列化。安全
CharSequence 接口用来实现获取字符序列的相关信息,接口定义以下:bash
length()
获取字符序列长度。charAt(int index)
获取某个索引对应字符。subSequence(int start, int end)
获取指定范围子字符串。toString()
转成字符串对象。chars()
用于获取字符序列的字符的 int 类型值的流,该接口提供了默认的实现。codePoints()
用于获取字符序列的代码点的 int 类型的值的流,提供了默认的实现。public interface CharSequence {
int length();
char charAt(int index);
CharSequence subSequence(int start, int end);
public String toString();
public default IntStream chars() {
省略代码。。
}
public default IntStream codePoints() {
省略代码。。
}
}
复制代码
private transient String toStringCache;
byte[] value;
byte coder;
int count;
复制代码
toString
方法生成的 String 对象,避免每次都要根据编码生成 String 对象。有若干种构造方法,能够指定容量大小参数,若是没有指定则构造方法默认建立容量为16的字符串对象。若是 COMPACT_STRINGS 为 true,即便用紧凑布局则使用 LATIN1 编码(ISO-8859-1编码),则开辟长度为16的 byte 数组。而若是是 UTF16 编码则开辟长度为32的 byte 数组。多线程
public StringBuffer() {
super(16);
}
AbstractStringBuilder(int capacity) {
if (COMPACT_STRINGS) {
value = new byte[capacity];
coder = LATIN1;
} else {
value = StringUTF16.newBytesFor(capacity);
coder = UTF16;
}
}
public StringBuffer(int capacity) {
super(capacity);
}
复制代码
若是构造函数传入的参数为 String 类型,则会开辟长度为str.length() + 16
的 byte 数组,并经过append
方法将字符串对象添加到 byte 数组中。并发
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
复制代码
相似地,传入参数为 CharSequence 类型时也作相同处理。app
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
复制代码
为了实现线程安全,其实最简单也多是最没效率的方法就是经过对某些方法进行同步,以此容许并发操做。因此 StringBuffer 和 StringBuilder 其实实现逻辑几乎都同样,而且抽象到 AbstractStringBuilder 抽象类中来实现,只是 StringBuffer 将一些必要的方法进行同步处理了。机器学习
StringBuffer 中大多数方法都只是加了 synchronized。分布式
好比下面该方法加了同步来保证计数的准确性。此外还包含不少其余方法,好比codePointCount
、capacity
、ensureCapacity
、codePointAt
、codePointBefore
、charAt
、getChars
、setCharAt
、substring
、subSequence
、indexOf
、lastIndexOf
、getBytes
。
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}
复制代码
该方法用于将该 StringBuffer 对象的容量压缩到与字符串长度大小相等。重写了该方法,主要是添加了同步,保证了数组复制过程的准确性。
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
public void trimToSize() {
int length = count << coder;
if (length < value.length) {
value = Arrays.copyOf(value, length);
}
}
复制代码
有多个
append
方法,都只是传入的参数不一样而已,一样是使用了 synchronized,另外它还会清理缓存 toStringCache,这是由于 append 后的字符串的值已经变了,因此须要重置缓存。重置缓存的方法还包括:appendCodePoint
、delete
、deleteCharAt
、replace
、insert
、reverse
。
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
复制代码
使用同步操做,先判断缓存是否为空,若是为空则先根据编码(Latin1 或 UTF16)建立对应编码占位的 String 对象,而后建立新 String 对象并返回。
@Override
public synchronized String toString() {
if (toStringCache == null) {
return toStringCache =
isLatin1() ? StringLatin1.newString(value, 0, count)
: StringUTF16.newString(value, 0, count);
}
return new String(toStringCache);
}
复制代码
该方法是序列化方法,分别将 value、count、shared 字段的值写入。
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
java.io.ObjectOutputStream.PutField fields = s.putFields();
char[] val = new char[capacity()];
if (isLatin1()) {
StringLatin1.getChars(value, 0, count, val, 0);
} else {
StringUTF16.getChars(value, 0, count, val, 0);
}
fields.put("value", val);
fields.put("count", count);
fields.put("shared", false);
s.writeFields();
}
复制代码
该方法是反序列方法,分别读取 value 和 count,而且初始化对象内的字节数组和编码标识。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
java.io.ObjectInputStream.GetField fields = s.readFields();
char[] val = (char[])fields.get("value", null);
initBytes(val, 0, val.length);
count = fields.get("count", 0);
}
void initBytes(char[] value, int off, int len) {
if (String.COMPACT_STRINGS) {
this.value = StringUTF16.compress(value, off, len);
if (this.value != null) {
this.coder = LATIN1;
return;
}
}
this.coder = UTF16;
this.value = StringUTF16.toBytes(value, off, len);
}
复制代码
-------------推荐阅读------------
跟我交流,向我提问:
公众号的菜单已分为“读书总结”、“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。
欢迎关注: