StringBuffe及StringBuilder源码分析

文章目录

  • 1. StringBuffer
    • 1.1 源码分析
      • 1.1.1 构造方法
        • 1. 构造空的StringBuffer对象
        • 2. 构造指定大小的StringBuffer对象
        • 3. 构造指定初始字符串的StringBuffer对象
        • 4. 构造指定初始字符串的StringBuffer对象
      • 1.1.2 基本方法
        • 1. 追加字符串
        • 2. 删除字符串
        • 3. 获取字符串的一部分
        • 4. 插入字符
        • 5. 获取下标索引
        • 6. 字符串翻转
        • 7. 字符的长度及字符数组的容量
      • 1.1.3 StringBuffer总结
  • 2. StringBuilder

StringBuffer及StringBuilder是两个经常拿来做字符串操作的类,人们通常都知道StringBuffer是线程安全的、StringBuilder是线程不安全的,但是对于其内部的源码没有做过多分析,本文来分析一下两者源码的区别及各自的优缺点。
本文出发点是能够从源码出发,详细的分析两者区别,关于两者笼统的结论,之前已经写过一篇简易文章: String、StringBuffer及StringBuilder区别

1. StringBuffer

整体看String、StringBuffer、StringBuilder三个类间的类结构:
StringBuffe及StringBuilder源码分析_第1张图片
上面的类结构图中,无论是直接依赖还是间接依赖,StringBuffer和StringBuilder都是以CharSequence及Appendable接口为基础,往下做功能扩展,因为String本身是是不可变了,StringBuffer及StringBuilder为了能够更方便、更高效地操作字符串,实现了许多操作字符串的功能,比如追加字符串、插入字符串、替换字符串等等。

1.1 源码分析

按照上面类结构分析StringBuffer主要继承了抽象类AbstractStringBuilder及接口CharSequence,其中AbstractStringBuilder主要提供一些字符串的操作方法,按照功能区分可以划分为追加字符串、删除字符、替换字符、插入字符、获取字符的索引、字符串翻转等,该系列方法都是操作字符串的核心方法,而实现于CharSequence接口的方法主要是获取字符串长度、获取某个索引的字符。
注意:所有的方法都是线程安全的,因为每个方法都用了synchronized关键字修饰,属于一种较为重量级别的锁,按照锁的特性,性能势必会较低。
StringBuffer的整体属性继承于AbstractStringBuilder,而AbstractStringBuilder底层则是用字符数组来存储字符。

  1. 利用字符数组value存储字符串
    char[] value;
  2. count表示value数组中真正存储的字符数
    int count;

下面按照功能点梳理源码

1.1.1 构造方法

1. 构造空的StringBuffer对象

该方法主要是调用父类的AbstractStringBuilder的构造方法,建立不包含任何字符串的字符串缓冲序列,默认创建的缓冲大小为16
StringBuffer.java

public StringBuffer() {
    super(16);
}

AbstractStringBuilder.java

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

2. 构造指定大小的StringBuffer对象

创建一个不包含任何字符串的StringBuffer对象,给定特定的字符串缓冲大小,而通过super()调用的父类方法与上者类似。

public StringBuffer(int capacity) {
    super(capacity);
}

3. 构造指定初始字符串的StringBuffer对象

将特定的字符串对象构造为StringBuffer对象,默认创建的StringBuffer大小为参数字符串长度+16

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

其中通过append()方法将字符串存储到原数组中,注意该方法中加上了synchronized关键字,通过加锁的方式,保证了该方法多线程间的安全性,而该方法内部的实现是通过父类AbstractStringBuilder中的append()方法。从这里也可以看到AbstractStringBuilder的重要性,基本上是StringBuffer类的核心方法的实现。

@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

调用父类的append()

// 该方法是父类AbstractStringBuilder的共用方法
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    // 用于数组长度的扩展
    ensureCapacityInternal(count + len);
    // 将字符串str追加到value字符数组后
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

数组扩展的方法分析:

// 数组扩展的方法
// 该方法是ensureCapacity的非同步版本
// 主要目的将数组扩展至minimumCapacity,主要是利用数组复制的方式
private void ensureCapacityInternal(int minimumCapacity) {
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,newCapacity(minimumCapacity));
    }
}


/**
 * 扩展数组大小的思路
 * 1. 判断参数中的minCapacity和(2*nowCapacity+2) = newCapacity的大小
 * 2. 如果newCapacity < minCapacity,则将数组扩展为minCapacity
 * 3. 如果newCapacity > minCapacity,则将数组扩展为2 * nowCapacity + 2
 * 4. 校验最新扩容的大小,至少不能比Integer.MAX_VALUE大
 * @param minCapacity
 * @return
 */
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;
}

/**
 * 校验最新扩容的大小,防止数据溢出
 * @param minCapacity
 * @return
 */
private int hugeCapacity(int minCapacity) {
    if (Integer.MAX_VALUE - minCapacity < 0) { 
        throw new OutOfMemoryError();
    }
    return (minCapacity > MAX_ARRAY_SIZE)
        ? minCapacity : MAX_ARRAY_SIZE;
}

4. 构造指定初始字符串的StringBuffer对象

将字符序列构造成字符串,默认创建的StringBuffer大小为参数的字符串长度+16
具体的数组长度扩展和构造方法3类似

public StringBuffer(CharSequence seq) {
    this(seq.length() + 16);
    append(seq);
}

1.1.2 基本方法

基本方法会按照功能来区分各个方法,为了控制分析的简介性,每个功能只分析一个方法。

1. 追加字符串

源代码中提供了多种追加字符串的方式,但是整体大同小异,只是处理请求参数的不同而已,现在只是针对最普遍的请求参数Object作分析
StringBuffe及StringBuilder源码分析_第2张图片

/**
 * 追加字符串
 * 该方法继承于AbstractStringBuilder
 * 思路:
 * 1. 将参数做强制类型转化为String,再通过父类AbstractStringBuilder的append()方法追加字符串
 * @param   obj   an {@code Object}.
 * @return
 */
@Override
public synchronized StringBuffer append(Object obj) {
    toStringCache = null;
    // 调用父类的append()方法追加字符串
    super.append(String.valueOf(obj));
    return this;
}

2. 删除字符串

删除指定索引范围内的字符,还有一个方法是删除指定索引的字符,整体思路一致。

/**
 * @param      start  开始的下标索引
 * @param      end    结束的下标索引
 * @return
 */
@Override
public synchronized StringBuffer delete(int start, int end) {
    toStringCache = null;
    // 通过调用父类的删除字符串方法
    super.delete(start, end);
    return this;
}


/**
 * 删除字符序列中一部分
 * 思路:
 * 1. 前面的都是一些参数的校验,比如检查收尾索引是否得当
 * 2. 通过数组复制的方式删除元素
 * @param start
 * @param end
 * @return
 */
public AbstractStringBuilder delete(int start, int end) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)
        end = count;
    if (start > end)
        throw new StringIndexOutOfBoundsException();
    int len = end - start;
    if (len > 0) {
        System.arraycopy(value, start+len, value, start, count-end);
        count -= len;
    }
    return this;
}	

3. 获取字符串的一部分

/**
 * 获取字符串的一部分
 * @param      start    开始索引
 * @param      end      结束索引
 * @return
 */
@Override
public synchronized String substring(int start, int end) {
    return super.substring(start, end);
}

/**
 * 获取字符串一部分
 * 前面是参数的校验,真正的思路是通过String的构造方法,构造出一个新的字符串
 * @param start
 * @param end
 * @return
 */
public String substring(int start, int end) {
    if (start < 0)
        throw new StringIndexOutOfBoundsException(start);
    if (end > count)
        throw new StringIndexOutOfBoundsException(end);
    if (start > end)
        throw new StringIndexOutOfBoundsException(end - start);
    return new String(value, start, end - start);
}

4. 插入字符

StringBuffe及StringBuilder源码分析_第3张图片
虽然方法众多,但是只分析其中一个方法,用一个方法了解整个插入的思路。

/**
 * 将字符串插入到元字符串中
 * @param      offset   开始插入的索引下标
 * @param      obj      插入的元素
 * @return
 */
@Override
public synchronized StringBuffer insert(int offset, Object obj) {
    toStringCache = null;
    // 调用父类的insert()方法
    super.insert(offset, String.valueOf(obj));
    return this;
}

/**
 * 将字符串插入的原字符索引中
 * 思路:
 * 1. 扩容
 * 2. 复制数组的方式扩展字符数组
 * @param offset        开始索引
 * @param str           插入的字符串
 * @return
 */
public AbstractStringBuilder insert(int offset, String str) {
    if ((offset < 0) || (offset > length()))
        throw new StringIndexOutOfBoundsException(offset);
    if (str == null)
        str = "null";
    int len = str.length();
    ensureCapacityInternal(count + len);
    System.arraycopy(value, offset, value, offset + len, count - offset);
    str.getChars(value, offset);
    count += len;
    return this;
}

5. 获取下标索引

获取str字符串在原字符串中的开始位置,该方法非同步方法。
该方法通过多层调用,StringBuffer ——> AbstractStringBuilder ——> String

@Override
public int indexOf(String str) {
    return super.indexOf(str);
}
/**
 * 该方法主要用于String、StringBuffer中的搜索使用,
 * @param source            被搜索的字符串
 * @param sourceOffset      被搜索字符的下标
 * @param sourceCount       被搜索字符串的总字符数
 * @param target            搜索的字符串
 * @param targetOffset      搜索字符串下标
 * @param targetCount       搜索字符串总字符数
 * @param fromIndex         开始搜索的下标
 * @return  返回目标字符串的起始下标,如果没有则返回-1
 */
static int indexOf(char[] source, int sourceOffset, int sourceCount,
        char[] target, int targetOffset, int targetCount,
        int fromIndex) {
    if (fromIndex >= sourceCount) {
        return (targetCount == 0 ? sourceCount : -1);
    }
    if (fromIndex < 0) {
        fromIndex = 0;
    }
    if (targetCount == 0) {
        return fromIndex;
    }

    char first = target[targetOffset];
    int max = sourceOffset + (sourceCount - targetCount);

    /**
     * 整体思路:通过双层循环方式寻找目标字符串的开始及结束
     */
    for (int i = sourceOffset + fromIndex; i <= max; i++) {
        /* Look for first character. */
        if (source[i] != first) {
            while (++i <= max && source[i] != first);
        }

        /* Found first character, now look at the rest of v2 */
        if (i <= max) {
            int j = i + 1;
            int end = j + targetCount - 1;
            for (int k = targetOffset + 1; j < end && source[j]
                    == target[k]; j++, k++);

            if (j == end) {
                /* Found whole string. */
                return i - sourceOffset;
            }
        }
    }
    return -1;
}

6. 字符串翻转

按照源码分析,该方法本身的时间复杂度为O(log2N)

/**
 * 字符串翻转
 * @return
 */
@Override
public synchronized StringBuffer reverse() {
    toStringCache = null;
    // 通过调用父类的字符串翻转方法
    super.reverse();
    return this;
}


/**
 * 翻转字符串
 * 整体的时间复杂度:O(log2N)
 * @return
 */
public AbstractStringBuilder reverse() {
    boolean hasSurrogates = false;
    int n = count - 1;
    for (int j = (n-1) >> 1; j >= 0; j--) {
        int k = n - j;
        char cj = value[j];
        char ck = value[k];
        value[j] = ck;
        value[k] = cj;
        if (Character.isSurrogate(cj) ||
            Character.isSurrogate(ck)) {
            hasSurrogates = true;
        }
    }
    if (hasSurrogates) {
        reverseAllValidSurrogatePairs();
    }
    return this;
}

7. 字符的长度及字符数组的容量

看到源码中提供了两个方法length()及capacity()方法,前者是获取StringBuffer对象中真正存储的字符数,后者获取的StringBuffer对象的数组的真正的容量。

/**
 * 获取StringBuffer对象字符串的总长度
 * @return
 */
@Override
public synchronized int length() {
    return count;
}
/**
 * 表示字符数组中的容量,一般该方法获取的值>=length()获取的值
 * @return
 */
@Override
public synchronized int capacity() {
    return value.length;
}

例子:

public class TestStringBuffer {
    public static void main(String[] args) {
    	// 该构造方法会默认创建长度为26的字符数组
        StringBuffer s = new StringBuffer("helloWorld");
        System.out.println(s.length());         // 10
        System.out.println(s.capacity());       // 26
    }
}

1.1.3 StringBuffer总结

可以从上面方法看到,该类中的方法基本上都加上了Synchronized关键字用于保证多线程间的线程安全。

2. StringBuilder

本文不对源码做详细的分析,因为本身代码和StringBuffer大同小异,只是StringBuilder每个方法没有加上Synchronized,虽然损失了同步的特性,但极大的提升了性能,一般使用的频率也比StringBuffer大。

你可能感兴趣的:(java)