java基础-String&StringBuilder&StringBuffer源码解析(基于JDK1.8)与区别

String:字符串常量

String的值是不可变的,每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。
java基础-String&StringBuilder&StringBuffer源码解析(基于JDK1.8)与区别_第1张图片
String初始值为"hello",然后再这个字符串后面加上新的字符串"world",这个过程需要重新在栈堆内存中开辟新的内存空间,最终得到"hello world",字符串相应的也需要开辟内存空间,这样两个字符串需要开辟三次内存空间,这是对内存空间的极大浪费。

从源码角度说明String为什么是字符串常量。

底层数据结构

String的底层数据结构是char类型数组。

 //String类固定有一个char类型的数组。
    private final char value[];
构造函数

我们构造String字符串时,实际上就是在构造char数组,已经确定char[]数组不可在改变。string类没有所谓的add方法,所有的增删操作都是通过new新的string来实现。

   // 初始化传入一个String类型参数的构造代码块。
    public  String(String original) { 
        this.value = original.value;
        this.hash = original.hash;
    }
     //传入一个char [] ,value[]copy这个char[].
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
特殊构造函数:
  //该构造函数根据入参StringBuffer对象中的字符内容来构建新的String对象。
    public String(StringBuffer buffer) {
        synchronized(buffer) { //synchronized:线程安全。
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
    //一个特殊的保护类型的构造方法。
    //String(char[] value)方法在创建String的时候会用到Arrays的copyOf方法
    //将value中的内容逐一复制到String当中,
    //而这个String(char[] value, boolean share)方法则是直接将value的引用赋值给String的value。
    //那么也就是说,这个方法构造出来的String和参数传过来的char[] value共享同一个数组。
    //share 只支持true. 只是为了和string(char [] value)这个函数有所区别。以便进行重载。
    String(char[] value, boolean share) {//重点: 赋值的只是一个引用。
        // assert share : "unshared not supported";
        this.value = value;
    }

这两个构造方法都是针对StringBuffer的操作,第一个是直接将StringBuffer传给String,因为此时StringBuffer可能被多个线程操作,String直接对数组进行copy可能会发生数据错乱。因此给其加了与StringBuffer类中一样的同步锁。
第二个构造方法主要是针对StringBuffer的toString()方法专门进行设计的,第二个参数实际上没有任何意义只是为了重载。需要注意的是,这里传进来的char数组并没有进行数据copy变成String私有的数据,而是直接进行引用拷贝,通过下文的StringBuffer.toString()方法研究。我们可以得知,在toString()方法调用后,StringBuffer中将不在保留这个数组的引用,因此,实际上也变成了String的私有数据。至于为什么要放在toString()里进行数据copy工作,也是为了线程安全,详解见下文。

数据操作方式:

改变char数组里面任何一个字符也需要new string。

 //替换char数组里所有字符
 //我们可以看出,只要进行修改,最终都是通过new 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;
    }

AbstractStringBuilder源码分析

继承关系:

java基础-String&StringBuilder&StringBuffer源码解析(基于JDK1.8)与区别_第2张图片
我们可以看出来,StringBuffer和StringBuilder有共同父类,通过源码我们可以得知,这两个实现类主要业务逻辑都是靠其父类AbstractStringBuilder实现的。因此我们对AbstractStringBuilder进行分析。

数据结构:
char[] value;

其底层数据结构和string一样,也是char类型数组,唯一不同的是,其是可以进行增删改操作的,并且还不需要new新的对象。

增长方式:
rivate 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;
    }

由源码可以看出,其增长方式为2n+2。

增删改的数据操作方式:
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;
    }
 private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    } 
 public AbstractStringBuilder append(boolean b) {
        if (b) {
            ensureCapacityInternal(count + 4);
            value[count++] = 't';
            value[count++] = 'r';
            value[count++] = 'u';
            value[count++] = 'e';
        } else {
            ensureCapacityInternal(count + 5);
            value[count++] = 'f';
            value[count++] = 'a';
            value[count++] = 'l';
            value[count++] = 's';
            value[count++] = 'e';
        }
        return this;
    }      

我们以append方式进行举例,每次对AbstractStringBuilder的操作,实际上是对char类型的value数组。在我们进行apped操作时,首先对检查value数组的剩余位置能否够新的数据写入,如若不能则先进行2n+2扩容在进行数据写入。
需要注意的是,null值和boolean类型值在char数组中的表现形式,而在String中,是不能传入这两种类型的。

StringBuilder:字符串变量,非线程安全。

从上文可以得出,其基本业务实现靠AbstractStringBuilder完成。

构造函数:
public StringBuilder() {
        //初始化16
        super(16);
    }
    public StringBuilder(int capacity) {
        super(capacity);
    }
    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }
    public StringBuilder(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

我们需要注意的是,在初始创建StringBuilder时,无论是直接存放数据,还是不存放数据,StringBuilder必须保证在初次操作后char类型数组还有一定的空间可以用来进行append操作。为什么不直接调用其2n+2的扩容方式呢?对于我们日常大多数的业务逻辑来说,删和改是不需要扩容的,并且一般初始化以后,扩容都是很少的一部分,甚至有的转换成StringBuilder只是为了修改和删除。因此,我们直接进行二倍扩容在初始字符串很大的情况下,会很浪费空间。

StringBuffer:字符串变量,线程安全的。

从上文可以得出,其基本业务实现也是靠AbstractStringBuilder完成。

构造函数:
public StringBuffer() {
        //初始容量16
        super(16);
    }
    public StringBuffer(int capacity) {
        super(capacity);
    }
     //多处16位的创建方式
    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }
    public StringBuffer(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

其构造函数和其它增删改方法和StringBuffer是一致的。

和StringBuilder不同之处:
   //转换为字符串时的缓存
    private transient char[] toStringCache;
   
    public synchronized String toString() {
        //得到转换成String的数组,String不需要在创建数组,而是直接使用toStringCache
        //这样做的原因是,StringBuffer可以多线程操作,如果传过去让String进行copy会出现问题,
        //因此StringBuffer就代为处理,并且此操作后,toStringCache的引用就会变为null
        //因此只有String的char数组持有数据的引用,保证了数据安全。
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }

三者区别:

  • StringBuilder&StringBuffer均代表可变的字符序列。
  • StringBuilder:可变序列,线程不安全,效率高。
  • StringBuffer: 可变序列,线程安全,效率低。
  • String可以直接赋null,而后面两个不行。
  • 在单线程情况下三者效率:StringBuilder>StringBuffer>String

你可能感兴趣的:(笔记)