前面分析完Java中常見容器的源碼,此篇博客來分析下Java
字符串相關的常用的三個類String
、StringBuilder
、StringBuffer
。
相信看過我前面的Java
容器源碼分析博客的小夥伴會發現一個規律
,我一般分析某個類的源碼都是從屬性
、構造器(初始化方法)
、常用的API
三個方向下手,並不會也沒有必要從源碼的第一行讀到最後一行。在此篇博客分析這三個類也同樣使用這種技巧
,如果你需要了解更多的API實現可以自己去翻源碼。
註明:以下源碼分析都是基於jdk 1.8.0_221
版本
String、StringBuilder、StringBuffer源碼分析與總結目錄
一、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
類
由於StringBuilder
、StringBuffer
類都是AbstractStringBuilder
的子類,所以我們先介紹下AbstractStringBuilder
類,待會介紹StringBuilder
、StringBuffer
類會輕鬆很多。
1、AbstractStringBuilder
類概述
AbstractStringBuilder
類的申明如下:
abstract class AbstractStringBuilder implements Appendable, CharSequence
AbstractStringBuilder
類是一個抽象類,實現了Appendable
接口,並且字符串的內容可修改。可能有部分小夥伴覺得Java
都已經設計了StringBuilder
、StringBuffer
類,爲啥還要設計它們的父類。主要是因爲StringBuilder
、StringBuffer
類功能基本是一樣的,只不過後者支持多線程併發讀寫,提取出一個父類,可以減少代碼的重複。
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
,比如insert
、subString
以及對應的重載,受篇幅限制,就不一一放上來了,其實沒有必要。
三、StringBuilder
類
StringBuilder
類的申明如下:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
StringBuilder
類的API都是對父類AbstractStringBuilder
的API
的封裝,只要看懂父類的相關API
即可。下面這放了append
、delete
方法,都是對父類API
的封裝,沒啥好介紹的。。。
四、StringBuffer
類
StringBuffer
類的申明如下:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
StringBuffer
類的API也都是對父類AbstractStringBuilder
的API
的封裝,只不過加了synchronized
關鍵字修飾,引入了鎖機制
。下面這放了append
、delete
方法,都是對父類API
的封裝,沒啥好介紹的。。。
五、總結
總的來說,String
對象是常量,無法修改字符串的內容,StringBuilder
、StringBuffer
對象支持對字符串的修改(刪除下標對應的字符、尾部添加字符串、插入字符串等),兩者都是AbstractStringBuilder
子類,不過StringBuilder
不支持多線程併發讀寫,而StringBuffer
通過synchronized
關鍵字修飾方法(this對象鎖),支持多線程併發讀寫,不過由於每次讀寫都要加鎖、釋放鎖,所以StringBuffer
的效率相對較低。