大家都知道String和StringBuffer的主要区别是:string不可变类,stringbuffer是可变类(都是对字符串操作)
关键:
String:value 是一个被final修饰的数组对象
StringBuffer:value [] char 没有被final修饰
String 对象不可变 源码分析(String 的底层是使用字符数组来实现的)
jdk 1.7 的源码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
// <span style="color:#FF0000;">数组的被final修饰,所以数据引用变量的值不能变</span>
private <span style="color:#CC0000;">final</span> char value[];
/** Cache the hash code for the string */
// 缓存String对象的哈希值
private int hash; // Default to 0
String 类中只有两个成员变量一个是value 一个是hash,这个hash和我们讨论的问题没关系,通过注解我们知道他是缓存String对象的hash值 。value 是一个被final修饰的数组对象,所以只能说他不能再引用到其他对象而不能说明他所引用的对象的内容不能改变。但我们在往下看源码就会发现String 类没有给这两个成员变量提供任何的方法所以我们也没办法修改所引用对象的内容,所以String 对象一旦被创建,这个变量被初始化后就不能再修改了,所以说String 对象是不可变对象。
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
// 创建新对象
return new String(buf, true);
}
}
return this;
}
总结:
1、String 类是一个final 修饰的类所以这个类是不能继承的,也就没有子类。
2、String 类的成员变量都是final类型的并且没有提供任何方法可以来修改引用变量所引用的对象的内容,所以一旦这个对象被创建并且成员变量初始化后这个对象就不能再改变了,所以说String 对象是一个不可变对象。
3、使用“+”连接字符串的过程产生了很多String 对象和StringBuffer 对象所以效率相比直接使用StringBuffer 对象的append() 方法来连接字符效率低很多。
4、引用变量是存在java虚拟机栈内存中的,它里面存放的不是对象,而是对象的地址或句柄地址。
5、对象是存在java heap(堆内存)中的值
6、引用变量的值改变指的是栈内存中的这个引用变量的值的改变是,对象地址的改变或句柄地址的改变,而对象的改变指的是存放在Java heap(堆内存)中的对象内容的改变和引用变量的地址和句柄没有关系。
二、stringbuffer:
1、 为什么StringBuffer 对象可变, 为什么要尽量指定初始大小,append方法是怎么实现的 下面我们来看看这几个为什么
2、String 对象不可变是因为成员变量都被final修饰并且没有提供任何访问被引用对象的方法所以不能改变,而StringBuffer是怎么样的那我们可以去看看源码:
public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;
/**
* Constructs a string buffer with no characters in it and an
* initial capacity of 16 characters.
*/
// 默认为16个字符
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</code>
* argument is less than <code>0</code>.
*/
public StringBuffer(int capacity) {
super(capacity);
}
/**
* Constructs a string buffer initialized to the contents of the
* specified string. The initial capacity of the string buffer is
* <code>16</code> plus the length of the string argument.
*
* @param str the initial contents of the buffer.
* @exception NullPointerException if <code>str</code> is <code>null</code>
*/
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
3、StringBuffer 类继承自AbstractStringBuilder 那在看看AbstractStringBuilder的源码
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
// 这里我们看到,这个数组没有被final 修饰,所以引用变量的值可以改变,
//可以引用到其他数组对象
char[] value;
/**
* The count is the number of characters used.
*/
// 记录字符的个数
int count;
/**
* This no-arg constructor is necessary for serialization of subclasses.
*/
AbstractStringBuilder() {
}
/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
// 构造函数,创建数组对象
value = new char[capacity];
}
/**
* Returns the length (character count).
*
* @return the length of the sequence of characters currently
* represented by this object
*/
public int length() {
return count;
}
从这些源码我们看到 他的数组和String 的不一样,因为成员变量value数组没有被final修饰所以可以修改他的引用变量的值,即可以引用到新的数组对象。所以StringBuffer对象是可变的
3、如果知道字符串的长度则创建对象的时候尽量指定大小
(1)、在上面的源代码中我们看到StringBuffer 的构造函数默认创建的大小为16个字符。
(2)、如果我们在创建对象的时候指定了大小则创建指定容量大小的数组对象
// 调用父类的构造函数,创建数组对象
public StringBuffer(int capacity) {
super(capacity);
}
/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
//按照指定容量创建字符数组
value = new char[capacity];
}
(3)、如果在创建对象时构造函数的参数为字符串则 创建的数组的长度为字符长度+16字符
这样的长度,然后再将这个字符串添加到字符数组中,添加的时候会判断原来字符数组中的个数加上这个字符串 的长度是否大于这个字符数组的大小如果大于则进行扩容如果没有则添加,源码如下:
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
append 出现在了这里刚好一起来看看 append方法的实现
4、其实append方法就做两件事,如果 count (字符数组中已有字符的个数)加添加的字符串的长度小于 value.length 也就是小于字符数组的容量则直接将要添加的字符拷贝到数组在修改count就可以了。
5、如果cout和添加的字符串的长度的和大于value.length 则会创建一个新字符数组 再将原有的字符拷贝到新字符数组,再将要添加的字符添加到字符数组中,再改变conut(字符数组中字符的个数)
整个添加过程的源码如下:
public synchronized StringBuffer append(Object obj) {
super.append(String.valueOf(obj));
return this;
}
调用父类的方法
public AbstractStringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
这个方法中调用了ensureCapacityInternal ()方法判断count(字符数组原有的字符个数)+str.length() 的长度是否大于value容量
/**
* This method has the same contract as ensureCapacity, but is
* never synchronized.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}
如果count+str.length() 长度大于value的容量 则调用方法进行扩容
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
Arrays.copyOf(value,newCapacity) 复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。
上面的getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
将字符从此字符串复制到目标字符数组dst中,第一个参数 第二个参数截取要添加字符串的长度,第三个为目标字符数组第四个为字符串要添加到数组的开始位置
到这里数组的赋值都结束了,修改count的值,整个append也就结束了。
总结:
1、StringBuffer 类被final 修饰所以不能继承没有子类
2、StringBuffer 对象是可变对象,因为父类的 value [] char 没有被final修饰所以可以进行引用的改变,而且还提供了方法可以修改被引用对象的内容即修改了数组内容。
3、在使用StringBuffer对象的时候尽量指定大小这样会减少扩容的次数,也就是会减少创建字符数组对象的次数和数据复制的次数,当然效率也会提升。
StringBuilder (线程安全)和StringBuffer(线程不安全)