Java-String&StringBuffer&StringBulider

164 阅读3分钟

String

在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。

String 在java中是不可变的。 当我们创建了一个String对象之后,它的值是不能发生改变的。

public static void main(String[] args) {
        // 创建了一个String对象“hello” 将其地址赋给s。而不是生成了一个对象s。
        String s="hello"; 
        System.out.println(s.hashCode());
        // 并非改变原有的值,而是重新生成了一个新String对象并将指针指向该对象
        // 即“hello” 并未变成“helloworld” 而是新生成了一个“helloworld”并将其地址赋给s
        s+="world"; 
        System.out.println(s);
        System.out.println(s.hashCode());
        System.out.println("world".hashCode());
    }
99162322 // “hello” 的hashcode
helloworld
-1524582912 // hello world 的hashcode
113318802   // world的hashcode

那么有没有办法使得String 可变呢?

StringBuilder

可变的String,其本质是在StringBuilder 里面放了一个char[] value 通过value来存放字符串信息。

其构造函数有多个

public StringBuilder() {
        super(16);
    }
public StringBuilder(int capacity) {
        super(capacity);
    }
public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

其中super 是什么? 我们知道super调用的是父类的构造函数而

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

由此可知调用的是AbstractStringBuilder的构造函数

AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

因此我们知道super调用的结果是生成了一个char数组,那么append又是怎么实现的?

public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

可见又是调用其父类AbstractStringBuilder的方法

public AbstractStringBuilder append(String str) {
        if (str == null)
            //返回一个char数组 其内容为‘n’ ‘u’ ‘l’ ‘l’
            return appendNull(); 
        int len = str.length();
         // 确认char数组是否放得下,放不下就进行扩容
        ensureCapacityInternal(count + len);
        // char[] value; 是对象的属性,存放字符发数组
        //int count 是对象的属性,存放已经使用的数组长度
        //将此字符串str中的字符复制到目标字符数组value中,从value[count]开始
        str.getChars(0, len, value, count); 
        count += len;
        return this;
    }

至此 我们知道 StringBuilder的append是通过在一个字符数组value后面添加来实现的,由于是数组所以值是可以改变的,并且由于value是StringBuilder的一个属性所以value指向的数组空间变化不会引起整个对象的变化。

那么当数组容量不够是又是如何扩容的呢?在上面的代码中我们知道是通过ensureCapacityInternal(count + len); 来判断容量是否不够,不够就进行扩容

private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            //通过下面的方法进行扩容
            expandCapacity(minimumCapacity);
    }
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;
        }
        //通过Arrays.copyOf进行扩容,使value指向一个新数组
        value = Arrays.copyOf(value, newCapacity);
    }
public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        //System中提供了一个native静态方法arraycopy(),可以使用这个方法来实现数组之间的复制
        //即创建了一个新的数组将原本的字符串copy到新数组中,并返回。
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

测试:

public static void main(String[] args) {
        StringBuilder stringBuilder1=new StringBuilder("hello");
        System.out.println(stringBuilder1.hashCode());
        StringBuilder stringBuilder2=stringBuilder1.append("world");
        System.out.println(stringBuilder2.hashCode());
        System.out.println(stringBuilder1==stringBuilder2);
        System.out.println(stringBuilder2);
    }
1163157884
1163157884
true
helloworld

StringBuffer

由此可知其与StringBulider 一样继承了AbstractStringBuilder

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

我们在来看看他的构造函数

public StringBuffer() {
        super(16);
    }
public StringBuffer(int capacity) {
        super(capacity);
    }
public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

再看看append 与toString

@Override
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }
public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }

发现这其实与StringBuilder相同 唯一不同的是所有的方法都加了synchronized 保证了线程安全。