字符串——讀《編寫(xiě)高質(zhì)量代碼:改善Java程序的151個(gè)建議》(四)

讀書(shū)饭弓,收獲双饥,分享
建議后面的五角星僅代表筆者個(gè)人需要注意的程度。
Talk is cheap.Show me the code

建議52:推薦使用String直接量賦值★☆☆☆☆

示例代碼:

public class Client {

    public static void main(String[] args) {
        String s1 = "謹(jǐn)以書(shū)為馬";
        String s2 = "謹(jǐn)以書(shū)為馬";
        System.out.println(s1 == s2);
        //運(yùn)行結(jié)果:true
        String s3 = new String("謹(jǐn)以書(shū)為馬");
        System.out.println(s1 == s3);
        //運(yùn)行結(jié)果:false
    }
}

兩種賦值的區(qū)別:

  • 直接賦值

    因?yàn)镾tring字符串是程序中最常使用的類型示启,所以Java為了避免在一個(gè)系統(tǒng)中大量產(chǎn)生String對(duì)象兢哭,于是就設(shè)計(jì)了一個(gè)字符串池(字符串常量池)

    它的機(jī)制是這樣的:創(chuàng)建一個(gè)字符串時(shí),首先檢查池中是否有字面值相等的字符串夫嗓,如果有迟螺,則不再創(chuàng)建,直接返回池中該對(duì)象的引用舍咖,若沒(méi)有則創(chuàng)建之矩父,然后放到池中,并返回新建對(duì)象的引用排霉。

  • new String("")賦值

    new String("")直接聲明的String對(duì)象是不檢查字符串池的窍株,也不會(huì)把對(duì)象放到池中。

對(duì)象放到池中會(huì)不會(huì)產(chǎn)生線程安全攻柠?

String類是一個(gè)不可變(Immutable)對(duì)象球订,有兩層意思:一是String類是final類,不可繼承瑰钮,不可能產(chǎn)生一個(gè)String的子類冒滩;二是在String類提供的所有方法中,如果有String返回值浪谴,就會(huì)新建一個(gè)String對(duì)象开睡,不對(duì)原對(duì)象進(jìn)行修改,這也就保證了原對(duì)象是不可改變的苟耻。

注意:字符串池比較特殊篇恒,它在編譯期已經(jīng)決定了其存在JVM的常量池(ConstantPool),垃圾回收器是不會(huì)對(duì)它進(jìn)行回收的凶杖。

建議53:注意方法中傳遞的參數(shù)要求★☆☆☆☆

replacereplaceAll方法舉例說(shuō)明:

public class Client {

    public static void main(String[] args) {
        String s1 = "謹(jǐn)以書(shū)為馬謹(jǐn)以";
        System.out.println(s1.replace("謹(jǐn)以", ""));
        //運(yùn)行結(jié)果:書(shū)為馬
        String s2 = "謹(jǐn)以書(shū)為馬謹(jǐn)以";
        System.out.println(s2.replaceAll("謹(jǐn)以", ""));
        //運(yùn)行結(jié)果:書(shū)為馬
    }
}

以上例子中胁艰,雖然使用replace與replaceAll的運(yùn)行結(jié)果一致,但是他們是有區(qū)別的官卡,源碼如下:

/** 
 * Replaces each substring of this string that matches the literal target sequence 
 * with the specified literal replacement sequence ...
 * 翻譯:使用指定的文字替換序列 替換此字符串中與文字目標(biāo)序列匹配的每個(gè)子字符串
 */
public String replace(CharSequence target, CharSequence replacement) {
        return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
                this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}
/** 
 * Replaces each substring of this string that matches the given regular expression 
 * with the given replacement...
 * 翻譯:替換此字符串中 與給定正則表達(dá)式匹配的每個(gè)子字符串 為給定的替換字符串
 */
public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

原來(lái)replaceAll方法傳遞的第一個(gè)參數(shù)是正則表達(dá)式蝗茁,但是在參數(shù)類型上只要是String就行,所以很容易讓使用者誤解寻咒。

  • replace 的參數(shù)是 char 和 CharSequence哮翘,即可以支持字符的替換,也支持字符串的替換毛秘。

  • replaceAll 的參數(shù)是 regex饭寺,即基于正則表達(dá)式的替換阻课,如果是正則,執(zhí)行正則替換艰匙,如果是字符串限煞,執(zhí)行字符串替換。

注意:雖然有時(shí)確實(shí)得到了預(yù)期的結(jié)果员凝,但我們要真正了解方法的參數(shù)要求

建議54:正確使用String署驻、StringBuffer、StringBuilder★☆☆☆☆

String類是不可改變的健霹,String類的操作都是產(chǎn)生新的String對(duì)象旺上,賦值操作表面看似值變了,不過(guò)是引用指向了新創(chuàng)建的字符串對(duì)象而已糖埋。

StringBuilder與StringBuffer基本相同宣吱,都是可變字符序列,StringBuffer的方法前都有synchronized關(guān)鍵字瞳别,所以StringBuffer是線程安全的征候,這也是StringBuffer在性能上遠(yuǎn)低于StringBuilder的原因。

性能比較(由低到高):String < StringBuffer < StringBuilder

三者的使用場(chǎng)景:

  • 使用String類的場(chǎng)景

    在字符串不經(jīng)常變化的場(chǎng)景中可以使用String類祟敛,例如常量的聲明疤坝、少量的變量運(yùn)算等。

  • 使用StringBuffer類的場(chǎng)景

    在頻繁進(jìn)行字符串的運(yùn)算(如拼接馆铁、替換卒煞、刪除等),并且運(yùn)行在多線程的環(huán)境中叼架,則可以考慮使用StringBuffer,例如XML解析衣撬、HTTP參數(shù)解析和封裝等乖订。

  • 使用StringBuilder類的場(chǎng)景

    在頻繁進(jìn)行字符串的運(yùn)算(如拼接、替換具练、刪除等)乍构,并且運(yùn)行在單線程的環(huán)境中,則可以考慮使用StringBuilder扛点,如SQL語(yǔ)句的拼裝哥遮、JSON封裝等。

建議55:注意字符串的位置★☆☆☆☆

示例代碼:

public class Client {

    public static void main(String[] args) {
        String s1 = 1 + 2 + "謹(jǐn)以書(shū)為馬";
        System.out.println(s1);
        //運(yùn)行結(jié)果:3謹(jǐn)以書(shū)為馬
        String s2 = "謹(jǐn)以書(shū)為馬" + 1 + 2;
        System.out.println(s2);
        //運(yùn)行結(jié)果:謹(jǐn)以書(shū)為馬12
    }
}

Java對(duì)加號(hào)的處理機(jī)制:在使用加號(hào)進(jìn)行計(jì)算的表達(dá)式中陵究,只要遇到String字符串眠饮,則所有的數(shù)據(jù)都會(huì)轉(zhuǎn)換為String類型進(jìn)行拼接,如果是原始數(shù)據(jù)铜邮,則直接拼接仪召,如果是對(duì)象寨蹋,則調(diào)用toString方法的返回值然后拼接。

注意在“+”表達(dá)式中扔茅,String字符串具有最高優(yōu)先級(jí)已旧。

建議56:自由選擇字符串拼接方法★★☆☆☆

字符串進(jìn)行拼接有三種方法:加號(hào)、concat方法及StringBuilder(StringBuffer召娜,兩者是一樣的)的append方法运褪。

三者之間的區(qū)別:append方法最快,concat方法次之玖瘸,加號(hào)最慢秸讹。具體看如下示例:

  1. “+”方法拼接字符串,示例代碼:
//“+”方法拼接字符串
String str = "a";
str += "c";
str += "d";
//內(nèi)部實(shí)現(xiàn)與以下代碼相同
str = new StringBuilder(str).append("c").toString();
str = new StringBuilder(str).append("d").toString();

它與純粹使用StringBuilder的append方法是不同的:一是每次會(huì)創(chuàng)建一個(gè)StringBuilder對(duì)象店读,二是每次執(zhí)行完畢都要調(diào)用toString方法將其轉(zhuǎn)換為字符串嗦枢,它的執(zhí)行時(shí)間就是耗費(fèi)在這里了。

注意:

String str = "a";
str += "b" + "c" ;
//內(nèi)部實(shí)現(xiàn)與以下代碼相同
str = new StringBuilder(str).append("b").append("b").toString();
//所以“+”拼接字符串屯断,盡量“一行”寫(xiě)文虏,避免循環(huán)中使用“+”拼接字符串
  1. concat方法拼接字符串源碼:
public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        //將原始字符串放到新增長(zhǎng)的char數(shù)組中
        char buf[] = Arrays.copyOf(value, len + otherLen);
        //將新字符串添加到buf中
        str.getChars(buf, len);
        //返回新的字符串
        return new String(buf, true);
}

其整體看上去就是一個(gè)數(shù)組拷貝,雖然在內(nèi)存中的處理都是原子性操作殖演,速度非逞趺兀快,不過(guò)趴久,注意看最后的return語(yǔ)句丸相,每次的concat操作都會(huì)新創(chuàng)建一個(gè)String對(duì)象,這就是concat速度慢下來(lái)的真正原因彼棍。

  1. append方法拼接字符串灭忠,StringBuilder的append方法直接由父類AbstractStringBuilder實(shí)現(xiàn),其代碼如下:
public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        //加長(zhǎng)數(shù)組座硕,并做copy
        ensureCapacityInternal(count + len);
        //將字符串復(fù)制到目標(biāo)數(shù)組
        str.getChars(0, len, value, count);
        count += len;
        return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
}

整個(gè)append方法都在做字符數(shù)組處理弛作,加長(zhǎng),然后數(shù)組拷貝华匾,這些都是基本的數(shù)據(jù)處理映琳,沒(méi)有新建任何對(duì)象,所以速度也就最快了蜘拉。

注意:StringBuilder的實(shí)現(xiàn)性能最優(yōu)萨西,但并不表示我們一定要使用StringBuilder,因?yàn)椤?”非常符合我們的編碼習(xí)慣旭旭,適合人類閱讀谎脯。在大多數(shù)情況下我們都可以使用加號(hào)操作,只有在系統(tǒng)性能臨界(如在性能“增之一分則太長(zhǎng)”的情況下)的時(shí)候才可以考慮使用concat或append方法您机。而且穿肄,很多時(shí)候系統(tǒng)80%的性能是消耗在20%的代碼上的年局,我們的精力應(yīng)該更多的投入到算法和結(jié)構(gòu)上。

建議57:推薦在復(fù)雜字符串操作中使用正則表達(dá)式★☆☆☆☆

正則表達(dá)式在字符串的查找咸产、替換矢否、剪切、復(fù)制脑溢、刪除等方面有著非凡的作用僵朗,特別是面對(duì)大量的文本字符需要處理(如需要讀取大量的LOG日志)時(shí),使用正則表達(dá)式可以大幅地提高開(kāi)發(fā)效率和系統(tǒng)性能屑彻,但是正則表達(dá)式是一個(gè)惡魔(Regular Expressions is evil)验庙,它會(huì)使程序難以讀懂,想想看社牲,寫(xiě)一個(gè)包含^粪薛、$、\A搏恤、\s违寿、\Q、+熟空、藤巢?、()息罗、[]掂咒、{}等符號(hào)的正則表達(dá)式,然后告訴你這是一個(gè)“這樣迈喉,這樣……”的字符串查找绍刮,你是不是要崩潰了压状?這代碼只有上帝才能看懂了嚷量!

注意:正則表達(dá)式是惡魔,威力巨大亏娜,但難以控制油坝。

建議58:強(qiáng)烈建議使用UTF編碼★☆☆☆☆

Java程序涉及的編碼包括兩部分:

  1. Java文件編碼
    如果我們使用記事本創(chuàng)建一個(gè).java后綴的文件,則文件的編碼格式就是操作系統(tǒng)默認(rèn)的格式刨裆。如果是使用IDE工具創(chuàng)建的澈圈,則依賴于IDE的設(shè)置。
  2. Class文件編碼
    通過(guò)javac命令生成的后綴名為.class的文件是UTF-8編碼的UNICODE文件帆啃,這在任何操作系統(tǒng)上都是一樣的瞬女,只要是class文件就會(huì)是UNICODE格式。需要說(shuō)明的是努潘,UTF是UNICODE的存儲(chǔ)和傳輸格式诽偷,它是為了解決UNICODE的高位占用冗余空間而產(chǎn)生的坤学,使用UTF編碼就標(biāo)志著字符集使用的是UNICODE。

注意:為了避免在應(yīng)用開(kāi)發(fā)中出現(xiàn)亂碼或者需要額外進(jìn)行轉(zhuǎn)碼操作报慕,最好的解決辦法就是使用統(tǒng)一的編碼格式深浮。

建議59:對(duì)字符串排序持一種寬容的心態(tài)★☆☆☆☆

Java中一涉及中文處理就會(huì)冒出很多問(wèn)題來(lái),其中排序也是一個(gè)讓人頭疼的課題眠冈,中國(guó)的漢字博大精深飞苇,Java是否都能精確的排序呢?最主要的一點(diǎn)是漢字中有象形文字蜗顽,音形分離布卡,是不是每個(gè)漢字都能按照拼音的順序排列好呢?

答案是:并不能雇盖。

示例代碼:

public class Client {

    public static void main(String[] args) {
        String[] strs = {"犇(B)", "鑫(X)"};
        Arrays.sort(strs, Collator.getInstance(Locale.CHINA));
        int i = 0;
        for (String str : strs) {
            System.out.println((++i) + "忿等、" + str);
        }
        //運(yùn)行結(jié)果:
        //1、鑫(X)
        //2崔挖、犇(B)
    }
}

輸出結(jié)果亂了贸街!不要責(zé)怪Java,它已經(jīng)盡量為我們考慮了虚汛,只是因?yàn)槲覀兊臐h字文化太博大精深了匾浪,要做好這個(gè)排序確實(shí)有點(diǎn)難為它。

更深層次的原因是Java使用的是UNICODE編碼卷哩,而中文UNICODE字符集是來(lái)源于GB18030的蛋辈,GB18030又是從GB2312發(fā)展起來(lái),GB2312是一個(gè)包含了7000多個(gè)字符的字符集将谊,它是按照拼音排序冷溶,并且是連續(xù)的,之后的GBK尊浓、GB18030都是在其基礎(chǔ)上擴(kuò)充出來(lái)的逞频,所以要讓它們完整排序也就難上加難了。

注意:如果排序不是一個(gè)關(guān)鍵算法栋齿,使用Collator類即可苗胀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瓦堵,隨后出現(xiàn)的幾起案子基协,更是在濱河造成了極大的恐慌,老刑警劉巖菇用,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜驮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡惋鸥,警方通過(guò)查閱死者的電腦和手機(jī)杂穷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)悍缠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人耐量,你說(shuō)我怎么就攤上這事飞蚓。” “怎么了拴鸵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵玷坠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我劲藐,道長(zhǎng)八堡,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任聘芜,我火速辦了婚禮兄渺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汰现。我一直安慰自己挂谍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布瞎饲。 她就那樣靜靜地躺著口叙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嗅战。 梳的紋絲不亂的頭發(fā)上妄田,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音驮捍,去河邊找鬼疟呐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛东且,可吹牛的內(nèi)容都是我干的启具。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼珊泳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鲁冯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起色查,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤晓褪,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后综慎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡勤庐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年示惊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了好港。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡米罚,死狀恐怖钧汹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情录择,我是刑警寧澤拔莱,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站隘竭,受9級(jí)特大地震影響塘秦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜动看,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一尊剔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧菱皆,春花似錦须误、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至篷店,卻和暖如春祭椰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背船庇。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工吭产, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸭轮。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓臣淤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親窃爷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邑蒋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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