深入理解Java常用類----String

?????Java中字符串的操作可謂是最常見的操作了表悬,String這個(gè)類它封裝了有關(guān)字符串操作的大部分方法,從構(gòu)建一個(gè)字符串對象到對字符串的各種操作都封裝在該類中溅固,本篇我們通過閱讀String類的源碼來深入理解下這些字符串操作背后的原理名秀。主要內(nèi)容如下:

  • 繁雜的構(gòu)造器
  • 屬性狀態(tài)的常用函數(shù)
  • 獲取內(nèi)部數(shù)值的常用函數(shù)
  • 比較大小的相關(guān)函數(shù)
  • 局部操作等常用函數(shù)

一、繁雜的構(gòu)造器
?????在學(xué)會操作字符串之前温亲,我們應(yīng)先了解下構(gòu)造一個(gè)字符串對象的方式有幾種棚壁。先看第一種構(gòu)造器:

private final char value[];

public String() {
   this.value = "".value;
}

String源碼中第一個(gè)私有域就是value這個(gè)字符數(shù)組,該數(shù)組被聲明為final表示一旦初始化就不能被改變栈虚。也就是說一個(gè)字符串對象實(shí)際上是由一個(gè)字符數(shù)組組成的袖外,并且該數(shù)組一旦被初始化則不能更改。這也很好的解釋了String對象的一個(gè)特性:不可變性魂务。一經(jīng)賦值則不能改變曼验。而我們第一種構(gòu)造器就很簡單,該構(gòu)造器會將當(dāng)前的string對象賦值為空(非null)粘姜。

接下來的幾種構(gòu)造器都很簡單鬓照,實(shí)際上都是操作了value這個(gè)數(shù)組,但都不是直接操作孤紧,因?yàn)樗豢筛牟蝰桑砸话愣际菑?fù)制到局部來實(shí)現(xiàn)的各種操作。

//1
public String(String original) {
     this.value = original.value;
     this.hash = original.hash;
}
//2
public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
//3
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;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

無論是第一種的傳入一個(gè)String類型号显,還是第二種的直接傳入char數(shù)組的方式臭猜,都是轉(zhuǎn)換為為當(dāng)前將要創(chuàng)建的對象中value數(shù)組屬性賦值。至于第三種方法押蚤,對傳入的char數(shù)組有要求蔑歌,它要求從該數(shù)組索引位置為offset開始的后count個(gè)字符組成新的數(shù)組作為參數(shù)傳入。該方法首先做了幾個(gè)極端的判斷并增設(shè)了對應(yīng)的異常拋出揽碘,核心方法是Arrays.copyOfRange這個(gè)方法次屠,它才是真正實(shí)現(xiàn)字符數(shù)組拷貝的方法。

該方法傳入三個(gè)參數(shù)钾菊,形參value帅矗,起始位置索引偎肃,終止位置索引煞烫。在該方法中主要做了兩件事情,第一累颂,通過起始位置和終止位置得到新數(shù)組的長度滞详,第二凛俱,調(diào)用本地函數(shù)完成數(shù)組拷貝。

System.arraycopy(original, from, copy, 0,Math.min(original.length - from, newLength));

雖然該方法是本地方法料饥,但是我們大致可以猜出他是如何實(shí)現(xiàn)的蒲犬,無非是通過while或者for循環(huán)遍歷前者賦值后者。我們看個(gè)例子:

    public static void main(String[] args){
        char[]  chs = new char[]{'w','a','l','k','e','r'};
        String s = new String(chs,0,3);
        System.out.println(s);
    }
輸出結(jié)果:wal

可以看見這是一種[ a,b)形式岸啡,也就是說索引包括起始位置原叮,但不包括終止位置,所以上例中只截取了索引為0巡蘸,1奋隶,2并沒有包括3,這種形式的截取方式在String的其他函數(shù)中也是常見的悦荒。

以上介紹的構(gòu)建String對象的方式中唯欣,基本都是屬于操作它內(nèi)部的字符數(shù)組來實(shí)現(xiàn)的,下面的幾種構(gòu)造器則是通過操作字節(jié)數(shù)組來實(shí)現(xiàn)對字符串對象的構(gòu)建搬味,當(dāng)然這些操作會涉及到編碼的問題境氢。下面我們看第一個(gè)有關(guān)字節(jié)數(shù)組的構(gòu)造器:

    public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(charsetName, bytes, offset, length);
    }

該方法首先保證charsetName不為null,然后調(diào)用checkBounds方法判斷offset碰纬、length是否小于0萍聊,以及offset+length是否大于bytes.length。然后調(diào)用一個(gè)核心的方法用于將字節(jié)數(shù)組按照指定的編碼方式解析成char數(shù)組嘀趟,我們可以看看這個(gè)方法:

    static char[] decode(String charsetName, byte[] ba, int off, int len)
        throws UnsupportedEncodingException
    {
        StringDecoder sd = deref(decoder);
        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
        if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
                              || csn.equals(sd.charsetName()))) {
            sd = null;
            try {
                Charset cs = lookupCharset(csn);
                if (cs != null)
                    sd = new StringDecoder(cs, csn);
            } catch (IllegalCharsetNameException x) {}
            if (sd == null)
                throw new UnsupportedEncodingException(csn);
            set(decoder, sd);
        }
        return sd.decode(ba, off, len);
    }

首先通過deref方法獲取對本地解碼器類的一個(gè)引用脐区,接著使用三目表達(dá)式獲取指定的編碼標(biāo)準(zhǔn),如果未指定編碼標(biāo)準(zhǔn)則默認(rèn)為 ISO-8859-1她按,然后緊接著的判斷主要是:如果未能從本地線程相關(guān)類中獲取到StringDecoder牛隅,或者與指定的編碼標(biāo)準(zhǔn)不符,則手動創(chuàng)建一個(gè)StringDecoder實(shí)例對象酌泰。最后調(diào)用一個(gè)decode方法完成譯碼的工作媒佣。相比于該方法,我們更常用以下這個(gè)方法來將一個(gè)字節(jié)數(shù)組轉(zhuǎn)換成char數(shù)組陵刹。

    public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException {
        this(bytes, 0, bytes.length, charsetName);
    }

只指定一個(gè)字節(jié)數(shù)組和一個(gè)編碼標(biāo)準(zhǔn)即可默伍,當(dāng)然內(nèi)部調(diào)用的還是我們上述的那個(gè)構(gòu)造器。當(dāng)然也可以不指定任何編碼標(biāo)準(zhǔn)衰琐,那么則會使用默認(rèn)的編碼標(biāo)準(zhǔn):UTF-8

public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
    }

當(dāng)然還可以更簡潔:

    public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }

但是一般用于轉(zhuǎn)換字節(jié)數(shù)組成字符串的構(gòu)造器還是使用由字節(jié)數(shù)組和編碼標(biāo)準(zhǔn)組成的兩個(gè)參數(shù)的構(gòu)造器也糊。

以上為String類中大部分構(gòu)造器的源代碼,有些源碼和底層操作系統(tǒng)等方面知識相關(guān)聯(lián)羡宙,理解不深狸剃,見諒。下面我們看看有關(guān)String類的其他一些有關(guān)操作狗热。

二钞馁、屬性狀態(tài)的常用函數(shù)
?????該分類的幾個(gè)函數(shù)還是相對而言較為簡單的虑省,主要有以下幾個(gè)函數(shù):

//返回字符串的長度
public int length() {
        return value.length;
}
//判斷字符串是否為空
public boolean isEmpty() {
        return value.length == 0;
    }
//獲取字符串中指定位置的單個(gè)字符
public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
}

有關(guān)字符串屬性的函數(shù)大致就這么些,相對而言比較簡單僧凰,下面看看獲取內(nèi)部數(shù)值的常用函數(shù)探颈。

三、獲取內(nèi)部數(shù)值的常用函數(shù)
?????此分類下的函數(shù)主要有兩大類训措,一個(gè)是返回的字符數(shù)組伪节,一個(gè)是返回的字節(jié)數(shù)組。我們首先看返回字符數(shù)組的方法绩鸣。

    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

該函數(shù)用于將當(dāng)前String對象中value字符數(shù)組的起始索引位置srcBegin到終止索引位置srcEnd拷貝到目標(biāo)數(shù)組dst中架馋,其中dst數(shù)組的起始位置為dstBegin索引處∪疲看個(gè)例子:

    public static void main(String[] args){
        String str = "hello-walker";
        char[] chs = new char[6];
        str.getChars(0,5,chs,1);
        for(int a=0;a<chs.length;a++){
            System.out.println(chs[a]);
        }
    }

結(jié)果如下:

這里寫圖片描述

我們指定從str 的[0叉寂,5)共五個(gè)字符組成一個(gè)數(shù)組,從chs數(shù)組索引為1開始总珠,一個(gè)個(gè)復(fù)制到chs里屏鳍。有關(guān)獲取獲取字符數(shù)組的函數(shù)就這么一個(gè),下面我們看看獲取字節(jié)數(shù)組的函數(shù)局服。

public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
        if (charsetName == null) throw new NullPointerException();
        return StringCoding.encode(charsetName, value, 0, value.length);
    }

這個(gè)函數(shù)的核心方法钓瞭,StringCoding.encode和上述的StringCoding.decode很相似,只不過一個(gè)提供編碼標(biāo)準(zhǔn)是為了解碼成字符串對象淫奔,而另一個(gè)則是提供編碼標(biāo)準(zhǔn)為了將字符串編碼成字節(jié)數(shù)組山涡。有關(guān)getBytes還有一些重載,但這些重載基本每個(gè)都會調(diào)用我們上述列出的這個(gè)方法唆迁,只是他們省略了一些參數(shù)(使用他們的默認(rèn)值)鸭丛。

四、判等函數(shù)
?????在我們?nèi)粘5捻?xiàng)目中可能經(jīng)常會遇到equls這個(gè)函數(shù)唐责,那么這個(gè)函數(shù)是否又是和符號 == 具有相同的功能呢鳞溉?下面我們看看判等函數(shù):

    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;
    }

我們看到該方法中,第一個(gè)判斷就使用了符號 == 鼠哥,實(shí)際上等于符號判斷的是:兩個(gè)對象是否指向同一內(nèi)存空間地址(當(dāng)然如果他們是指向同一內(nèi)存的熟菲,他們內(nèi)部封裝的數(shù)值自然也是相等的)。 從上述代碼中我們可以看出朴恳,這個(gè)equals方法抄罕,首先判斷兩個(gè)對象是否指向同一內(nèi)存位置,如果是則返回true于颖,如果不是才判斷他們內(nèi)部封裝的數(shù)組是否是相等的呆贿。

public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
    }

該方法是忽略大小寫的判等方法,核心方法是regionMatches:

    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;
        
        if ((ooffset < 0) || (toffset < 0)
                || (toffset > (long)value.length - len)
                || (ooffset > (long)other.value.length - len)) {
            return false;
        }
        while (len-- > 0) {
            char c1 = ta[to++];
            char c2 = pa[po++];
            if (c1 == c2) {
                continue;
            }
            if (ignoreCase) {
                char u1 = Character.toUpperCase(c1);
                char u2 = Character.toUpperCase(c2);
                if (u1 == u2) {
                    continue;
                }
                if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                    continue;
                }
            }
            return false;
        }
        return true;
    }

首先是檢錯(cuò)判斷恍飘,簡單判斷下傳入的參數(shù)是否小于0等榨崩,然后通過不斷讀取兩個(gè)字符數(shù)組的字符比較是否相等,如果相等則直接跳過余下代碼進(jìn)入下次循環(huán)章母,否則分別將這兩個(gè)字符轉(zhuǎn)換為小寫和大寫兩種形式進(jìn)行比較母蛛,如果相等,依然返回true乳怎。equals方法只能判斷兩者是否相等彩郊,但是對于誰大誰小則無能為力。 下面我們看看compare相關(guān)方法蚪缀,它可以表兩者大小秫逝。

public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

該方法將根據(jù)字典順序,判斷出兩者大小询枚,代碼比較簡單违帆,不再贅述。忽略大小寫的按字典順序排類似金蜀,主要涉及以下方法:

public int compareToIgnoreCase(String str) {
        return CASE_INSENSITIVE_ORDER.compare(this, str);
}

這里的compare方法是CASE_INSENSITIVE_ORDER類的一個(gè)內(nèi)部類刷后。

為了不讓文章篇幅過長,本篇暫時(shí)結(jié)束渊抄,下篇將介紹最常見的一些有關(guān)字符串操作的函數(shù)源碼尝胆,總結(jié)的不好,望海涵护桦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末含衔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子二庵,更是在濱河造成了極大的恐慌贪染,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件催享,死亡現(xiàn)場離奇詭異抑进,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)睡陪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門寺渗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兰迫,你說我怎么就攤上這事信殊。” “怎么了汁果?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵涡拘,是天一觀的道長。 經(jīng)常有香客問我据德,道長鳄乏,這世上最難降的妖魔是什么跷车? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮橱野,結(jié)果婚禮上朽缴,老公的妹妹穿的比我還像新娘。我一直安慰自己水援,他們只是感情好密强,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜗元,像睡著了一般或渤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奕扣,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天薪鹦,我揣著相機(jī)與錄音,去河邊找鬼惯豆。 笑死距芬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的循帐。 我是一名探鬼主播框仔,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拄养!你這毒婦竟也來了离斩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤瘪匿,失蹤者是張志新(化名)和其女友劉穎跛梗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棋弥,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡核偿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了顽染。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漾岳。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖粉寞,靈堂內(nèi)的尸體忽然破棺而出尼荆,到底是詐尸還是另有隱情,我是刑警寧澤唧垦,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布捅儒,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏巧还。R本人自食惡果不足惜鞭莽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望麸祷。 院中可真熱鬧澎怒,春花似錦、人聲如沸摇锋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荸恕。三九已至,卻和暖如春死相,著一層夾襖步出監(jiān)牢的瞬間融求,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工算撮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留生宛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓肮柜,卻偏偏與公主長得像陷舅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子审洞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容