就String而言,平時工作中用得最多,但是很多時候還是用不好,有必要對他進行整體的分析下。如果看過Thinking in java,再看下JDK的源碼,很多東西就會變得十分明了?,F在對String的底層實現進行下分析。
首先是對構造函數而言,我工作中最常用到的可能就是new String(str)這個構造函數了,所以再在此關注這一個。這個構造函數是對傳進來的String進行解析,將其放進一個數組當中,我們設定為arrayOfChar,String類定義了全局變量offset(偏移量),count(大小),value(char類型的數組),此時,會將offset設為0,count設置為arrayOfChar.length,value設置為arrayOfChar,此時整個String構造器完成了初始化工作,三個全局變量全部設定了初始值,當String類型的對象調用類中的方法時,其實就是對這三個變量進行的操作,下面的方法分析中會涉及到。在String的方法中,很多方法返回的都是新的String對象,原因是因為String初始化后,大小就固定了,如下面講解到的方法SubString、concat等方法。
常用方法分析:1、最簡單的isEmpty,底層也是很簡單的一行代碼,return this.count == 0,清晰明了,很好的完成了相應的功能。在此,就使用了構造器中的count。
2、charAt,獲取指定下標的字符。之前說過,構造器將String處理成了一個數組,此時要想得到指定下標字符,就很easy了,直接使用數組中的[n]就可以拿到了,不過它加入了偏移量,之前做了簡單的異常處理,防止數組越界。
3、equals將字符串與指定對象的比較,開發中最常用的方法之一。在方法開始,就做了一個判斷,如果當前對象和指定對象一樣,直接返回true,這里當前對象使用的是this關鍵字,如果不等于,則進行接下來的操作。當指定對象不是String類型(使用instanceof判斷),直接返回false,如果是String類型,則取出當前對象的數組value和指定對象的數組value1,然后根據偏移量對數組進行逐個比較,若數組相等,則返回true,反之有任何一個元素不等,當即返回false。
4、compareTo將兩個字符串按照字典順序比較。返回負數,0,正數。其實底層實現方式跟equals相似,也是使用數組實現的。但是,他的邏輯卻不一樣,它會逐個拿到相同下標的數組元素,然后拿到字典順序進行比較,如果相等,繼續,如果不等,直接相減并return,這是就是正數或者負數了,如果全部相等,最后返回0。
5、startWith(String,int)判斷字符串是否是已制定String開始,這里分析帶兩個參數的,原因是因為startWith(String),endsWith(String)都是調用這個方法實現的,這也進一步展現了代碼重用性帶來的方便,搞笑、簡潔。它的實現依舊是使用數組,將參數String與當前對象數組逐個進行比較,最后得出結果。不過,第二個參數int說明了指定String要從當前對象數組中的哪個下標進行比較。 startWith(String)實際上默認給了startWith(String,int)中int的值為0,表明從數組下標為0的開始比較。endsWith(String)實際就是從數組下標為當前對象數組的長度-指定字符數組的長度進行比較。這樣一來,所以的方法最后實現都是startWith(String,int)來實現的。這一點在工作中也應該使用,即代碼的重用性,最后上升到代碼的簡潔。代碼雖短(一行到兩行),但是卻十分完美的實現了其功能,且清晰明了。個人覺得,方法contains也可以使用startWith(String,int)方法來實現,JDK使用indexOf實現的,不過使用startWith需要循環,效率當然會低很多的?!敬颂幬抑皇桥e一反三,考慮效率,肯定是indexOf好多了】。
6、substring(int1,int2)截取字符串,substring(int)調用前一個,不過第二個int2設置為當前對象數組的長度。它表示從下標int1到下標int2的元素取出來,如果缺失int2,默認為數組長度,最后的實現使用的是String的一個構造函數Stirng(int,int,char[])實現的,返回一個新的String對象。在這個構造函數中,對新的String對象初始化了,包括offset,count,value,這樣截取的方法就返回了一個符合要求的全新的String對象。
7、其實還有concat、replace等比較常用的方法,但是,其底層都是針對數組進行的操作,以上講了幾個典型的,這幾個就不在一一講了,可以直接看源碼,很容易就理解了。只不過這兩個都是返回了全新的String對象,還使用了一些其他的方法,但是思想都差不多一樣。其他還有很多常用方法如getChars(char[], int),replaceAll(String, String),split都是調用了相關類的方法,就不在這里講了。
8、在String中有個靜態內部類CaseInsensitiveComparator,此處還是看下比較好。這個類中只包含了一個方法compare,這個方法是比較連個字符串的,但是忽略其大小寫。方法中使用charAt取出對應的數組元素,然后Character.toUpperCase進行轉換比較,為了安全,他還使用了Character.toLowerCase進行轉換比較,如果相等,進行下一個比較,如果不等,直接求差返回。這個靜態內部類中的方法是不能在其他類中方法進行直接訪問的,而必須要通過外部類的compareToIgnoreCase方法進行訪問。這樣做的目的,我個人認為是防止其他類直接訪問靜態類中的方法,有點類似代理模式,不過這點我還有疑惑,在下面的疑惑中我會提出。這種做法在遍歷器中也用到了,不過在遍歷器的做法稍有不同,那里的做法是返回內部類的對象,然后利用這個對象去訪問內部類中的方法。
總體來說,String我就理解了這么多,了解了底層怎么實現的,在使用String方法時也就更加得心應手了。看完了底層實現,收獲很多,但是也有很多疑惑,先將疑惑列出,希望知道的IT朋友們幫助解答。
疑惑:1、方法equalsIgnoreCase的實現為return (this == paramString),為什么這樣就可以忽略大小寫?具體的實現方式是怎么樣的?
2、剛剛提到的內部類,為什么要使用內部類,我直接將內部類的方法設置為PRivate的,然后在相應的方法中調用,也能起到同樣的效果,可能是我對內部類不了解,但是為什么要這樣做?好處是什么?
3、hashCode方法給類的全局變量hash賦值了,但是在哪個地方使用到了呢?僅僅賦值而已?會不會跟String的==有關?有的話是什么關系?
續:StringBuffer、StringBuilder:
其實搞明白了String的相關方法,再回頭看StringBuffer和StringBuilder,就會很easy了。其中很多方法的實現都差不多一樣的,例如subString方法。之所以StringBuffer是安全的,是因為他的方法都有關鍵字synchronized,所以速度較慢,很多地方都推薦優先使用StringBuider。其實,看了他們的源碼就知道了,他兩的實現方式其實都是一樣的,很多方法都是調用他們的父類方法AbstractStringBuilder的方法的。其實在這里分析AbstractStringBuilder類更為好,相當于分析了SB和SF兩個類的實現。ASB類中的方法主要是insert和append,ASB類的對象不想String那樣,大小后期可以動態更改的,原因是ASB維護的是數組,當數組大小不滿足的時候,他會擴展數組的大小,不需要重新創建對象。所以說StringBuffer、StringBuilder的初始化以后,大小是可以動態變化的,原因就是底層維護的是數組,可以進行擴展,而不需要新的對象。當然Insert的原理也差不多一樣,也是操作數組,所以大小是動態變化的。API文檔中給這兩個類的定義為:一個可變的字符序列。
想要弄清楚StringBuffer、StringBuilder,個人覺得弄清了String就行了,很多東西實現邏輯都一樣,只是底層的實現稍微存在差別,所以導致在性能上有些不同而已。
總體的字符串就分析到這,理解了底層的實現,在日常工作中用得也就更加得心應手了。
以上純屬個人閱讀JDK源碼的理解,歡迎大蝦們指出不對或者補充,同時對于疑惑部分肯定大家賜教,可以評論或者私信,也可以發郵件給我[email protected],有什么好的學習方法,也可以隨時交流。
歡迎各位IT朋友私信我,相互交流、學習!
新聞熱點
疑難解答