讀書(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ù)要求★☆☆☆☆
以replace和replaceAll方法舉例說(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)最慢秸讹。具體看如下示例:
- “+”方法拼接字符串,示例代碼:
//“+”方法拼接字符串
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)中使用“+”拼接字符串
- 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)的真正原因彼棍。
- 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程序涉及的編碼包括兩部分:
- Java文件編碼
如果我們使用記事本創(chuàng)建一個(gè).java后綴的文件,則文件的編碼格式就是操作系統(tǒng)默認(rèn)的格式刨裆。如果是使用IDE工具創(chuàng)建的澈圈,則依賴于IDE的設(shè)置。 - 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類即可苗胀。