阅读java源码可能是每一个java程序员的必修课,只有知其所以然,才能更好的使用java,写出更优美的程序,阅读java源码也为我们后面阅读java框架的源码打下了基础。阅读源代码其实就像再看一篇长篇推理小说一样,不能急于求成,需要慢慢品味才行。这一系列的文章,记录了我阅读源码的收获与思路,读者也可以借鉴一下,也仅仅是借鉴,问渠那得清如许,绝知此事要躬行!要想真正的成为大神,还是需要自己亲身去阅读源码而不是看几篇分析源码的博客就可以的。更多的文章我放在了简书上(https://www.jianshu.com/u/37f0c8df3144)
总结StringBuffer、StringBuilder的操作基本一致,只是StringBuffer在一些方法上加了锁,保证线程安全,他们都继承于AbstractStringBuilder。无参构造时,StringBuffer容量的初始大小是16,当向构造器中传入字符串时,其容量大小为字符串长度+16,同时也可以直接指定其容量大小。
而对于StringBuffer的扩容,首先是将容量大小扩大二倍后再加2,然后判断是否足够或则溢出,如果出现Int溢出则执行hugeCapacity()
方法,将与Integer.MAX_VALUE
和MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
进行比较,如果大于Integer.MAX_VALUE
则抛出OutOfMemoryError
错误,如果小于MAX_ARRAY_SIZE
则赋值为MAX_ARRAY_SIZE
,如果小于Integer.MAX_VALUE
,大于MAX_ARRAY_SIZE
则返回扩容后的值。
如果没有溢出,并且扩容后的容量足够,那么就将容量设置为扩容后的容量,如果不够,且没有溢出,则直接将容量大小赋值为所需容量。
StringBuffer还支持手动扩容,在创建StringBuffer的对象后,还可以使用ensureCapacity()
方法将容量扩到指定大小。如果对于内存利用率要求比较高的情况,还可以利用StringBuffer的trimToSize()
方法对 StringBuffer的容量进行缩减,释放出没有被使用的存储空间。
在阅读完String、StringBuffer、StringBuilder源码后,我们可以发现之所以StringBuffer、StringBuilder在对字符串进行例如赋值,追加,替换等操作时会比String高效的多,就是因为String每次操作后会创建一个新的Srting对象来存储结果,而StringBuffer、StringBuilder无需重新创建对象。三者对于字符串的操作本质上都是在对存储字符串的字符数组value操作。
因为StringBuffer和StringBuilder的操作基本一样,所以这里以StringBuffer的源码解读为例。
类型变量
首先我们看一下StringBuffer有哪些变量:
StringBuffer独有的变量:
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;
这里序列号serialVersionUID
是为了对象序列化可以忽略,剩下的就只有一个char数组toStringCache
。代码中的注释也说的很清楚了,这个变量是用于缓存当前存储的字符串,当变量改变时清空。这里的改变包括很多情况,总结来说就是两点:当存储的字符串值改变或者字符串的长度改变时,toStringCache
会被清空。
StringBuffer继承了AbstractStringBuilder,所以它还有下面的变量:
/**
* 存储字符串的字符数组
* The value is used for character storage.
*/
char[] value;
/**
* 当前存储的字符串的长度
* The count is the number of characters used.
*/
int count;
方法
这里我们主要关注StringBuffer的构造方法和与扩容相关的方法。
首先看看StringBuffer的构造方法:
/**
*创建一个没有存储任何字符的字符串变量,并且它的初始容量为16
* Constructs a string buffer with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuffer() {
super(16);
}
/**
*创建一个指定容量大小的没有存储任何字符的字符串变量。
* Constructs a string buffer with no characters in it and
* the specified initial capacity.
*
* @param capacity the initial capacity.
* @exception NegativeArraySizeException if the {@code capacity}
* argument is less than {@code 0}.
*/
public StringBuffer(int capacity) {
super(capacity);
}
/**
*根据String,获得一个StringBuilder,这里可以用于String转换为StringBuilder.
* Constructs a string buffer initialized to the contents of the
* specified string. The initial capacity of the string buffer is
* {@code 16} plus the length of the string argument.
*
* @param str the initial contents of the buffer.
*/
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
/**
*根据字符序列获得一个StringBuilder。
* Constructs a string buffer that contains the same characters
* as the specified {@code CharSequence}. The initial capacity of
* the string buffer is {@code 16} plus the length of the
* {@code CharSequence} argument.
* <p>
* If the length of the specified {@code CharSequence} is
* less than or equal to zero, then an empty buffer of capacity
* {@code 16} is returned.
*
* @param seq the sequence to copy.
* @since 1.5
*/
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
这里可以总结出,StringBuffer的出示容量是16,我们也可以自定义创建指定初始容量大小的StringBuffer。而当是String或者CharSequence转换为StringBuffer时,StringBuffer的初始容量大小=输入的字符串长度+16
。
append()追加字符串方法
观察StringBuffer的所有append()方法我们可以发现,其append()的实现是如下三个步骤并且是加了锁的:
1.令toStringCache = null。
2.super.append(), 调父类相应的append()方法。
3.返回结果。
令toStringCache=null,与前面toStringCache注释相对应。我们接下来看看super.append()代码,即AbstractStringBuilder的append()方法。
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
/**
* Appends the specified string to this character sequence.
* <p>
* The characters of the {@code String} argument are appended, in
* order, increasing the length of this sequence by the length of the
* argument. If {@code str} is {@code null}, then the four
* characters {@code "null"} are appended.
* <p>
* Let <i>n</i> be the length of this character sequence just prior to
* execution of the {@code append} method. Then the character at
* index <i>k</i> in the new character sequence is equal to the character
* at index <i>k</i> in the old character sequence, if <i>k</i> is less
* than <i>n</i>; otherwise, it is equal to the character at index
* <i>k-n</i> in the argument {@code str}.
*
* @param str a string.
* @return a reference to this object.
*/
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;
}
// Documentation in subclasses because of synchro difference
public AbstractStringBuilder append(StringBuffer sb) {
if (sb == null)
return appendNull();
int len = sb.length();
ensureCapacityInternal(count + len);
sb.getChars(0, len, value, count);
count += len;
return this;
}
/**
* @since 1.8
*/
AbstractStringBuilder append(AbstractStringBuilder asb) {
if (asb == null)
return appendNull();
int len = asb.length();
ensureCapacityInternal(count + len);
asb.getChars(0, len, value, count);
count += len;
return this;
}
// Documentation in subclasses because of synchro difference
@Override
public AbstractStringBuilder append(CharSequence s) {
if (s == null)
return appendNull();
if (s instanceof String)
return this.append((String)s);
if (s instanceof AbstractStringBuilder)
return this.append((AbstractStringBuilder)s);
return this.append(s, 0, s.length());
}
从上面的代码可以看出,在AbstractStringBuilder进行追加操作时,主要分为一下几个步骤:
1.判断要追加的字符串是否为空,如果为空那么就执行appendNull()后返。
2.不为空,就执行ensureCapacityInternal()方法。
3.最后执行getChars()方法。
那么我们接下来看看appendNull()、ensureCapacityInternal()、getChars()方法的源代码:
appendNull():
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
这里可以看到,当我们追加的字符串为空时,StringBuffer会在末尾直接追加"null";
ensureCapacityInternal():
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
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;
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
可以看出,ensureCapacityInternal()的主要作用就是检测当前的容量是否足够容纳追加后的字符串,如果不能那么就先将容量扩充到原来的2倍,再加上2,如果还不够,那么直接将容量扩充到所需容量大小,如果时大容量即2倍之后会造成溢出的情况且小于Integer.MAX_VALUE - 8;,那么就将容量设定为Integer.MAX_VALUE - 8。容量最大值为int的最大值。而对于ensureCapacity()方法,在StringBuffer中重写了的,这里我想StringBuffer重写的原因时为了在手动扩充StringBuffer容量时,保证其线程安全。
ensureCapacity()手动扩容方法
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}
getChars():
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
if (srcBegin < 0)
throw new StringIndexOutOfBoundsException(srcBegin);
if ((srcEnd < 0) || (srcEnd > count))
throw new StringIndexOutOfBoundsException(srcEnd);
if (srcBegin > srcEnd)
throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
在进行判空、判容之后,就开始进行真正的字符追加操作了,这里在判断参数合理性之后,调用了底层的内存拷贝方法arraycopy(),这个方法时native的所以我们不需要继续关心下去。