String: 不可变的字符序列;底层使用char[]存储
StringBuffer: 可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder: 可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
几个问题:
1.理解什么是不可变,什么是可变?
观察String类底层:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
我们发现String类底层是一个char[]数组来存储字符串的,也是被final修饰的;代表这个数组一旦创建就不可更改.我们调用String类中的方法修改了字符串的内容之后,原来的字符串还在方法区中的字符串常量池中没有改变,只是返回了一个新的字符串.这就是不可变的意思.
String s = "abc";
String s1 = s.toUpperCase();
System.out.println(s);//abc 之前的字符串s没有变
System.out.println(s1);//ABC 返回新的字符串s1
观察StringBuffer底层:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public StringBuffer() {
super(16);
}
StringBuffer类继承了AbstractStringBuilder类,底层创建了一个长度是16的数组
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
StringBuffer底层也是一个char[]数组来存储字符串的,但是没有被final修饰,表示这个char[]数组是可变的.我们调用StringBuffer类中的方法修改了字符串的内容之后,原来的字符串还在方法区中的字符串常量池中也改变了,返回新的字符串和原来的是一样的,两个栈空间的对象名s,s1都指向方法区中字符串常量池中同一个地址.这就是可变的意思.
StringBuffer s = new StringBuffer("abc");
StringBuffer s1 = s.append("def");
System.out.println(s);//abcdef 原来的字符串s改变了
System.out.println(s1);//abcdef 返回的新的字符串s1
2.为什么可变?
StringBuffer继承于抽象类AbstractStringBuilder,观察AbstractStringBuilder类中的append方法:
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;
}
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);
}//调了System类中的arraycopy方法:
System类中的arraycopy方法:
@param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
* @exception IndexOutOfBoundsException if copying would cause
* access of data outside array bounds.
* @exception ArrayStoreException if an element in the <code>src</code>
* array could not be stored into the <code>dest</code> array
* because of a type mismatch.
* @exception NullPointerException if either <code>src</code> or
* <code>dest</code> is <code>null</code>.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
观察发现:StringBuffer继承于抽象类AbstractStringBuilder,他的内部实现靠他的父类完成,AbstractStringBuilder内的char数组是变量,可以用append追加,StringBuffer的append方法的底层实现就是创建一个新的目标对象,然后将各个字符引用串接起来,这就是一个新的对象了,本质上就是栈多了一个变量,而堆上面并没有变化(为什么是在栈上呢?因为触发到的arraycopy方法是native的,也就是其生成的变量会放到本地方法栈上面去)
StringBuffer的append方法并不会在堆上创建新的StringBuffer对象且内容是结果字符串,而是在arraycopy方法的帮助下,将各个字符引用连接起来
3.扩容原理
StringBuffer和StringBuilder底层的char[]数组都有扩容机制,在AbstractStringBuilder类中有一个方法
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;
}
使用append()方法添加数据时,如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组.
默认情况下,扩容为原来容量的2倍 + 2,同时将原数组中的元素复制到新的数组中。
StringBuffer和StringBuilder类似,方法也一样,只是StringBuilder是单线程操作字符串,线程不安全;
而StringBuffer是多线程操作字符串,是线程安全的.在开发中我们根据实际需要选择使用.
4. StringBuffer、StringBuilder中的常用方法
增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length();
遍历:for() + charAt() / toString()
5.一个面试题:
问: String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
答: 两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”