Java字符串類之String、StringBuilder、StringBuffer源碼分析與總結(你知道三者的區別?)

  前面分析完Java中常見容器的源碼,此篇博客來分析下Java字符串相關的常用的三個類StringStringBuilderStringBuffer

  相信看過我前面的Java容器源碼分析博客的小夥伴會發現一個規律,我一般分析某個類的源碼都是從屬性構造器(初始化方法)常用的API三個方向下手,並不會也沒有必要從源碼的第一行讀到最後一行。在此篇博客分析這三個類也同樣使用這種技巧,如果你需要了解更多的API實現可以自己去翻源碼。

註明:以下源碼分析都是基於jdk 1.8.0_221版本
在這裏插入圖片描述

一、String

1、String類概述

  String類的申明如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence

在這裏插入圖片描述

  String類,相信只要用過Java的都知道這個類,但是有不少人對這個類有些誤解String類是一個final類,並且實例中存放字符串內容的屬性也是常量,無法修改字符串的內容,這貌似與我們的常識貌似有些不符。

  比如stringA = stringA + “123”,這不是能修改stringA嗎?但是仔細觀察這個計算式,其實你是將stringA對象指向的字符串內容與“123”拼接生成一個新的String對象,然後讓stringA重新指向拼接好的對象。但是並沒有修改stringA之前指向的字符串本身!通過這一個例子就想改變某些小夥伴的之前對String類的認識,貌似有些困難,你可以查一下它有那個API可以修改字符串的內容。

2、String類主要屬性

  String類只有兩個重要的屬性:

/**
 * 存放字符串內容的char數組(注意這個屬性是常量,也就說一旦初始化就無法修改)
 */
private final char value[];

/**
 * 字符串內容對應的hash值,調用hashCode方法纔會設置,沒有set相關的api
 */
private int hash; // Default to 0

3、String類主要構造器

  String類有十來個構造器,下面我只展示了比較常用的幾個。

/**
 * 默認構造器,空串
 */
public String() {
    this.value = "".value;
}
/**
 * 從另一個String對象copy字符串內容
 */
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}
/**
 * 從char數組copy
 */
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}
/**
 * 指定插入數組copy的區間範圍[offset, offset + count)
 */
public String(char value[], int offset, int count) {
	// 檢查其實下標合法性
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count <= 0) {
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        if (offset <= value.length) {
            this.value = "".value;
            return;
        }
    }
    // offset + count不能超過了value.length,會產生下標越界
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}
/**
 * 從byte數組copy,指定區間範圍[offset, offset + count),並且設置字符集(比如"UTF-8")
 */
public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException {
    if (charsetName == null)
        throw new NullPointerException("charsetName");
    // checkBounds方法是檢查offset、length的合法性,與上一個構造器檢測是一樣的
    checkBounds(bytes, offset, length);
    this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
/**
 * 使用StringBuffer對象中的字符串內容初始化
 * (注意這裏有個synchronized同步代碼塊,後面介紹StringBuffer類再回過頭來看就知道了)
 */
public String(StringBuffer buffer) {
    synchronized(buffer) {
        this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
    }
}
/**
 * 使用StringBuilder對象的字符串內容初始化
 */
public String(StringBuilder builder) {
    this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

4、String類主要API

①、charAt方法

/**
 * 通過下標獲取字符串中的字符,String對象不支持 stringA[index]這種語法
 */
public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

②、equals方法

/**
 * 比較兩個字符串
 */
public boolean equals(Object anObject) {
    if (this == anObject) {
    	// 自己和自己比較肯定相等吧
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
        	// 當兩個字符串的長度相同纔有內容比較的意義
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
/**
 * 忽略字母大小寫比較字符串
 */
public boolean equalsIgnoreCase(String anotherString) {
	// 如果是自己和自己比較,直接返回true
	// 否則當anotherString != null,並且長度相等,才進行忽略字母大小寫比較字符串
    return (this == anotherString) ? true : (anotherString != null)
            && (anotherString.value.length == value.length)
            && regionMatches(true, 0, anotherString, 0, value.length);
}

③、startsWith/endsWith方法

/**
 * 判斷是否以prefix子串爲前綴,比如“123456789”以“123”、“123456”等前綴開頭
 */
public boolean startsWith(String prefix) {
	// 前綴判斷從標0開始比較
    return startsWith(prefix, 0);
}
/**
 * 判斷是否以suffix子串爲後綴,比如“123456789”以“789”、“456789”等後綴結尾
 */
public boolean endsWith(String suffix) {
	// 後綴比較,從value.length - suffix.value.length開始比較
    return startsWith(suffix, value.length - suffix.value.length);
}
/**
 * 前綴、後綴的比較,通過調整toffset偏移下標
 */
public boolean startsWith(String prefix, int toffset) {
    char ta[] = value;
    int to = toffset;
    char pa[] = prefix.value;
    int po = 0;
    int pc = prefix.value.length;
    // Note: toffset might be near -1>>>1.
    if ((toffset < 0) || (toffset > value.length - pc)) {
        return false;
    }
    // 一個一個字符比較即可
    while (--pc >= 0) {
        if (ta[to++] != pa[po++]) {
            return false;
        }
    }
    return true;
}

④、split方法

/**
 * 字符串切割,比如“12a34a56”,使用子串"a"切割後,可得"12","34","45"三個字符串
 */
public String[] split(String regex) {
    return split(regex, 0);
}
/**
 * limit表示最多分成幾段,limit == 0表示不限制
 */
public String[] split(String regex, int limit) {
    char ch = 0;
    // 這個if判斷比較複雜
    if (
    	((regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) 
    	|| (regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0)) 
    	&& (ch < Character.MIN_HIGH_SURROGATE ||ch > Character.MAX_LOW_SURROGATE)
    ){
    	// 當regex的長度==1時,此時它不能是".$|()[{^?*+\\"字符串中的字符(正則表達式中的元字符)
    	// 當regex的長度==2時,第一個字符是反斜槓並且第二個不是ascii數字或ascii字母。
   		// 此時才能直接遍歷求解
        int off = 0;
        int next = 0;
        boolean limited = limit > 0;
        ArrayList<String> list = new ArrayList<>();
        // 下面的操作比較簡單,只要碰到一個regex就切開即可
        while ((next = indexOf(ch, off)) != -1) {
            if (!limited || list.size() < limit - 1) {
            	// 沒有到達上限纔可以繼續切割剩餘的部分
                list.add(substring(off, next));
                off = next + 1;
            } else {
                // 到達上限,最後一段當一整段,不再切割
                list.add(substring(off, value.length));
                off = value.length;
                break;
            }
        }
        // 沒有切割出任何子串
        if (off == 0)
            return new String[]{this};
        if (!limited || list.size() < limit)
            list.add(substring(off, value.length));

        // Construct result
        int resultSize = list.size();
        if (limit == 0) {
            while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                resultSize--;
            }
        }
        String[] result = new String[resultSize];
        return list.subList(0, resultSize).toArray(result);
    }
    // 否則它需要調用正則表達式進行處理
    return Pattern.compile(regex).split(this, limit);
}

  介紹此時,仍然沒有出現修改String對象的方法,並不是我沒有放上來,而是確實沒有哇~

二、AbstractStringBuilder

  由於StringBuilderStringBuffer類都是AbstractStringBuilder的子類,所以我們先介紹下AbstractStringBuilder類,待會介紹StringBuilderStringBuffer類會輕鬆很多。

1、AbstractStringBuilder類概述

  AbstractStringBuilder類的申明如下:

abstract class AbstractStringBuilder implements Appendable, CharSequence

在這裏插入圖片描述
  AbstractStringBuilder類是一個抽象類,實現了Appendable接口,並且字符串的內容可修改。可能有部分小夥伴覺得Java都已經設計了StringBuilderStringBuffer類,爲啥還要設計它們的父類。主要是因爲StringBuilderStringBuffer類功能基本是一樣的,只不過後者支持多線程併發讀寫,提取出一個父類,可以減少代碼的重複。

2、AbstractStringBuilder類主要屬性

  AbstractStringBuilder類有兩個屬性:

/**
 * 存儲字符串
 */
char[] value;

/**
 * 字符串長度
 */
int count;

3、AbstractStringBuilder類主要構造器

  AbstractStringBuilder類只有兩個構造器,比較簡單

/**
 * 默認構造器
 */
AbstractStringBuilder() {
}

/**
 * 指定數組初始化長度
 */
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}

4、AbstractStringBuilder類主要API

①、charAt方法

/**
 * 通過index訪問字符串中的字符
 */
@Override
public char charAt(int index) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    return value[index];
}

②、setCharAt方法

/**
 * 修改字符串中指定下標的字符
 */
public void setCharAt(int index, char ch) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    value[index] = ch;
}

③、append方法

  append方法用於在尾端添加字符內容,此方法有很多個重載,我放了三個較爲重要的,如有需要可以翻下源碼。

/**
 * 將一個String對象的字符串內容拼接到當前AbstractStringBuilder的尾部
 */
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    // 確保value數組有足夠的長度放下拼接的字符段
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}
/**
 * 將一個AbstractStringBuilder對象(也可能是子類對象StringBuilder、StringBuffer)的字符串內容拼接到當前AbstractStringBuilder的尾部
 */
AbstractStringBuilder append(AbstractStringBuilder asb) {
    if (asb == null)
        return appendNull();
    int len = asb.length();
    // 確保value數組有足夠的長度放下拼接的字符段
    ensureCapacityInternal(count + len);
    asb.getChars(0, len, value, count);
    count += len;
    return this;
}
/**
 * 將一個char數組的[offset, offset + len)的字符段拼接到當前對象尾部
 */
public AbstractStringBuilder append(char str[], int offset, int len) {
	// 確保value數組有足夠的長度放下拼接的字符段
    if (len > 0)
        ensureCapacityInternal(count + len);
    System.arraycopy(str, offset, value, count, len);
    count += len;
    return this;
}

④、deleteCharAt方法

/**
 * 刪除制定下標的字符
 */
public AbstractStringBuilder deleteCharAt(int index) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    // 由於存儲字符的是數組,所以我們需要將[index + 1, count)的字符全部前移一個位置
    System.arraycopy(value, index+1, value, index, count-index-1);
    count--;
    return this;
}

⑤、delete方法

/**
 * 刪除字符串[start, end)中的字符
 */
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) {
    	// 需要將[end, count)段的字符全部前一個位置
        System.arraycopy(value, start+len, value, start, count-end);
        count -= len;
    }
    return this;
}

⑥、reverse方法

/**
 * 字符串內容翻轉,比如"abcdefg"翻轉後"gfedcba"
 */
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;
}

  AbstractStringBuilder類其實還有不少API,比如insertsubString以及對應的重載,受篇幅限制,就不一一放上來了,其實沒有必要。

三、StringBuilder

  StringBuilder類的申明如下:

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

在這裏插入圖片描述
  StringBuilder類的API都是對父類AbstractStringBuilderAPI的封裝,只要看懂父類的相關API即可。下面這放了appenddelete方法,都是對父類API的封裝,沒啥好介紹的。。。
在這裏插入圖片描述
在這裏插入圖片描述

四、StringBuffer

  StringBuffer類的申明如下:

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

在這裏插入圖片描述
  StringBuffer類的API也都是對父類AbstractStringBuilderAPI的封裝,只不過加了synchronized關鍵字修飾,引入了鎖機制。下面這放了appenddelete方法,都是對父類API的封裝,沒啥好介紹的。。。
在這裏插入圖片描述
在這裏插入圖片描述

五、總結

  總的來說,String對象是常量,無法修改字符串的內容,StringBuilderStringBuffer對象支持對字符串的修改(刪除下標對應的字符、尾部添加字符串、插入字符串等),兩者都是AbstractStringBuilder子類,不過StringBuilder不支持多線程併發讀寫,而StringBuffer通過synchronized關鍵字修飾方法(this對象鎖),支持多線程併發讀寫,不過由於每次讀寫都要加鎖、釋放鎖,所以StringBuffer的效率相對較低。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章