*本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨(dú)家發(fā)布
從源碼角度徹底分析三者底層實(shí)現(xiàn).第一次寫源碼分析,小記一筆,由于本人才疏學(xué)淺,有很多地方可能存在誤解和不足,還望大家在評論區(qū)批評指正.
一柒昏、引言
學(xué)Java很久了,一直處于使用API+查API的狀態(tài),不了解原理,久而久之總是覺得很虛,作為一名合格的程序員這是不允許的,不能一直當(dāng)API Player,我們要去了解分析底層實(shí)現(xiàn),下次在使用時才能知己知彼.知道在什么時候該用什么方法和什么類比較合適.
在之前,我知道的關(guān)于String,StringBuffer,StringBuilder的知識點(diǎn)大概如下
- String是不可變的(修改String時漱办,不會在原有的內(nèi)存地址修改翔烁,而是重新指向一個新對象)搏熄,String用final修飾秉溉,不可繼承,String本質(zhì)上是個final的char[]數(shù)組,所以char[]數(shù)組的內(nèi)存地址不會被修改,而且String 也沒有對外暴露修改char[]數(shù)組的方法.不可變性可以保證線程安全以及字符串串常量池的實(shí)現(xiàn).頻繁的增刪操作是不建議使用String的.
- StringBuffer是線程安全的,多線程建議使用這個.
- StringBuilder是非線程安全的,單線程使用這個更快.
對于上面這些結(jié)論,我也不知道從哪里來的,,,,感覺好像是前輩的經(jīng)驗(yàn)吧,,,好了,廢話不多說,直接上代碼吧.
二匣沼、String源碼分析
看下繼承結(jié)構(gòu)源碼:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
}
可以看到String是final的,不允許繼承.里面用來存儲value的是一個final數(shù)組,也是不允許修改的.String有很多方法,下面就String類常用方法進(jìn)行分析.
1.構(gòu)造方法
public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
...
可以看到默認(rèn)的構(gòu)造器是構(gòu)建的空字符串,其實(shí)所有的構(gòu)造器就是給value數(shù)組賦初值.
2.字符串長度
返回該字符串的長度,這太簡單了,就是返回value數(shù)組的長度.
public int length() {
return value.length;
}
3.字符串某一位置字符
返回字符串中指定位置的字符;
public char charAt(int index) {
//1. 首先判斷是否越界
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
//2. 返回相應(yīng)位置的值
return value[index];
}
4.提取子串
用String類的substring方法可以提取字符串中的子串捂龄,簡單分析一下substring(int beginIndex, int endIndex)吧,該方法從beginIndex位置起释涛,從當(dāng)前字符串中取出到endIndex-1位置的字符作為一個 新的字符串(重新new了一個String) 返回.
方法內(nèi)部是將數(shù)組進(jìn)行部分復(fù)制完成的,所以該方法不會對原有的數(shù)組進(jìn)行更改.
public String substring(int beginIndex, int endIndex) {
//1.驗(yàn)證入?yún)⑹欠裨浇? if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
//2. 記錄切割長度
int subLen = endIndex - beginIndex;
//3. 入?yún)⒑戏ㄐ? if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
//4. 如果開始切割處是0,結(jié)束切割處是value數(shù)組長度,那么相當(dāng)于沒有切割嘛,就直接返回原字符串;如果是其他情況:則重新新建一個String對象
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
/**
* 通過一個char數(shù)組復(fù)制部分內(nèi)容生成一個新數(shù)組,復(fù)制區(qū)間:從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);
}
//count==0
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
//復(fù)制一部分
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
5.字符串比較
-
compareTo(String anotherString)
該方法是對字符串內(nèi)容按字典順序進(jìn)行大小比較,通過返回的整數(shù)值指明當(dāng)前字符串與參數(shù)字符串的大小關(guān)系.若當(dāng)前對象比參數(shù)大則返回正整數(shù)倦沧,反之返回負(fù)整數(shù)枢贿,相等返回0.
主要是挨個字符進(jìn)行比較
public int compareTo(String anotherString) {
//1. 記錄長度
int len1 = value.length;
int len2 = anotherString.value.length;
//2. 最短長度
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
//3. 循環(huán)逐個字符進(jìn)行比較,如果不相等則返回字符之差
//這里只需要循環(huán)lim次就行了
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
//可能是正數(shù)或負(fù)數(shù)
return c1 - c2;
}
k++;
}
//4. 最后返回長度之差 這里可能是0,即相等
return len1 - len2;
}
-
compareToIgnore(String anotherString)
與compareTo()方法相似,但忽略大小寫.
實(shí)現(xiàn):從下面的源碼可以看出,最終實(shí)現(xiàn)是通過一個內(nèi)部類CaseInsensitiveComparator,它實(shí)現(xiàn)了Comparator和Serializable接口,并實(shí)現(xiàn)了compare()方法,里面的實(shí)現(xiàn)方法和上面的compareTo()方法差不多,只不過忽略大小寫.
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
-
equals(Object anotherObject)
比較當(dāng)前字符串和參數(shù)字符串刀脏,在兩個字符串相等的時候返回true,否則返回false.
大體實(shí)現(xiàn)思路:
- 先判斷引用是否相同
- 再判斷該Object對象是否是String的實(shí)例
- 再判斷兩個字符串的長度是否一致
- 最后挨個字符進(jìn)行比較
public boolean equals(Object anObject) {
//1. 引用相同
if (this == anObject) {
return true;
}
//2. 是String的實(shí)例?
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
//3. 長度
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
//4. 挨個字符進(jìn)行比較
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
-
equalsIgnoreCase(String anotherString)
與equals方法相似超凳,但忽略大小寫.但是這里要稍微復(fù)雜一點(diǎn),因?yàn)闋窟B到另一個方法regionMatches(),沒關(guān)系,下面跟著我一起慢慢分析.
在equalsIgnoreCase()方法里面首先是校驗(yàn)引用值是否一致,再判斷否為空,緊接著判斷長度是否一致,最后通過regionMatches()方法測試兩個字符串每個字符是否相等(忽略大小寫).
在regionMatches()方法中其實(shí)還是比較簡單的,就是逐字符進(jìn)行比較,當(dāng)需要進(jìn)行忽略大小寫時,如果遇到不相等的2字符,先統(tǒng)一轉(zhuǎn)成大寫進(jìn)行比較,如果相同則繼續(xù)比較下一個,不相同則轉(zhuǎn)成小寫再判斷是否一致.
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
public boolean regionMatches(boolean ignoreCase, int toffset,
String other, int ooffset, int len) {
char ta[] = value;
int to = toffset;
char pa[] = other.value;
int po = ooffset;
// Note: toffset, ooffset, or len might be near -1>>>1.
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false;
}
while (len-- > 0) {
//循環(huán)校驗(yàn)每個字符是否相等,相等則繼續(xù)校驗(yàn)下一個字符
char c1 = ta[to++];
char c2 = pa[po++];
if (c1 == c2) {
continue;
}
//如果遇到不相等的2字符,再判斷是否忽略大小寫.
//先統(tǒng)一轉(zhuǎn)成大寫進(jìn)行比較,如果相同則繼續(xù)比較下一個,不相同則轉(zhuǎn)成小寫再判斷是否一致
if (ignoreCase) {
// If characters don't match but case may be ignored,
// try converting both characters to uppercase.
// If the results match, then the comparison scan should
// continue.
char u1 = Character.toUpperCase(c1);
char u2 = Character.toUpperCase(c2);
if (u1 == u2) {
continue;
}
// Unfortunately, conversion to uppercase does not work properly
// for the Georgian alphabet, which has strange rules about case
// conversion. So we need to make one last check before
// exiting.
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
continue;
}
}
return false;
}
return true;
}
6.字符串連接
將指定字符串聯(lián)到此字符串的結(jié)尾愈污,效果等價于"+".
實(shí)現(xiàn)思路:構(gòu)建一個新數(shù)組,先將原來的數(shù)組復(fù)制進(jìn)新數(shù)組里面,再將需要連接的字符串復(fù)制進(jìn)新數(shù)組里面(存放到后面).
public String concat(String str) {
//1. 首先獲取傳入字符串長度 咦,居然沒有對入?yún)⒑戏ㄐ赃M(jìn)行判斷?萬一是null呢
int otherLen = str.length();
//2. 如果傳入字符串長度為0,就沒必要往后面走了
if (otherLen == 0) {
return this;
}
//3. 記錄當(dāng)前數(shù)組長度
int len = value.length;
//4. 搞一個新數(shù)組(空間大小為len + otherLen),前面len個空間用來存放value數(shù)組
char buf[] = Arrays.copyOf(value, len + otherLen);
//5. 將str存入buf數(shù)組的后面otherLen個空間里面
str.getChars(buf, len);
//6. new一個String將新建的buf數(shù)組傳入
return new String(buf, true);
}
//將des數(shù)組復(fù)制進(jìn)value數(shù)組中,dstBegin:目的數(shù)組放置的起始位置
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
//這里的share參數(shù)貌似總是為true 所以是暫時沒用咯??
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
7.字符串中單個字符查找
-
indexOf(int ch/String str)
返回指定字符在此字符串中第一次出現(xiàn)處的索引,在該對象表示的字符序列中第一次出現(xiàn)該字符的索引,如果未出現(xiàn)該字符轮傍,則返回 -1暂雹。
其實(shí)該方法最后是調(diào)用的
indexOf(ch/str, 0);
該方法放到下面進(jìn)行分析. -
indexOf(int ch/String str, int fromIndex)
該方法與第一種類似,區(qū)別在于該方法從fromIndex位置向后查找.
先分析indexOf(int ch, int fromIndex),該方法是查找ch在fromIndex索引之后第一次出現(xiàn)的索引.主要就是逐個字符進(jìn)行比較,相同則返回索引.如果未找到則返回-1.
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
//1. 邊界
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1.
return -1;
}
//2. 是否是罕見字符
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
//在這里其實(shí)已經(jīng)處理了大多數(shù)情況
final char[] value = this.value;
//3. 從fromIndex開始,循環(huán),找到第一個與ch相等的進(jìn)行返回
for (int i = fromIndex; i < max; i++) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
//4. 罕見字符處理
return indexOfSupplementary(ch, fromIndex);
}
}
再來分析indexOf(String str, int fromIndex),該方法功能是從指定的索引處開始创夜,返回第一次出現(xiàn)的指定子字符串在此字符串中的索引.
大體思路:
- 有點(diǎn)類似于字符串查找子串,先在當(dāng)前字符串中找到與目標(biāo)字符串的第一個字符相同的索引處
- 再從此索引出發(fā)循環(huán)遍歷目標(biāo)字符串后面的字符.
- 如果全部相同,則返回下標(biāo);如果不全部相同,則重復(fù)步驟1
文字可能描述不清楚,上圖片
我們要在beautifulauful中查找ful,那么步驟是首先找到f,再匹配后面的ul
部分,找到則返回索引,未找到則繼續(xù)查找.
public int indexOf(String str, int fromIndex) {
return indexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}
//在source數(shù)組中查找target數(shù)組
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
//1. 校驗(yàn)參數(shù)合法性
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
//2. 記錄第一個需要匹配的字符
char first = target[targetOffset];
//3. 這一次匹配的能到達(dá)的最大索引
int max = sourceOffset + (sourceCount - targetCount);
//4. 循環(huán)遍歷后面的數(shù)組
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
//5. 循環(huán)查找,直到查找到第一個和目標(biāo)字符串第一個字符相同的索引
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
//6. 找到了第一個字符,再來看看目標(biāo)字符串剩下的部分
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
//7. 匹配一下目標(biāo)字符串后面的字符串是否相等 不相等的時候就跳出循環(huán)
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);
//8. 如果全部相等,則返回索引
if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
-
lastIndexOf(int ch/String str)
該方法與第一種類似杭跪,區(qū)別在于該方法從字符串的末尾位置向前查找.
實(shí)現(xiàn)方法也與第一種是類似的,只不過是從后往前查找.
public int lastIndexOf(int ch) {
return lastIndexOf(ch, value.length - 1);
}
public int lastIndexOf(int ch, int fromIndex) {
//1. 判斷是否是罕見字符
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
//2. 從fromIndex(從哪個索引開始), value.length - 1(數(shù)組最后一個索引)中小一點(diǎn)的往前找,這里之所以這樣做是因?yàn)閒romIndex可能比value.length-1大.這里求最小值就可以覆蓋所有情況,不管fromIndex和value.length-1誰大.
int i = Math.min(fromIndex, value.length - 1);
//3. 循環(huán) 逐個字符進(jìn)行比較,找到則返回索引
for (; i >= 0; i--) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
//4. 罕見字符處理
return lastIndexOfSupplementary(ch, fromIndex);
}
}
-
lastIndexOf(int ch/String str, int fromIndex)
該方法與第二種方法類似,區(qū)別在于該方法從fromIndex位置向前查找.
實(shí)現(xiàn)思路:這里要稍微復(fù)雜一點(diǎn),相當(dāng)于從后往前查找指定子串.上圖吧
圖畫的有點(diǎn)丑,哈哈. 假設(shè)我們需要在
StringBuffer
中查找ABuff
中的子串Buff
,因?yàn)?code>Buff的長度是4,所以我們最大的索引可能值是圖中的rightIndex.然后我們就開始在source數(shù)組中匹配目標(biāo)字符串的最后一個字符,匹配到后,再逐個字符進(jìn)行比較剩余的字符,如果全部匹配,則返回索引.未全部匹配,則再次在source數(shù)組中尋找與目標(biāo)字符串最后一個字符相等的字符,然后找到后繼續(xù)匹配除去最后一個字符剩余的字符串. 唉~敘述的不是特別清晰,看代碼吧,代碼比我說的清晰..
public int lastIndexOf(String str, int fromIndex) {
return lastIndexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}
static int lastIndexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
/*
* Check arguments; return immediately where possible. For
* consistency, don't check for null str.
*/
//1. 最大索引的可能值
int rightIndex = sourceCount - targetCount;
//2. 參數(shù)合法性檢驗(yàn)
if (fromIndex < 0) {
return -1;
}
if (fromIndex > rightIndex) {
fromIndex = rightIndex;
}
/* Empty string always matches. */
if (targetCount == 0) {
return fromIndex;
}
//3. 記錄目標(biāo)字符串最后一個字符索引處和該字符內(nèi)容
int strLastIndex = targetOffset + targetCount - 1;
char strLastChar = target[strLastIndex];
//4. 只需要遍歷到min處即可停止遍歷了,因?yàn)樵趍in前面的字符數(shù)量已經(jīng)小于目標(biāo)字符串的長度了
int min = sourceOffset + targetCount - 1;
//5. strLastChar在source中的最大索引
int i = min + fromIndex;
//這里的語法不是很常見,有點(diǎn)類似于goto,平時我們在使用時盡量不采用這種方式,這種方式容易降低代碼的可讀性,而且容易出錯.
startSearchForLastChar:
while (true) {
//6. 在有效遍歷區(qū)間內(nèi),循環(huán)查找第一個與目標(biāo)字符串最后一個字符相等的字符,如果找到,則跳出循環(huán),該字符的索引是i
while (i >= min && source[i] != strLastChar) {
i--;
}
//7. 如果已經(jīng)小于min了,那么說明沒找到,直接返回-1
if (i < min) {
return -1;
}
//8. 找到了,則再進(jìn)行查找目標(biāo)字符串除去最后一個字符剩下的子串
//從最后一個字符的前一個字符開始查找
int j = i - 1;
//9. 目標(biāo)字符串除去最后一個字符剩下的子串長度是targetCount - 1,此處start是此次剩余子串查找能到達(dá)的最小索引處
int start = j - (targetCount - 1);
//10. 記錄目標(biāo)字符串的倒數(shù)第二個字符所在target中的索引
int k = strLastIndex - 1;
//11. 循環(huán)查找剩余子串是否全部字符相同
//不相同則直接跳出繼續(xù)第6步
//全部相同則返回索引
while (j > start) {
if (source[j--] != target[k--]) {
i--;
continue startSearchForLastChar;
}
}
return start - sourceOffset + 1;
}
}
8.字符串中字符的替換
-
replace(char oldChar, char newChar)
功能:用字符newChar替換當(dāng)前字符串中所有的oldChar字符驰吓,并返回一個新的字符串.
大體思路:- 首先判斷oldChar與newChar是否相同,相同的話就沒必要進(jìn)行后面的操作了
- 從最前面開始匹配與oldChar相匹配的字符,記錄索引為i
- 如果上面的i是正常范圍內(nèi)(小于len),新建一個數(shù)組,長度為len(原來的字符串的長度),將i索引前面的字符逐一復(fù)制進(jìn)新數(shù)組里面,然后循環(huán)
i<=x<len
的字符,將字符逐一復(fù)制進(jìn)新數(shù)組,但是這次的復(fù)制有規(guī)則,即如果那個字符與oldChar相同那么新數(shù)組對應(yīng)索引處就放newChar. - 最后通過新建的數(shù)組new一個String對象返回
思考:一開始我覺得第二步好像沒什么必要性,沒有第二步其實(shí)也能實(shí)現(xiàn).但是,仔細(xì)想想,假設(shè)原字符串沒有查找到與oldChar匹配的字符,那么我們就可以規(guī)避去新建一個數(shù)組,從而節(jié)約了不必要的開銷.可以,很棒,我們就是要追求極致的性能,減少浪費(fèi)資源.
小細(xì)節(jié):源碼中有一個小細(xì)節(jié),注釋中有一句
avoid getfield opcode
,意思是避免getfield操作碼?
感覺那句代碼就是拷貝了一個引用副本啊,有什么高大上的作用?查閱文章https://blog.csdn.net/gaopu12345/article/details/52084218 后發(fā)現(xiàn)答案:在一個方法中需要大量引用實(shí)例域變量的時候涧尿,使用方法中的局部變量代替引用可以減少getfield操作的次數(shù),提高性能檬贰。
public String replace(char oldChar, char newChar) {
//1. 如果兩者相同,那么就沒必要進(jìn)行比較了
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
//2. 從最前面開始,循環(huán)遍歷,找到與oldChar相同的字符
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
//3. 如果找到了與oldChar相同的字符才進(jìn)入if
if (i < len) {
//4. 新建一個數(shù)組,用于存放新數(shù)據(jù)
char buf[] = new char[len];
//5. 將i前面的全部復(fù)制進(jìn)新數(shù)組里面去
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
//6. 在i后面的字符,我們將其一個一個地放入新數(shù)組中,當(dāng)然在放入時需要比對是否和oldChar相同,相同則存放newChar
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
//7. 最終重新new一個String
return new String(buf, true);
}
}
return this;
}
9.其他類方法
-
trim()
功能:截去字符串兩端的空格姑廉,但對于中間的空格不處理
大體實(shí)現(xiàn):記錄前面有st個空格,最后有多少個空格,那么長度就減去多少個空格,最后根據(jù)上面的這2個數(shù)據(jù)去切割字符串.
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
//1. 記錄前面有多少個空格
while ((st < len) && (val[st] <= ' ')) {
st++;
}
//2. 記錄后面有多少個空格
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
//3. 切割唄,注意:切割里面具體實(shí)現(xiàn)是重新new了一個String
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
-
startsWith(String prefix)或endsWith(String suffix)
功能:用來比較當(dāng)前字符串的起始字符或子字符串prefix和終止字符或子字符串suffix是否和當(dāng)前字符串相同,重載方法中同時還可以指定比較的開始位置offset.
思路:比較簡單,就直接看代碼了,有詳細(xì)注釋.
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
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.
//1. 入?yún)z測合法性
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
//2. 循環(huán)進(jìn)行逐個字符遍歷,有不相等的就直接返回false,遍歷完了還沒發(fā)現(xiàn)不相同的,那么就是true
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
-
contains(String str)
功能:判斷參數(shù)s是否被包含在字符串中翁涤,并返回一個布爾類型的值.
思路:其實(shí)就是利用已經(jīng)實(shí)現(xiàn)好的indexOf()去查找是否包含.源碼中對于已實(shí)現(xiàn)的東西利用率還是非常高的.我們要多學(xué)習(xí).
public boolean contains(CharSequence s) {
return indexOf(s.toString()) > -1;
}
10.基本類型轉(zhuǎn)換為字符串類型
這部分代碼一看就懂,都是一句代碼解決.
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[]) {
return new String(data);
}
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
public static String copyValueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
public static String copyValueOf(char data[]) {
return new String(data);
}
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(char c) {
char data[] = {c};
return new String(data, true);
}
public static String valueOf(int i) {
return Integer.toString(i);
}
public static String valueOf(long l) {
return Long.toString(l);
}
public static String valueOf(float f) {
return Float.toString(f);
}
public static String valueOf(double d) {
return Double.toString(d);
}
注意事項(xiàng)
最后注意一下:Android 6.0(23) 源碼中桥言,String類的實(shí)現(xiàn)被替換了,具體調(diào)用的時候葵礼,會調(diào)用一個StringFactory來生成一個String.
來看下Android源碼中String,,我擦,,這.....直接拋錯誤UnsupportedOperationException
,可能是因?yàn)镺racle告Google的原因吧..
public String() {
throw new UnsupportedOperationException("Use StringFactory instead.");
}
public String(String original) {
throw new UnsupportedOperationException("Use StringFactory instead.");
}
我們平時開發(fā)APP時都是使用的java.lang包下面的String,上面的問題一般不會遇到,但是作為Android開發(fā)者還是要了解一下.
三号阿、AbstractStringBuilder源碼分析
先看看類StringBuffer和StringBuilder的繼承結(jié)構(gòu)
可以看到StringBuffer和StringBuilder都是繼承了AbstractStringBuilder.所以這里先分析一下AbstractStringBuilder.
在這基類里面真實(shí)的保存了StringBuffer和StringBuilder操作的實(shí)際數(shù)據(jù)內(nèi)容,數(shù)據(jù)內(nèi)容其實(shí)是一個char[] value;
數(shù)組,在其構(gòu)造方法中其實(shí)就是初始化該字符數(shù)組.
char[] value;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
1.擴(kuò)容
既然數(shù)據(jù)內(nèi)容(上面的value數(shù)組)是在AbstractStringBuilder里面的,那么很多操作我覺得應(yīng)該也是在父類里面,比如擴(kuò)容,下面我們看看源碼
public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}
/**
* 確保value字符數(shù)組不會越界.重新new一個數(shù)組,引用指向value
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
/**
* 擴(kuò)容:之前的大小的2倍+2
*/
private int newCapacity(int minCapacity) {
// overflow-conscious code 擴(kuò)大2倍+2
//小知識點(diǎn):這里可能會溢出,溢出后是負(fù)數(shù)哈,注意
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
//MAX_ARRAY_SIZE的值是Integer.MAX_VALUE - 8,先判斷一下預(yù)期容量(newCapacity)是否在0<x<MAX_ARRAY_SIZE之間,在這區(qū)間內(nèi)就直接將數(shù)值返回,不在這區(qū)間就去判斷一下是否溢出
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
/**
* 判斷大小 是否溢出
*/
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
可以看到這里的擴(kuò)容方式是 = 以前的大小*2+2,其他的細(xì)節(jié)方法中已給出詳細(xì)注釋.
2.追加
舉一個比較有代表性的添加,詳細(xì)注釋在代碼中
/**
* 追加:從指定字符串的片段
*/
public AbstractStringBuilder append(CharSequence s, int start, int end) {
//1. 如果是空,則添加字符串"null"
if (s == null)
s = "null";
//2. 判斷是否越界
if ((start < 0) || (start > end) || (end > s.length()))
throw new IndexOutOfBoundsException(
"start " + start + ", end " + end + ", s.length() "
+ s.length());
//3. 記錄添加字符串長度
int len = end - start;
//4. 判斷一下 當(dāng)前數(shù)組長度+需要添加的字符串長度 是否夠裝,不夠裝就擴(kuò)容(擴(kuò)容時還有復(fù)制原內(nèi)容到新數(shù)組中)
ensureCapacityInternal(count + len);
//5. 追加內(nèi)容到value數(shù)組最后
for (int i = start, j = count; i < end; i++, j++)
value[j] = s.charAt(i);
//6. 更新數(shù)組長度
count += len;
return this;
}
3.增加
這里的大體思想是和以前大一的時候用C語言在數(shù)組中插入數(shù)據(jù)是一樣的.
這里假設(shè)需要插入的字符串s,插入在目標(biāo)字符串desOffset處,插入的長度是len.首先將需要插入處的desOffset~desOffset+len往后挪,挪到desOffset+len處,然后在desOffset處插入目標(biāo)字符串.
大體思想就是這樣,是不是覺得很熟悉?? ヽ( ̄▽ ̄)?
下面這個方法是上面思路的具體實(shí)現(xiàn),詳細(xì)的邏輯分析已經(jīng)放到代碼注釋中.
//插入字符串,從dstOffset索引處開始插入,插入內(nèi)容為s中的[start,end]字符串
public AbstractStringBuilder insert(int dstOffset, CharSequence s,
int start, int end) {
//1. 空處理
if (s == null)
s = "null";
//2. 越界判斷
if ((dstOffset < 0) || (dstOffset > this.length()))
throw new IndexOutOfBoundsException("dstOffset "+dstOffset);
//3. 入?yún)z測是否合法
if ((start < 0) || (end < 0) || (start > end) || (end > s.length()))
throw new IndexOutOfBoundsException(
"start " + start + ", end " + end + ", s.length() "
+ s.length());
//4. 長度記錄
int len = end - start;
//5. 判斷一下 當(dāng)前數(shù)組長度+需要添加的字符串長度 是否夠裝,不夠裝就擴(kuò)容(擴(kuò)容時還有復(fù)制原內(nèi)容到新數(shù)組中)
ensureCapacityInternal(count + len);
//6. 將原數(shù)組中dstOffset開始的count - dstOffset個字符復(fù)制到dstOffset + len處,,,,這里其實(shí)就是騰出一個len長度的區(qū)間,用用戶存放目標(biāo)字符串,這個區(qū)間就是dstOffset到dstOffset + len
System.arraycopy(value, dstOffset, value, dstOffset + len,
count - dstOffset);
//7. 存放目標(biāo)字符串
for (int i=start; i<end; i++)
value[dstOffset++] = s.charAt(i);
//8. 記錄字符串長度
count += len;
//9. 返回自身引用 方便鏈?zhǔn)秸{(diào)用
return this;
}
4.刪除
源碼里面的刪除操作實(shí)際上是復(fù)制,比如下面這個方法刪除start到end之間的字符,實(shí)際是將以end開始的字符復(fù)制到start處,并且將數(shù)組的長度記錄count減去len個
//刪除從start到end索引區(qū)間( [start,end)前閉后開區(qū)間 )內(nèi)內(nèi)容
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;
//當(dāng)start==end時不會改變
if (len > 0) {
//將value數(shù)組的start+len位置開始的count-end個字符復(fù)制到value數(shù)組的start位置處. 注意,并且將數(shù)組count減去len個.
System.arraycopy(value, start+len, value, start, count-end);
count -= len;
}
return this;
}
5.切割
我擦,,,,原來StringBuffer的切割效率并不高嘛,其實(shí)就是new了一個String....
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);
}
6.改
改其實(shí)就是對其替換,而在源碼中替換最終的實(shí)現(xiàn)其實(shí)是復(fù)制(還是復(fù)制..( ̄▽ ̄)~*
).
大體思路: 假設(shè)需要將字符串str替換value數(shù)組中的start-end中,這時只需將end后面的字符往后移動,在中間騰出一個坑,用于存放需要替換的str字符串.最后將str放到value數(shù)組中start索引處.
public AbstractStringBuilder replace(int start, int end, String str) {
//1. 入?yún)z測合法性
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (start > count)
throw new StringIndexOutOfBoundsException("start > length()");
if (start > end)
throw new StringIndexOutOfBoundsException("start > end");
if (end > count)
end = count;
//2. 目標(biāo)String長度
int len = str.length();
//3. 計(jì)算新的數(shù)組的長度
int newCount = count + len - (end - start);
//4. 判斷一下是否需要擴(kuò)容
ensureCapacityInternal(newCount);
//5. 將value數(shù)組的end位置開始的count - end個字符復(fù)制到value數(shù)組的start+len處. 相當(dāng)于把end之后的字符移到最后去,然后中間留個坑,用來存放str(需要替換成的值)
System.arraycopy(value, end, value, start + len, count - end);
//6. 這是String的一個方法,用于將str復(fù)制到value中start處 其最底層實(shí)現(xiàn)是native方法(getCharsNoCheck() )
str.getChars(value, start);
//7. 更新count
count = newCount;
return this;
}
7.查詢
查詢是最簡單的,就是返回數(shù)組中相應(yīng)索引處的值.
public char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
四、StringBuffer源碼分析
定義
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
StringBuffer和StringBuilder都是相同的繼承結(jié)構(gòu).都是繼承了AbstractStringBuilder.
StringBuffer和StringBuilder構(gòu)造方法,可以看到默認(rèn)大小是16,
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
1. 我們先來看看StringBuffer的append方法
啥,不就是調(diào)用父類的append方法嘛..
但是,請 注意:前面說了StringBuffer是線程安全的,為什么,源碼里面使用了synchronized給方法加鎖了.
public synchronized StringBuffer append(boolean b) {
toStringCache = null;
super.append(b);
return this;
}
@Override
public synchronized StringBuffer append(char c) {
toStringCache = null;
super.append(c);
return this;
}
@Override
public synchronized StringBuffer append(int i) {
toStringCache = null;
super.append(i);
return this;
}
2.StringBuffer的其他方法
幾乎都是所有方法都加了synchronized,幾乎都是調(diào)用的父類的方法.
public synchronized StringBuffer delete(int start, int end) {
toStringCache = null;
super.delete(start, end);
return this;
}
public synchronized StringBuffer replace(int start, int end, String str) {
toStringCache = null;
super.replace(start, end, str);
return this;
}
public synchronized int indexOf(String str, int fromIndex) {
return super.indexOf(str, fromIndex);
}
...
五鸳粉、StringBuilder分析
定義
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
1. 我們先來看看StringBuilder的append方法
啥,還是調(diào)用父類的append方法嘛..
但是,請 注意:前面說了StringBuilder不是線程安全的,為什么,源碼里面沒有使用synchronized進(jìn)行加鎖.
public StringBuilder append(boolean b) {
super.append(b);
return this;
}
@Override
public StringBuilder append(char c) {
super.append(c);
return this;
}
@Override
public StringBuilder append(int i) {
super.append(i);
return this;
}
2.StringBuilder的其他方法
也是全部調(diào)用的父類方法. 但是是沒有加鎖的.
public StringBuilder delete(int start, int end) {
super.delete(start, end);
return this;
}
public StringBuilder replace(int start, int end, String str) {
super.replace(start, end, str);
return this;
}
public int indexOf(String str) {
return super.indexOf(str);
}
...
六扔涧、小細(xì)節(jié)
String的hashCode()中使用了一個魔法數(shù)字 31 可以去學(xué)習(xí)一下為什么使用31,http://www.cnblogs.com/nullllun/p/8350178.html
String定義的char數(shù)組 是空白final
七、總結(jié)
- String,StringBuffer,StringBuilder最終底層存儲與操作的都是char數(shù)組.但是String里面的char數(shù)組是final的,而StringBuffer,StringBuilder不是,也就是說,String是不可變的,想要新的字符串只能重新生成String.而StringBuffer和StringBuilder只需要修改底層的char數(shù)組就行.相對來說,開銷要小很多.
- String的大多數(shù)方法都是重新new一個新String對象返回,頻繁重新生成容易生成很多垃圾.
- 還是那句古話,StringBuffer是線程安全的,StringBuilder是線程不安全的.因?yàn)镾tringBuffer的方法是加了synchronized鎖起來了的,而StringBuilder沒有.
- 增刪比較多時用StringBuffer或StringBuilder(注意單線程與多線程)赁严。實(shí)際情況按需而取吧扰柠,既然已經(jīng)知道了里面的原理粉铐。
學(xué)習(xí)源碼我們能從中收獲什么
Java的源碼都是經(jīng)過上千萬(我亂說的..哈哈)的程序員校驗(yàn)過的,不管是算法、命名卤档、doc文檔蝙泼、寫作風(fēng)格等等都非常規(guī)范,值得我們借鑒與深思劝枣。還有很多很多的小技巧汤踏。
下次在使用時能按需而取,追求性能舔腾。
避免項(xiàng)目中的很多錯誤的發(fā)生溪胶。