從該類的聲明中咱們能夠看出String是final類型的,表示該類不能被繼承,同時該類實現了三個接口:java.io.Serializable、 Comparable、 CharSequencejava
這是一個字符數組,而且是final類型,他用於存儲字符串內容,從fianl這個關鍵字中咱們能夠看出,String的內容一旦被初始化了是不能被更改的。 雖然有這樣的例子: String s = 「a」; s = 「b」 可是,這並非對s的修改,而是從新指向了新的字符串, 從這裏咱們也能知道,String其實就是用char[]實現的。程序員
緩存字符串的hash Code,默認值爲 0正則表達式
由於String實現了Serializable接口,因此支持序列化和反序列化支持。Java的序列化機制是經過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,若是相同就認爲是一致的,能夠進行反序列化,不然就會出現序列化版本不一致的異常(InvalidCastException)。數據庫
String類做爲一個java.lang包中比較經常使用的類,天然有不少重載的構造方法.在這裏介紹幾種典型的構造方法:編程
咱們知道,其實String就是使用字符數組(char[])實現的。因此咱們可使用一個字符數組來建立一個String,那麼這裏值得注意的是,當咱們使用字符數組建立String的時候,會用到Arrays.copyOf方法和Arrays.copyOfRange方法。這兩個方法是將原有的字符數組中的內容逐一的複製到String中的字符數組中。一樣,咱們也能夠用一個String類型的對象來初始化一個String。這裏將直接將源String中的value和hash兩個屬性直接賦值給目標String。由於String一旦定義以後是不能夠改變的,因此也就不用擔憂改變源String的值會影響到目標String的值。數組
固然,在使用字符數組來建立一個新的String對象的時候,不只可使用整個字符數組,也可使用字符數組的一部分,只要多傳入兩個參數int offset
和int count
就能夠了。緩存
在Java中,String實例中保存有一個char[]字符數組,char[]字符數組是以unicode碼來存儲的,String 和 char 爲內存形式,byte是網絡傳輸或存儲的序列化形式。因此在不少傳輸和存儲的過程當中須要將byte[]數組和String進行相互轉化。因此,String提供了一系列重載的構造方法來將一個字符數組轉化成String,提到byte[]和String之間的相互轉換就不得不關注編碼問題。String(byte[] bytes, Charset charset)是指經過charset來解碼指定的byte數組,將其解碼成unicode的char[]數組,夠形成新的String。安全
這裏的bytes字節流是使用charset進行編碼的,想要將他轉換成unicode的char[]數組,而又保證不出現亂碼,那就要指定其解碼方式網絡
一樣使用字節數組來構造String也有不少種形式,按照是否指定解碼方式分的話能夠分爲兩種:app
String(byte bytes[]) String(byte bytes[], int offset, int length) String(byte bytes[], Charset charset) String(byte bytes[], String charsetName) String(byte bytes[], int offset, int length, Charset charset) String(byte bytes[], int offset, int length, String charsetName)
若是咱們在使用byte[]構造String的時候,使用的是下面這四種構造方法(帶有charsetName或者charset參數)的一種的話,那麼就會使用StringCoding.decode方法進行解碼,使用的解碼的字符集就是咱們指定的charsetName或者charset。 咱們在使用byte[]構造String的時候,若是沒有指明解碼使用的字符集的話,那麼StringCoding的decode方法首先調用系統的默認編碼格式,若是沒有指定編碼格式則默認使用ISO-8859-1編碼格式進行編碼操做。主要體現代碼以下:
做爲String的兩個「兄弟」,StringBuffer和StringBuider也能夠被當作構造String的參數。
固然,這兩個構造方法是不多用到的,至少我歷來沒有使用過,由於當咱們有了StringBuffer或者StringBuilfer對象以後能夠直接使用他們的toString方法來獲得String。關於效率問題,Java的官方文檔有提到說使用StringBuilder的toString方法會更快一些,緣由是StringBuffer的toString方法是synchronized的,在犧牲了效率的狀況下保證了線程安全。
String除了提供了不少公有的供程序員使用的構造方法之外,還提供了一個保護類型的構造方法(Java 7),咱們看一下他是怎麼樣的:
從代碼中咱們能夠看出,該方法和 String(char[] value)有兩點區別,第一個,該方法多了一個參數: boolean share,其實這個參數在方法體中根本沒被使用,也給了註釋,目前不支持使用false,只使用true。那麼能夠判定,加入這個share的只是爲了區分於String(char[] value)方法,不加這個參數就沒辦法定義這個函數,只有參數不能才能進行重載。那麼,第二個區別就是具體的方法實現不一樣。咱們前面提到過,String(char[] value)方法在建立String的時候會用到 會用到Arrays的copyOf方法將value中的內容逐一複製到String當中,而這個String(char[] value, boolean share)方法則是直接將value的引用賦值給String的value。那麼也就是說,這個方法構造出來的String和參數傳過來的char[] value共享同一個數組。 那麼,爲何Java會提供這樣一個方法呢? 首先,咱們分析一下使用該構造函數的好處:
首先,性能好,這個很簡單,一個是直接給數組賦值(至關於直接將String的value的指針指向char[]數組),一個是逐一拷貝。固然是直接賦值快了。
其次,共享內部數組節約內存。
可是,該方法之因此設置爲protected,是由於一旦該方法設置爲公有,在外面能夠訪問的話,那就破壞了字符串的不可變性。例如以下YY情形:
若是構造方法沒有對arr進行拷貝,那麼其餘人就能夠在字符串外部修改該數組,因爲它們引用的是同一個數組,所以對arr的修改就至關於修改了字符串。
因此,從安全性角度考慮,他也是安全的。對於調用他的方法來講,因爲不管是原字符串仍是新字符串,其value數組自己都是String對象的私有屬性,從外部是沒法訪問的,所以對兩個字符串來講都很安全。
在Java 7 之有不少String裏面的方法都使用這種「性能好的、節約內存的、安全」的構造函數。好比:substring、replace、concat、valueOf等方法(實際上他們使用的是public String(char[], int, int)方法,原理和本方法相同,已經被本方法取代)。
可是在Java 7中,substring已經再也不使用這種「優秀」的方法了,爲何呢? 雖然這種方法有不少優勢,可是他有一個致命的缺點,對於sun公司的程序員來講是一個零容忍的bug,那就是他頗有可能形成內存泄露。 看一個例子,假設一個方法從某個地方(文件、數據庫或網絡)取得了一個很長的字符串,而後對其進行解析並提取其中的一小段內容,這種狀況常常發生在網頁抓取或進行日誌分析的時候。下面是示例代碼。
在這裏aLongString只是臨時的,真正有用的是aPart,其長度只有20個字符,可是它的內部數組倒是從aLongString那裏共享的,所以雖然aLongString自己能夠被回收,但它的內部數組卻不能(以下圖)。這就致使了內存泄漏。若是一個程序中這種狀況常常發生有可能會致使嚴重的後果,如內存溢出,或性能降低。
新的實現雖然損失了性能,並且浪費了一些存儲空間,但卻保證了字符串的內部數組能夠和字符串對象一塊兒被回收,從而防止發生內存泄漏,所以新的substring比原來的更健壯。
額、、、扯了好遠,雖然substring方法已經爲了其魯棒性放棄使用這種share數組的方法,可是這種share數組的方法仍是有一些其餘方法在使用的,這是爲何呢?首先呢,這種方式構造對應有不少好處,其次呢,其餘的方法不會將數組長度變短,也就不會有前面說的那種內存泄露的狀況(內存泄露是指不用的內存沒有辦法被釋放,好比說concat方法和replace方法,他們不會致使元數組中有大量空間不被使用,由於他們一個是拼接字符串,一個是替換字符串內容,不會將字符數組的長度變得很短!)。
length() 返回字符串長度 isEmpty() 返回字符串是否爲空 charAt(int index) 返回字符串中第(index+1)個字符 char[] toCharArray() 轉化成字符數組 trim() 去掉兩端空格 toUpperCase() 轉化爲大寫 toLowerCase() 轉化爲小寫 String concat(String str) //拼接字符串 String replace(char oldChar, char newChar) //將字符串中的oldChar字符換成newChar字符 //以上兩個方法都使用了String(char[] value, boolean share); boolean matches(String regex) //判斷字符串是否匹配給定的regex正則表達式 boolean contains(CharSequence s) //判斷字符串是否包含字符序列s String[] split(String regex, int limit) 按照字符regex將字符串分紅limit份。 String[] split(String regex)
在建立String的時候,可使用byte[]數組,將一個字節數組轉換成字符串,一樣,咱們能夠將一個字符串轉換成字節數組,那麼String提供了不少重載的getBytes方法。可是,值得注意的是,在使用這些方法的時候必定要注意編碼問題。好比:
這段代碼在不一樣的平臺上運行獲得結果是不同的。因爲咱們沒有指定編碼方式,因此在該方法對字符串進行編碼的時候就會使用系統的默認編碼方式,好比在中文操做系統中可能會使用GBK或者GB2312進行編碼,在英文操做系統中有可能使用iso-8859-1進行編碼。這樣寫出來的代碼就和機器環境有很強的關聯性了,因此,爲了不沒必要要的麻煩,咱們要指定編碼方式。如使用如下方式:
字符串有一系列方法用於比較兩個字符串的關係。 前四個返回boolean的方法很容易理解,前三個比較就是比較String和要比較的目標對象的字符數組的內容,同樣就返回true,不同就返回false,核心代碼以下:
v1 v2分別表明String的字符數組和目標對象的字符數組。 第四個和前三個惟一的區別就是他會將兩個字符數組的內容都使用toUpperCase方法轉換成大寫再進行比較,以此來忽略大小寫進行比較。相同則返回true,不想同則返回false 。
在這裏,看到這幾個比較的方法代碼,有不少編程的技巧咱們應該學習。咱們看equals方法:
該方法首先判斷this == anObject ?,也就是說判斷要比較的對象和當前對象是否是同一個對象,若是是直接返回true,如不是再繼續比較,而後在判斷anObject是否是String類型的,若是不是,直接返回false,若是是再繼續比較,到了能終於比較字符數組的時候,他仍是先比較了兩個數組的長度,不同直接返回false,同樣再逐一比較值。 雖然代碼寫的內容比較多,可是能夠很大程度上提升比較的效率。值得學習~~!!!
contentEquals有兩個重載,StringBuffer須要考慮線程安全問題,再加鎖以後調用contentEquals((CharSequence) sb)方法。contentEquals((CharSequence) sb)則分兩種狀況,一種是cs instanceof AbstractStringBuilder,另一種是參數是String類型。具體比較方式幾乎和equals方法相似,先作「宏觀」比較,在作「微觀」比較。
下面這個是equalsIgnoreCase代碼的實現:
看到這段代碼,眼前爲之一亮。使用一個三目運算符和&&操做代替了多個if語句。
hashCode的實現其實就是使用數學公式:
s[i]是string的第i個字符,n是String的長度。那爲何這裏用31,而不是其它數呢? 計算機的乘法涉及到移位計算。當一個數乘以2時,就直接拿該數左移一位便可!選擇31緣由是由於31是一個素數!
所謂素數: 質數又稱素數。指在一個大於1的天然數中,除了1和此整數自身外,無法被其餘天然數整除的數。 素數在使用的時候有一個做用就是若是我用一個數字來乘以這個素數,那麼最終的出來的結果只能被素數自己和被乘數還有1來整除!如:咱們選擇素數3來作係數,那麼3*n只能被3和n或者1來整除,咱們能夠很容易的經過3n來計算出這個n來。這應該也是一個緣由! (本段表述有問題,感謝 @沉淪 的提醒)
在存儲數據計算hash地址的時候,咱們但願儘可能減小有一樣的hash地址,所謂「衝突」。若是使用相同hash地址的數據過多,那麼這些數據所組成的hash鏈就更長,從而下降了查詢效率!因此在選擇係數的時候要選擇儘可能長的係數而且讓乘法儘可能不要溢出的係數,由於若是計算出來的hash地址越大,所謂的「衝突」就越少,查找起來效率也會提升。 31能夠 由i*31== (i<<5)-1來表示,如今不少虛擬機裏面都有作相關優化,使用31的緣由多是爲了更好的分配hash地址,而且31只佔用5bits!
在java乘法中若是數字相乘過大會致使溢出的問題,從而致使數據的丟失.
而31則是素數(質數)並且不是很長的數字,最終它被選擇爲相乘的係數的緣由不過與此!
在Java中,整型數是32位的,也就是說最多有2^32= 4294967296個整數,將任意一個字符串,通過hashCode計算以後,獲得的整數應該在這4294967296數之中。那麼,最多有 4294967297個不一樣的字符串做hashCode以後,確定有兩個結果是同樣的, hashCode能夠保證相同的字符串的hash值確定相同,可是,hash值相同並不必定是value值就相同。
前面咱們介紹過,java 7 中的substring方法使用String(value, beginIndex, subLen)方法建立一個新的String並返回,這個方法會將原來的char[]中的值逐一複製到新的String中,兩個數組並非共享的,雖然這樣作損失一些性能,可是有效地避免了內存泄露。
1)replace的參數是char和CharSequence,便可以支持字符的替換,也支持字符串的替換 2)replaceAll和replaceFirst的參數是regex,即基於規則表達式的替換,好比,能夠經過replaceAll(「\d」, 「*」)把一個字符串全部的數字字符都換成星號; 相同點是都是所有替換,即把源字符串中的某一字符或字符串所有換成指定的字符或字符串, 若是隻想替換第一次出現的,可使用 replaceFirst(),這個方法也是基於規則表達式的替換,但與replaceAll()不一樣的是,只替換第一次出現的字符串; 另外,若是replaceAll()和replaceFirst()所用的參數據不是基於規則表達式的,則與replace()替換字符串的效果是同樣的,即這二者也支持字符串的操做;
String的底層是由char[]實現的:經過一個char[]類型的value屬性!早期的String構造器的實現呢,不會拷貝數組的,直接將參數的char[]數組做爲String的value屬性。而後test[0] = 'A';將致使字符串的變化。爲了不這個問題,提供了copyValueOf方法,每次都拷貝成新的字符數組來構造新的String對象。可是如今的String對象,在構造器中就經過拷貝新數組實現了,因此這兩個方面在本質上已經沒區別了。
valueOf()有不少種形式的重載,包括:
能夠看到這些方法能夠將六種基本數據類型的變量轉換成String類型。
該方法返回一個字符串對象的內部化引用。 衆所周知:String類維護一個初始爲空的字符串的對象池,當intern方法被調用時,若是對象池中已經包含這一個相等的字符串對象則返回對象池中的實例,不然添加字符串到對象池並返回該字符串的引用。
咱們知道,Java是不支持重載運算符,String的「+」是java中惟一的一個重載運算符,那麼java使如何實現這個加號的呢?咱們先看一段代碼:
而後咱們將這段代碼反編譯:
看了反編譯以後的代碼咱們發現,其實String對「+」的支持其實就是使用了StringBuilder以及他的append、toString兩個方法。
接下來咱們看如下這段代碼,咱們有三種方式將一個int類型的變量變成呢過String類型,那麼他們有什麼區別?
一、第三行和第四行沒有任何區別,由於String.valueOf(i)也是調用Integer.toString(i)來實現的。 二、第二行代碼實際上是String i1 = (new StringBuilder()).append(i).toString();,首先建立一個StringBuilder對象,而後再調用append方法,再調用toString方法。