深入理解Java常用類----String(二)

?????上篇介紹了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)諒!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第5章 引用類型(返回首頁(yè)) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,692評(píng)論 0 4
  • 曾經(jīng)看過(guò)這樣一位媽媽: 面對(duì)大哭不止的孩子時(shí),突然大吼一聲:“你給我憋回去?!焙⒆颖粐樀每蘼曅×艘稽c(diǎn),但仍然沒(méi)有停...
    童說(shuō)家閱讀 2,481評(píng)論 0 2
  • 其實(shí)我并不羨慕因各種節(jié)日在朋友圈曬禮物秀恩愛(ài)的甜蜜伴侶,比起節(jié)日喧囂后的冷清,我更在意平淡里的小幸福。 和老公...
    未央koreyoshi閱讀 782評(píng)論 0 0
  • 當(dāng)我看到你這個(gè)案例的時(shí)候,我想起了曾經(jīng)看過(guò)的美國(guó)的一些研究和一些書籍,其中有談到一個(gè)令我印象特別深刻的觀點(diǎn),就是底...
    卷幃望月閱讀 420評(píng)論 0 0
  • iOS7~下需要現(xiàn)在info.plist下添加View controller-based status bar a...
    蘇渝粵閱讀 3,067評(píng)論 0 2

友情鏈接更多精彩內(nèi)容