?????上篇介紹了String類的構(gòu)造器,獲取內(nèi)部屬性等方法,最后留下了最常用的局部操作函數(shù)沒(méi)有介紹,本篇將接著上篇內(nèi)容,從這些最常見(jiàn)的函數(shù)的操作說(shuō)起,看看我們?nèi)粘=?jīng)常使用的這些方法的內(nèi)部是怎么實(shí)現(xiàn)的。第一個(gè)函數(shù):
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;
}
該方法用于判斷是否當(dāng)前的字符串對(duì)象是以指定的子串開頭。prefix參數(shù)指定了這個(gè)字串,toffset參數(shù)指定了要從原字符串的哪里開始查找。先看個(gè)例子:
public static void main(String[] args){
String str = "hello-walker";
System.out.println(str.startsWith("wa", 0));
System.out.println(str.startsWith("wa",6));
}
結(jié)果如下:
源代碼相對(duì)而言也是比較容易理解的,首先是做了個(gè)簡(jiǎn)單的判斷,如果toffset小于0或者toffset和prefix的長(zhǎng)度超過(guò)了原字符串的長(zhǎng)度,直接返回false。接著通過(guò)了一個(gè)while循環(huán)從原字符串的toffset位置和prefix的0位置開始,一個(gè)字符一個(gè)字符的比較,一旦發(fā)現(xiàn)有兩者在某個(gè)位置的字符值是不等的,返回false,否則在循環(huán)結(jié)束時(shí)返回true。該方法還有一個(gè)重載,該重載默認(rèn)toffset為0,即從原字符串的開頭開始搜索。
endWith這個(gè)方法其實(shí)內(nèi)部調(diào)用的還是上述介紹的startWith方法。
public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}
我們看到該方法內(nèi)部調(diào)用的startsWith方法,第二個(gè)參數(shù)傳入的是value.length - suffix.value.length,該參數(shù)將會(huì)導(dǎo)致程序跳過(guò)前面一部分的字符,直接跳到還剩下suffix.value.length的字符的位置處。
下面我們看看hashCode在String類中的的實(shí)現(xiàn):
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
雖然知道是這么實(shí)現(xiàn)的,但是我不知道為什么這么做。只知道它每次都乘31然后加上當(dāng)前字符的Unicode編號(hào)。下面看一個(gè)重要的方法:
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
return -1;
}
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
final char[] value = this.value;
for (int i = fromIndex; i < max; i++) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
return indexOfSupplementary(ch, fromIndex);
}
}
indexOf方法用于返回某個(gè)字符首次出現(xiàn)的位置,當(dāng)然對(duì)應(yīng)的還有l(wèi)astIndexOf,我們一點(diǎn)點(diǎn)看。上述的方法,兩個(gè)參數(shù),第一個(gè)參數(shù)的值表示需要查找的指定字符(我們知道字符和int型是可以無(wú)條件互轉(zhuǎn)的,所以這里用int接收),后面的代碼主要分為兩部分,一部分是大部分情況,另一部分則是專門用于處理增補(bǔ)字集情況,該情況我們暫時(shí)不去研究。第一部分的代碼就比較簡(jiǎn)單了,遍歷整個(gè)字符串對(duì)象,如果找到指定字符,則返回當(dāng)前位置,否則返回-1。當(dāng)然該方法也有一些重載,但本質(zhì)都是調(diào)用了上述介紹的方法。
lastIndexOf方法類似,只不過(guò)他是從后往前查找,此處不再贅述。
下面看一個(gè)截取子串的方法:
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
該方法的前面兩個(gè)判斷主要用于處理一些極端情況,最后一條語(yǔ)句是該方法的核心。如果beginIndex 為0表示截取整個(gè)字符串則直接返回當(dāng)前字符串對(duì)象,否則重新構(gòu)造一個(gè)字符串對(duì)象。當(dāng)然該方法自然是有重載的,
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
從該重載的兩個(gè)參數(shù)可以看出來(lái),之前只提供一個(gè)beginIndex則默認(rèn)從開始索引處全部截取余下字符。而此處指定endIndex則選擇性的截取從beginIndex到endIndex之間的子串作為結(jié)果返回。具體的實(shí)現(xiàn)也是類似,只是多了一些判斷。
下面介紹的方法可以連接兩個(gè)不同的字符串。
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
該方法具有一個(gè)參數(shù),該參數(shù)的值是一個(gè)字符串對(duì)象,用于連接在當(dāng)前字符串對(duì)象的后面。前三行很簡(jiǎn)單,就是判斷連接字符串str是否為空,如果是則直接返回當(dāng)前字符串對(duì)象,我們看到很多的方法源碼都是會(huì)把核心方法放在最后面,前面是一堆判斷,這也是一種效率的體現(xiàn),就是說(shuō)如果不滿足調(diào)用該方法的條件則直接在前面被pass了,而不用調(diào)用復(fù)雜耗時(shí)的核心方法。Arrays.copyOf 方法用于創(chuàng)建一個(gè)能夠容納上述兩個(gè)字符串的更大的數(shù)組,然后將原字符串復(fù)制到進(jìn)去,后面留給str的位置為空。接著調(diào)用getChars方法從偏移量為len的索引位置開始將str中字符拷貝到buf中,最后構(gòu)建字符串對(duì)象返回。
下面看一個(gè)更為實(shí)用的方法:
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;
}
該方法用于替換字符串對(duì)象中指定的某個(gè)字符,當(dāng)然它會(huì)替換掉所有的oldchar。該方法首先判斷oldchar(需要被替換的字符)是否和newchar(替換它的字符)相等,如果相等則不用做任何操作,直接返回當(dāng)前字符串對(duì)象,否則,通過(guò)while循環(huán)找到第一個(gè)oldchar,然后重新構(gòu)建了一個(gè)char數(shù)組,該數(shù)組和value這個(gè)數(shù)組長(zhǎng)度一樣,接著將第一個(gè)oldchar位置之前的所有字符復(fù)制到新數(shù)組中,然后while循環(huán)一邊遍歷value數(shù)組查找oldchar并替換為newchar,一邊將newchar添加到新數(shù)組中,最后返回新數(shù)組構(gòu)造的String 對(duì)象。
上述的該方法只能替換指定的一個(gè)字符,但是不能替換某個(gè)子串。下面的幾個(gè)方法都是用于替換某個(gè)子串。
@1替換第一個(gè)子串
public String replaceFirst(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}
@2替換每一個(gè)符合規(guī)則的子串
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
@3
public String replace(CharSequence target, CharSequence replacement) {
return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}
上述的第一個(gè)方法是相對(duì)較為好理解,第二個(gè)和第三個(gè)方法都是替換所有指定的子串,他們的區(qū)別在于,replaceAll方法是基于正則表達(dá)式的,replace則只針對(duì)char串的替換。例如:
public static void main(String[] args){
String str = "aaabssddaa\\\\";
System.out.println(str);
System.out.println(str.replace("\\\\", "x"));
System.out.println(str.replaceAll("\\\\", "x"));
}
輸出結(jié)果:
我們知道在Java中 \ 表示轉(zhuǎn)義字符,也就是上述的str中 \\ 將被轉(zhuǎn)義成兩個(gè) \ ,而在正則表達(dá)式中該符號(hào)也是轉(zhuǎn)義字符,所以我們 replaceAll 方法中的第一個(gè)參數(shù)的實(shí)際值為:\,被轉(zhuǎn)義了兩次,所以針對(duì)str中的 \的替換,replaceAll 輸出兩個(gè)x,而在replace方法中,四個(gè)\被Java轉(zhuǎn)義了一次為兩個(gè)\,所以replace輸出一個(gè)x。它兩區(qū)別就是一個(gè)是基于正則表達(dá)式的,一個(gè)則只針對(duì)char子串。
下面看一個(gè)分割字符串的函數(shù)split,由于代碼比較多,此處就不貼出來(lái)了,我大致介紹下實(shí)現(xiàn)原理。該方法的參數(shù)依然是依賴正則表達(dá)式的,其內(nèi)部定義了一個(gè)ArrayList,定義一個(gè)用于匹配字符串的Matcher對(duì)象,然后while循環(huán)去find原字符串對(duì)象,如果找到則直接subSequence前面的所有字符集合,并添加到ArrayList中,然后起始位置從0跳到當(dāng)前位置之后繼續(xù)搜索,最后ArrayList對(duì)象的toArray方法,返回String類型數(shù)組。
下面看一個(gè)join方法:
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// Number of elements not likely worth Arrays.stream overhead.
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
首先,該方法是靜態(tài)方法。然后該方法中涉及到一個(gè)類StringJoiner ,它有一個(gè)構(gòu)造方法:
public StringJoiner(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
Objects.requireNonNull(prefix, "The prefix must not be null");
Objects.requireNonNull(delimiter, "The delimiter must not be null");
Objects.requireNonNull(suffix, "The suffix must not be null");
// make defensive copies of arguments
this.prefix = prefix.toString();
this.delimiter = delimiter.toString();
this.suffix = suffix.toString();
this.emptyValue = this.prefix + this.suffix;
}
該構(gòu)造函數(shù)為該類的一些字段賦值,至于這些字段時(shí)干什么的,等再次遇到的時(shí)候介紹,此處只需了解下他們的存在。此處調(diào)用該構(gòu)造函數(shù)并傳入delimiter分割符,然后調(diào)用了該類對(duì)象的add方法,
public StringJoiner add(CharSequence newElement) {
prepareBuilder().append(newElement);
return this;
}
private StringBuilder prepareBuilder() {
//此處value為一個(gè)StringBilder實(shí)例,是StringJoiner的一個(gè)成員
if (value != null) {
value.append(delimiter);
} else {
value = new StringBuilder().append(prefix);
}
return value;
}
第一次add會(huì)走else部分,新建一個(gè)StringBuilder對(duì)象并添加prefix元素(此處在調(diào)用構(gòu)造器的時(shí)候?yàn)槠滟x值為空)賦值給我們的成員變量,回到add方法添加該元素到StringBuilder中,第二次到prepareBuilder方法中只會(huì)向StringBuilder實(shí)例中添加delimiter分割符,然后出來(lái)add方法中又將第二個(gè)元素添加到其中。這樣就完成了為這些元素連接一個(gè)分隔符,并放入到StringBuilder實(shí)例中,最后tostring返回??磦€(gè)例子:
public static void main(String[] args){
String[] strs = new String[]{"hello","walker","yam","cyy","huaaa"};
System.out.println(String.join("-",strs));
}
輸出結(jié)果:hello-walker-yam-cyy-huaaa
最后還有兩個(gè)方法,比較簡(jiǎn)單不再贅述其原理實(shí)現(xiàn)。
//返回內(nèi)部的字符數(shù)組,之所以不直接返回value是為了封裝的嚴(yán)密性
public char[] toCharArray() {
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
}
//去除頭尾部的空格
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
至此,有關(guān)String源碼的閱讀大致結(jié)束,并沒(méi)有涉及全部代碼,有些源碼束作者能力問(wèn)題,沒(méi)能完全參透,總結(jié)的不好,見(jiàn)諒!