网上太多讲解String, StringBuilder, StringBuffer的区别了,但是大多数都浅尝而止,大多数只是提到String是字符串常量,因为它是常量,所以也是线程安全的,StringBuilder和StringBuffer的区别是后者是线程安全的,还有很多细节没有介绍到,尽自己之能补充一下。
先上一张类结构图。
可变性
总所周知,String类是一个不可变类也就是final 符描述,难道final符描述的对象就不可变了吗?答案并不是,final描述标识引用不可变,但是引用下面的内存信息还是可以变化的,String不可变的一个根本原因是String根本没有提供操作它的内部对象的方法,以及它内部的char[]也加上final符,进而就变成不可变了。StringBuffer与StringBuilder都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,提供了各种append的方法,所以他们俩是可变类。
初始化
初始化就很简单了,String 只能通过new String(“hello”)方式进行初始化,String s = “hello”,这种方式如果常量池里有这个对象,它只是赋引用,并没有进行初始化。
同样,StringBuffer与StringBuilder也是只能通过new 的方式来初始化。
修改值
String是不可变的,所以不存在修改的概念,但是下面的这些代码看着像是修改,实际上是个语法糖,最终还是会转换为StringBuilder进行append。
String str = "hello";
str += "java";
语法糖解析之后变为:
StringBuffer sb = new StringBuffer("hello");
sb.append("java");
str = sb.toString();
所以,没有循环,判断或者其他作用域阻断的情况下,尽情的用+吧,没有问题的,不用担心性能问题(1.5之后)。
StringBuffer与StringBuilder可以通过append来进行修改。
StringBuffer与StringBuilder的append的扩容过程是怎样的?直接看源码吧。
/**
* 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;
}
数组的扩容的方法在ensureCapacityInternal中。
/**
* For positive values of {@code minimumCapacity}, this method
* behaves like {@code ensureCapacity}, however it is never
* synchronized.
* If {@code minimumCapacity} is non positive due to numeric
* overflow, this method throws {@code OutOfMemoryError}.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
最后调用newCapacity进行扩容。
/**
* Returns a capacity at least as large as the given minimum capacity.
* Returns the current capacity increased by the same amount + 2 if
* that suffices.
* Will not return a capacity greater than {@code MAX_ARRAY_SIZE}
* unless the given minimum capacity is greater than that.
*
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero or
* greater than Integer.MAX_VALUE
*/
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;
先扩容之前的长度的2倍+2,如果不够的话,直接使用入参的长度,目前还没想通为什么要这么做,直接使用入参的长度不就好了么?可能是为了减少扩容次数。
equals()和hashCode()
String实现了equals()方法和hashCode()方法
而StringBuffer和StringBuilder并没有实现equals()方法和hashCode()方法,因此不能使用StringBuffer对象存储进Java集合中,会达不到想要的效果。
线程安全
String和StringBuffer是线程安全的,因为String是常量而StringBuffer的所有方法都通过synchronized符进行标识,StringBuilder不是线程安全的。
所以在使用上遵循以下几点:
1.如果要操作少量的数据用 String
2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
效率上:
StringBuilder > StringBuffer > String
然后我想吐槽的是StringBuffer真的有人用么,在业务代码里,我从来没有见到过,框架代码可能会用到,java的开发人员帮我们想到了,我们知道就可以了。