在前面的章节内, 我们自己研究了java.lang.String
类的源码. 本章主要解决与String相关的几个问题:
+
运算符的重载及其含义我们回顾下String的源码.
public final class String
implements java.io.Serializable, Comparable, CharSequence {
/** The value is used for character storage. */
private final char value[];
可以看到String
真正存储数据的地方在与char value[]
. 存储在一个数组内, 且其定义为final
类型. 也就是这个引用是不可改变的.
且其声明为private类型的. 在外界无法访问. 但是, 如果通过反射机制还是可以更改char []
数组内的值的.
import java.lang.reflect.Field;
/**
* 使用反射更改数组内的值.
*
* */
public class UpdateStringWithReflect {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
// 声明字符串
String tmpStr = "abcde";
System.out.println("Before tmpStr: "+tmpStr);
// 获取char value[]
Field valueFieldOfString = String.class.getDeclaredField("value");
// 更改访问权限
valueFieldOfString.setAccessible(true);
// 访问对象
char[] tmpValue = (char[]) valueFieldOfString.get(tmpStr);
// 更改对象内的值 更改为hello
tmpValue[0]='h';
tmpValue[1]='e';
tmpValue[2]='l';
tmpValue[3]='l';
tmpValue[4]='o';
System.out.println("After tmp: "+tmpStr);
}
}
输出结果:
Before tmpStr: abcde
After tmp: hello
可以看到其值发生了更改. 但是值得注意的是, 被final
修饰的对象, 其引用是无法更改的.
StringBuilder的声明如下所示:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
其继承的AbstractStringBuilder
也有一个char[] value
. 用于存储数据. 但是其无final
关键字修饰. 也就是其是可变的.
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
// StringBuilder
public StringBuilder append(String str) {
super.append(str);
return this;
}
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;
}
// String类中的 getChars方法
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
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;
}
可以看到每次真实扩容的代码为int newCapacity = (value.length << 1) + 2;
. 每次扩容2倍+2
. 且更改value
数组的指针, 因为是非final
关键字修饰的.
// StringBuilder
public AbstractStringBuilder insert(int offset, String str) {
if ((offset < 0) || (offset > length()))
throw new StringIndexOutOfBoundsException(offset);
if (str == null)
str = "null";
int len = str.length();
// 扩充长度
ensureCapacityInternal(count + len);
// 将原来 offset->end 的部分进行后移动 offset->offset+str.leng 移动到 offset+str.length -> end+str.length 即拷贝(end-offset)个char
System.arraycopy(value, offset, value, offset + len, count - offset);
str.getChars(value, offset);
count += len;
return this;
}
// String
// 将value值拷贝到 dst[] 数组内. 从dstBegin开始进行拷贝.
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
由上可知, StringBuilder
主要维护2个方法:
append
方法. 末尾进行插入.insert
方法. 从中间的某个地方进行插入. public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
可以看到, 其多维护了一个对象toStringCache
. 这个是干什么用的呢? 我们后面细说.
可以看到其与StringBuilder
一样. 都是继承AbstractStringBuilder
. 主要方法也都是append
与insert
.
随后, 我们看下其append
方法.
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
其append
方法是synchronized
关键字修饰的. 也就是线程安全的.
public AbstractStringBuilder insert(int offset, int i) {
return insert(offset, String.valueOf(i));
}
public synchronized StringBuffer insert(int offset, char c) {
toStringCache = null;
super.insert(offset, c);
return this;
}
但是, 让我比较疑惑的是. 其insert
方法有的是线程同步, 有的不是? 这是为什么?
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
对于toStringCache
的操作. 除了在append
和insert
时, 将其设置为null
以外. 其余主要的操作就是在toString()
方法内将其进行赋值.
Builder 模式
.append
和insert
.StringBuilder
是线程不安全的.StringBuffer
是线程安全的, 保证线程安全的策略是使用synchronized
关键字, 对所有append
方法和部分insert
方法添加.to be continue…
[1]. 【JDK】:java.lang.String、StringBuilder、StringBuffer 源码解析
[2]. String类真的不可变吗?