String表示字符串,,Java中所有字符串的字面值都是String類的實(shí)例,例如“ABC”,。字符串是常量,,在定義之后不能被改變,字符串緩沖區(qū)支持可變的字符串,。因?yàn)?String 對(duì)象是不可變的,,所以可以共享它們。例如: 相當(dāng)于 這里還有一些其他使用字符串的例子: String類提供了檢查字符序列中單個(gè)字符的方法,,比如有比較字符串,,搜索字符串,,提取子字符串,創(chuàng)建一個(gè)字符串的副本,、字符串的大小寫轉(zhuǎn)換等,。實(shí)例映射是基于
一,、定義 從該類的聲明中我們可以看出String是final類型的,,表示該類不能被繼承,同時(shí)該類實(shí)現(xiàn)了三個(gè)接口: 二、屬性 這是一個(gè)字符數(shù)組,,并且是final類型,他用于存儲(chǔ)字符串內(nèi)容,,從fianl這個(gè)關(guān)鍵字中我們可以看出,,String的內(nèi)容一旦被初始化了是不能被更改的。 雖然有這樣的例子: String s = “a”; s = “b” 但是,,這并不是對(duì)s的修改,,而是重新指向了新的字符串, 從這里我們也能知道,,String其實(shí)就是用char[]實(shí)現(xiàn)的,。 緩存字符串的hash Code,默認(rèn)值為 0
三,、構(gòu)造方法 String類作為一個(gè)java.lang包中比較常用的類,自然有很多重載的構(gòu)造方法.在這里介紹幾種典型的構(gòu)造方法: 1.使用字符數(shù)組、字符串構(gòu)造一個(gè)String 我們知道,,其實(shí)String就是使用字符數(shù)組(char[])實(shí)現(xiàn)的,。所以我們可以使用一個(gè)字符數(shù)組來創(chuàng)建一個(gè)String,那么這里值得注意的是,,當(dāng)我們使用字符數(shù)組創(chuàng)建String的時(shí)候,,會(huì)用到
2.使用字節(jié)數(shù)組構(gòu)造一個(gè)String 在Java中,String實(shí)例中保存有一個(gè)
同樣使用字節(jié)數(shù)組來構(gòu)造String也有很多種形式,,按照是否指定解碼方式分的話可以分為兩種:
如果我們?cè)谑褂胋yte[]構(gòu)造String的時(shí)候,使用的是下面這四種構(gòu)造方法(帶有 3.使用StringBuffer和StringBuider構(gòu)造一個(gè)String 作為String的兩個(gè)“兄弟”,,StringBuffer和StringBuider也可以被當(dāng)做構(gòu)造String的參數(shù)。 當(dāng)然,,這兩個(gè)構(gòu)造方法是很少用到的,,至少我從來沒有使用過,因?yàn)楫?dāng)我們有了StringBuffer或者StringBuilfer對(duì)象之后可以直接使用他們的toString方法來得到String,。關(guān)于效率問題,,Java的官方文檔有提到說使用StringBuilder的toString方法會(huì)更快一些,原因是StringBuffer的 4.一個(gè)特殊的保護(hù)類型的構(gòu)造方法 String除了提供了很多公有的供程序員使用的構(gòu)造方法以外,還提供了一個(gè)保護(hù)類型的構(gòu)造方法(Java 7),,我們看一下他是怎么樣的: 從代碼中我們可以看出,,該方法和
但是,,該方法之所以設(shè)置為protected,,是因?yàn)橐坏┰摲椒ㄔO(shè)置為公有,在外面可以訪問的話,,那就破壞了字符串的不可變性,。例如如下YY情形: 如果構(gòu)造方法沒有對(duì)arr進(jìn)行拷貝,那么其他人就可以在字符串外部修改該數(shù)組,,由于它們引用的是同一個(gè)數(shù)組,,因此對(duì)arr的修改就相當(dāng)于修改了字符串。
在Java 7 之有很多String里面的方法都使用這種“性能好的、節(jié)約內(nèi)存的,、安全”的構(gòu)造函數(shù),。比如: 但是在Java 7中,substring已經(jīng)不再使用這種“優(yōu)秀”的方法了,為什么呢,? 雖然這種方法有很多優(yōu)點(diǎn),,但是他有一個(gè)致命的缺點(diǎn),對(duì)于sun公司的程序員來說是一個(gè)零容忍的bug,,那就是他很有可能造成內(nèi)存泄露,。 看一個(gè)例子,假設(shè)一個(gè)方法從某個(gè)地方(文件,、數(shù)據(jù)庫或網(wǎng)絡(luò))取得了一個(gè)很長(zhǎng)的字符串,,然后對(duì)其進(jìn)行解析并提取其中的一小段內(nèi)容,這種情況經(jīng)常發(fā)生在網(wǎng)頁抓取或進(jìn)行日志分析的時(shí)候,。下面是示例代碼,。 在這里aLongString只是臨時(shí)的,真正有用的是aPart,,其長(zhǎng)度只有20個(gè)字符,,但是它的內(nèi)部數(shù)組卻是從aLongString那里共享的,因此雖然aLongString本身可以被回收,,但它的內(nèi)部數(shù)組卻不能(如下圖),。這就導(dǎo)致了內(nèi)存泄漏。如果一個(gè)程序中這種情況經(jīng)常發(fā)生有可能會(huì)導(dǎo)致嚴(yán)重的后果,,如內(nèi)存溢出,,或性能下降。 新的實(shí)現(xiàn)雖然損失了性能,,而且浪費(fèi)了一些存儲(chǔ)空間,,但卻保證了字符串的內(nèi)部數(shù)組可以和字符串對(duì)象一起被回收,從而防止發(fā)生內(nèi)存泄漏,,因此新的substring比原來的更健壯,。 額、,、,、扯了好遠(yuǎn),雖然substring方法已經(jīng)為了其魯棒性放棄使用這種share數(shù)組的方法,,但是這種share數(shù)組的方法還是有一些其他方法在使用的,,這是為什么呢?首先呢,,這種方式構(gòu)造對(duì)應(yīng)有很多好處,,其次呢,其他的方法不會(huì)將數(shù)組長(zhǎng)度變短,,也就不會(huì)有前面說的那種內(nèi)存泄露的情況(內(nèi)存泄露是指不用的內(nèi)存沒有辦法被釋放,,比如說concat方法和replace方法,,他們不會(huì)導(dǎo)致元數(shù)組中有大量空間不被使用,,因?yàn)樗麄円粋€(gè)是拼接字符串,,一個(gè)是替換字符串內(nèi)容,不會(huì)將字符數(shù)組的長(zhǎng)度變得很短?。?。 四、其他方法
getBytes 在創(chuàng)建String的時(shí)候,可以使用byte[]數(shù)組,,將一個(gè)字節(jié)數(shù)組轉(zhuǎn)換成字符串,,同樣,我們可以將一個(gè)字符串轉(zhuǎn)換成字節(jié)數(shù)組,,那么String提供了很多重載的getBytes方法,。但是,值得注意的是,,在使用這些方法的時(shí)候一定要注意編碼問題,。比如: 這段代碼在不同的平臺(tái)上運(yùn)行得到結(jié)果是不一樣的。由于我們沒有指定編碼方式,,所以在該方法對(duì)字符串進(jìn)行編碼的時(shí)候就會(huì)使用系統(tǒng)的默認(rèn)編碼方式,,比如在中文操作系統(tǒng)中可能會(huì)使用GBK或者GB2312進(jìn)行編碼,在英文操作系統(tǒng)中有可能使用iso-8859-1進(jìn)行編碼,。這樣寫出來的代碼就和機(jī)器環(huán)境有很強(qiáng)的關(guān)聯(lián)性了,,所以,為了避免不必要的麻煩,,我們要指定編碼方式,。如使用以下方式: 比較方法 字符串有一系列方法用于比較兩個(gè)字符串的關(guān)系。 前四個(gè)返回boolean的方法很容易理解,,前三個(gè)比較就是比較String和要比較的目標(biāo)對(duì)象的字符數(shù)組的內(nèi)容,,一樣就返回true,不一樣就返回false,核心代碼如下: v1 v2分別代表String的字符數(shù)組和目標(biāo)對(duì)象的字符數(shù)組,。 第四個(gè)和前三個(gè)唯一的區(qū)別就是他會(huì)將兩個(gè)字符數(shù)組的內(nèi)容都使用toUpperCase方法轉(zhuǎn)換成大寫再進(jìn)行比較,,以此來忽略大小寫進(jìn)行比較。相同則返回true,,不想同則返回false 在這里,,看到這幾個(gè)比較的方法代碼,有很多編程的技巧我們應(yīng)該學(xué)習(xí),。我們看equals方法: 該方法首先判斷 contentEquals有兩個(gè)重載,StringBuffer需要考慮線程安全問題,,再加鎖之后調(diào)用 下面這個(gè)是equalsIgnoreCase代碼的實(shí)現(xiàn): 看到這段代碼,眼前為之一亮,。使用一個(gè)三目運(yùn)算符和&&操作代替了多個(gè)if語句,。 hashCode hashCode的實(shí)現(xiàn)其實(shí)就是使用數(shù)學(xué)公式: s[i]是string的第i個(gè)字符,,n是String的長(zhǎng)度,。那為什么這里用31,而不是其它數(shù)呢? 計(jì)算機(jī)的乘法涉及到移位計(jì)算,。當(dāng)一個(gè)數(shù)乘以2時(shí),,就直接拿該數(shù)左移一位即可,!選擇31原因是因?yàn)?1是一個(gè)素?cái)?shù)! 所謂素?cái)?shù): 質(zhì)數(shù)又稱素?cái)?shù),。指在一個(gè)大于1的自然數(shù)中,,除了1和此整數(shù)自身外,沒法被其他自然數(shù)整除的數(shù),。 素?cái)?shù)在使用的時(shí)候有一個(gè)作用就是如果我用一個(gè)數(shù)字來乘以這個(gè)素?cái)?shù),,那么最終的出來的結(jié)果只能被素?cái)?shù)本身和被乘數(shù)還有1來整除,!如:我們選擇素?cái)?shù)3來做系數(shù),那么3*n只能被3和n或者1來整除,,我們可以很容易的通過3n來計(jì)算出這個(gè)n來,。這應(yīng)該也是一個(gè)原因! (本段表述有問題,,感謝 @沉淪 的提醒) 在存儲(chǔ)數(shù)據(jù)計(jì)算hash地址的時(shí)候,,我們希望盡量減少有同樣的hash地址,所謂“沖突”,。如果使用相同hash地址的數(shù)據(jù)過多,那么這些數(shù)據(jù)所組成的hash鏈就更長(zhǎng),,從而降低了查詢效率,!所以在選擇系數(shù)的時(shí)候要選擇盡量長(zhǎng)的系數(shù)并且讓乘法盡量不要溢出的系數(shù),因?yàn)槿绻?jì)算出來的hash地址越大,,所謂的“沖突”就越少,,查找起來效率也會(huì)提高。 31可以 由i*31== (i<> 在java乘法中如果數(shù)字相乘過大會(huì)導(dǎo)致溢出的問題,,從而導(dǎo)致數(shù)據(jù)的丟失. 而31則是素?cái)?shù)(質(zhì)數(shù))而且不是很長(zhǎng)的數(shù)字,,最終它被選擇為相乘的系數(shù)的原因不過與此!
substring 前面我們介紹過,java 7 中的substring方法使用String(value, beginIndex, subLen)方法創(chuàng)建一個(gè)新的String并返回,,這個(gè)方法會(huì)將原來的char[]中的值逐一復(fù)制到新的String中,,兩個(gè)數(shù)組并不是共享的,雖然這樣做損失一些性能,,但是有效地避免了內(nèi)存泄露,。 replaceFirst、replaceAll,、replace區(qū)別
copyValueOf 和 valueOf String的底層是由char[]實(shí)現(xiàn)的:通過一個(gè)char[]類型的value屬性,!早期的String構(gòu)造器的實(shí)現(xiàn)呢,,不會(huì)拷貝數(shù)組的,直接將參數(shù)的char[]數(shù)組作為String的value屬性,。然后
可以看到這些方法可以將六種基本數(shù)據(jù)類型的變量轉(zhuǎn)換成String類型,。 intern()方法 該方法返回一個(gè)字符串對(duì)象的內(nèi)部化引用,。 眾所周知:String類維護(hù)一個(gè)初始為空的字符串的對(duì)象池,當(dāng)intern方法被調(diào)用時(shí),,如果對(duì)象池中已經(jīng)包含這一個(gè)相等的字符串對(duì)象則返回對(duì)象池中的實(shí)例,,否則添加字符串到對(duì)象池并返回該字符串的引用。 String對(duì)“+”的重載 我們知道,,Java是不支持重載運(yùn)算符,,String的“+”是java中唯一的一個(gè)重載運(yùn)算符,那么java使如何實(shí)現(xiàn)這個(gè)加號(hào)的呢,?我們先看一段代碼: 然后我們將這段代碼反編譯: 看了反編譯之后的代碼我們發(fā)現(xiàn),,其實(shí)String對(duì)“+”的支持其實(shí)就是使用了StringBuilder以及他的append、toString兩個(gè)方法,。 String.valueOf和Integer.toString的區(qū)別 接下來我們看以下這段代碼,,我們有三種方式將一個(gè)int類型的變量變成呢過String類型,那么他們有什么區(qū)別,?
|
|