????????本文是我自己在秋招復習時的讀書筆記,整理的知識點负蠕,也是為了防止忘記蛙埂,尊重勞動成果,轉載注明出處哦遮糖!如果你也喜歡绣的,那就點個小心心,文末贊賞一杯豆奶吧,嘻嘻屡江。 讓我們共同成長吧……
一芭概、字符串
????????Java中的字符串處理主要有下面三個類來處理的:String、StringBuffer惩嘉、StringBuilder
1罢洲、String
? ??1、String簡介
? ??初始化:
????一般由String聲明的字符串文黎,長度是不可變的惹苗,這也是它與StringBuffer和StringBuilder最直觀的一個區(qū)別。一般初始化方式:String s = "hello world";經過這條語句耸峭,JVM的棧內存中產生一個s變量桩蓉,堆內存中產生hello world字符串對象。s指向了hello world的地址劳闹。像上面這種方式產生的字符串屬于直接量字符串對象院究,JVM在處理這類字符串的時候,會進行緩存玷或,產生時放入字符串池儡首,當程序需要再次使用的時候,無需重新創(chuàng)建一個新的字符串蔬胯,而是直接指向已存在的字符串氛濒∥韪停看下面程序:
public? class? String? Test4{
????public? static? void? main(String[] args){
????????String s ="hello world";
????????String s2 ="hello world";
????????System.out.println(s == s2);
????}
}
????該程序輸出:true 因為s和s2都指向了hello world字符串,它們的地址是同一個执桌。 我們常說仰挣,String的一個很大的特點错蝴,就是它是一個“不可變的字符串”顷锰,就是說馍惹,當一個String對象完成創(chuàng)建后,該對象的內容就固定下來了慎框,但是為什么還會有下面這種情況呢?
public? class? String? Init{
????public? static? void? main(String[] args){
????????String str ="I like";//---------1--------
????????System.out.println(System.identityHashCode(str));
????????str = str +"java";//--------2---------
????????System.out.println(System.identityHashCode(str));
????}
}
該程序輸出:
14576877
12677476
????說明:str似乎是變了馅精,這是為什么呢洲敢?其實是這樣的:str只是一個引用變量,當程序執(zhí)行完1后壮不,str指向“I like”询一。當程序執(zhí)行完2之后,連接運算符會將兩個字符串連在一起绊诲,并且讓str指向新的串:"I like java"掂之,所以动雹,從這里應該可以看得出來胰蝠,最初的對象確實沒有改變茸塞,只是str所指向的對象在不斷改變钾虐。
????String對象的另一種初始化方式效扫,就是采用String類提供的構造方法進行初始化。String類提供了16種構造方法济丘,常用的有五種:
? ??String() ---------?初始化一個String對象,表示一個空字符序列
? ??String(String value)?---------?利用一個直接量創(chuàng)建一個新串
? ??String(char[] value)?---------?利用一個字符數組創(chuàng)建
? ??String(char[] value,int offset,int count)?---------?截取字符數組泪掀,從offset開始count個字符創(chuàng)建
? ??String(StringBuffer buffer)?---------?利用StringBuffer創(chuàng)建
形如:
????String s = new String();
????String s1 = new String(“hello”);
????char[] c = {'h','e','l','l','o'};
????String s2 = new String(c);
????String s3 = new String(c,1,3);
????以上就是String類的基本初始化方法。
2塔拳、String類的一些常用方法
????字符串是最常用的對象,所以颂碧,我們有必要徹底的了解下它肌似,下面我會列舉常用的字符串里的方法川队,因為有很多,就不一一列舉对雪。
????-------public int length()--------
該方法用于獲取字符串的長度,實現如下:
????int? length(){
????????return count;
? ? }
????這是JDK種的原始實現栅干,count在String類里被定義為一個整型常量:private final int count;并且不論采用哪種構造方法,最終都會為count賦值窿给。
使用方法:
????String s ="hello world";
????int length = s.length();
這個比較簡單。
-----------public boolean equals(Object anObject)-----------
該方法用于比較給定對象是否與String相等角撞。
JDK里是這樣實現的:
public? boolean? equals(Object anObject){
????????if(this== anObject) {
????????return? true;
}
if(anObjectinstanceofString) {
? ? String anotherString = (String)anObject;
int n = count;
if(n == anotherString.count) {
char? v1[] = value;//---------1---------
char? v2[] = anotherString.value;//-------2----------
int? i = offset;
int? j = anotherString.offset;
while(n-- !=0) {
if(v1[i++] != v2[j++])
return? false;
}
return? true;
? ? }
}
return? false;
? ? }
從1和2處也看出來劣领,String的底層是基于字符數組的尖淘。我們可以像下面這種方式使用equals():
String s1 =newString("hello world");
String s2 =newString("hello world");
String s3 =newString("hello");
System.out.println(s1.equals(s2));;
System.out.println(s1.equals(s3));
結果輸出:
true
false
此處插入一個很重要的知識點,重寫equals()的一般步驟及注意事項:
1. 使用==操作符檢查“實參是否為指向對象的一個引用”缴守。
2. 使用instanceof操作符檢查“實參是否為正確的類型”。?
3. 把實參轉換到正確的類型村砂。?
4. 對于該類中每一個“關鍵”域罕模,檢查實參中的域與當前對象中對應的域值是否匹配蒿讥。
a.對于既不是float也不是double類型的基本類型的域,可以使用==操作符進行比較
b.對于對象引用類型的域摔敛,可以遞歸地調用所引用的對象的equals方法
?c.對于float類型的域,先使用Float.floatToIntBits轉換成int類型的值售貌,然后使用==操作符比較int類型的值
d.對于double類型的域给猾,先使用Double.doubleToLongBits轉換成long類型的值,然后使用==操作符比較long類型的值颂跨。
5. 當你編寫完成了equals方法之后敢伸,應該問自己三個問題:它是否是對稱的、傳遞的恒削、一致的池颈?(其他兩個特性通常會自行滿足)如果答案是否定的躯砰,那么請找到這些特性未能滿足的原因揭保,再修改equals方法的代碼。
稍后我再做說明匕得,請先再看個例子:
public? class? String? Init{
public? static? void? main(String[] args){
String s ="hello world";
String s1 =newString("hello world");
String s2 =newString("hello world");
String s3 =newString("hello");
String s4 ="hello world";
System.out.println(s.equals(s1));;
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println("------------------");
System.out.println(s == s1);
System.out.println(s == s3);
System.out.println(s == s4);
}
}
輸出:
true
true
false
------------------
false
false
true
此處驗證了一個問題,就是比較方法equals()和==的區(qū)別,一句話:equals()比較的是對象的內容冀惭,也就是JVM堆內存中的內容科吭,==比較的是地址宜狐,也就是棧內存中的內容俭驮。
如上述代碼中,s司光、s1、s2掀虎、s4他們四個String對象的內容都是"hello world"继效,所以翁逞,用equals()比較他們肉拓,返回的都是true毫痕。但是蛉加,當s和s1用==比較時,卻返回false七婴,因為二者在堆中開辟的地址不一樣饲化,所以棚品,返回的肯定是false。而為什么s和s4用==比較時嚼锄,返回的是true呢减拭,因為上文中提到過,直接量的字符串會產生緩存池区丑,所以拧粪,當聲明s4的時候,編譯器檢測到緩存池中存在相同的字符串沧侥,所以就直接使用可霎,只要將s4指向s所指向的字符串就行了,二者指向同一字符串宴杀,所以地址當然相等癣朗!
注意:此處隱藏著一個比較細的編程習慣,尤其是用==進行比較的時候旺罢,盡量將常量放在==的左邊旷余,因為我們有的時候,會不小心將==寫成=扁达,這樣的話正卧,如果將常量放在左邊,編譯器會報錯跪解,提醒你炉旷,但是,如果將變量放在左邊叉讥,常量放右邊砾跃,即使你寫成了=,編譯器默認為變量賦值了节吮,因此也不會報錯。
因為String類實現了public interface Comparable判耕,而Comparable接口里有唯一的方法:public int compareTo(T o)透绩。所以,String類還有另一個字符串比較方法:compareTo()
-----------------public int compareTo(String anotherString)---------------
compareTo()可實現比較兩個字符串的大小蜈首,源碼如下:
publicintcompareTo(String anotherString){
intlen1 = count;
intlen2 = anotherString.count;
intn = Math.min(len1, len2);
charv1[] = value;
charv2[] = anotherString.value;
inti = offset;
intj = anotherString.offset;
if(i == j) {
intk = i;
intlim = n + i;
while(k < lim) {
charc1 = v1[k];
charc2 = v2[k];
if(c1 != c2) {
returnc1 - c2;
}
k++;
? ? }
}else{
while(n-- !=0) {
charc1 = v1[i++];
charc2 = v2[j++];
if(c1 != c2) {
returnc1 - c2;
}
? ? }
}
returnlen1 - len2;
? ? }
compareTo是怎么實現的呢呈驶?
首先拱雏,會對兩個字符串左對齊悔据,然后從左到右一次比較望艺,如果相同顽耳,繼續(xù)扼菠,如果不同尼荆,則計算不同的兩個字符的ASCII值的差烛亦,返回就行了诈泼。與后面的其他字符沒關系。
舉個例子:
public? class? ?CompareToTest{
public? static? void? main(String[] args){
String s ="hallo";
String s2 ="ha";
String s3 ="haeeo";
inta = s.compareTo(s2);
System.out.println("a:"+a);
intb = s.compareTo(s3);
System.out.println("b:"+b);
intc = s2.compareTo(s3);
System.out.println("c:"+c);
}
}
程序輸出:
a:3
b:7
c:-3
s和s2相比煤禽,前兩個相同铐达,如果是這種情況,則直接返回length1-length2
s和s3相比檬果,前兩個相同瓮孙,不用管,直接用第三個字符的ASCII碼做差就行了选脊。所以'l'-'a'=7
此處網友“handsomeman_wei”問我源碼中的c1-c2理解不了杭抠,就是上面紅字部分的解釋。
s2和s3相比恳啥,同第一種情況一樣偏灿,只是length1比length2小,因此值為負數角寸。
-----------public char charAt(int index)-----------
獲取指定位置的字符菩混,比較容易理解,源碼為:
public? char? ?charAt(intindex){
if((index <0) || (index >= count)) {
throw? new? StringIndexOutOfBoundsException(index);
? ? ? ? }
return? value[index + offset];
? ? }
String s = "hallo";
char a = s.charAt(2);
System.out.println(a);
輸出:l
注意:參數index的值從0到字符串的長度-1扁藕,所以沮峡,如果值不在這個范圍內,如下:
String s = "hallo";
char a = s.charAt(8);
System.out.println(a);
則報錯:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 8
at java.lang.String.charAt(String.java:686)
at com.xtfggef.string.CompareToTest.main(CompareToTest.java:20)
與charAt()相對應的是indexOf():根據給定的字符串亿柑,返回他的位置邢疙。
indexOf()有多個參數:
public int indexOf(int ch)
public int indexOf(int ch, int fromIndex)
public int indexOf(String str)
public int indexOf(String str, int fromIndex)
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex)
有興趣的自己去試試,這兒就不多闡述了望薄。
-----------substring()------------
publicStringsubstring(intbeginIndex){
returnsubstring(beginIndex, count);
? ? }
用于截取字符串疟游,此處與另一個方法對比:
publicStringsubstring(intbeginIndex,intendIndex){
if(beginIndex <0) {
thrownewStringIndexOutOfBoundsException(beginIndex);
}
if(endIndex > count) {
thrownewStringIndexOutOfBoundsException(endIndex);
}
if(beginIndex > endIndex) {
thrownewStringIndexOutOfBoundsException(endIndex - beginIndex);
}
return((beginIndex ==0) && (endIndex == count)) ?this:
newString(offset + beginIndex, endIndex - beginIndex, value);
? ? }
前者調用后者來實現,前者截取從指定位置到字符串結束的子字符串痕支,后者截取從指定位置開始颁虐,到endIndex-1位置的子字符串。
publicclassCompareToTest{
publicstaticvoidmain(String[] args){
String s ="helloworld";
String s1 = s.substring(2);
String s2 = s.substring(2,7);
String s3 = (String) s.subSequence(2,7);
System.out.print("s1:"+s1+"\n"+"s2:"+s2+"\n"+"s3:"+s3);
}
}
輸出:
s1:lloworld
s2:llowo
s3:llowo
細心的讀者應該看出來卧须,該類里面包含一個subSequence()另绩,而且該方法與substring(int,int)返回的結果一樣儒陨,觀察下源碼,不難發(fā)現的區(qū)別:
publicCharSequencesubSequence(intbeginIndex,intendIndex){
returnthis.substring(beginIndex, endIndex);
? ? }
? ? }
其實subSequence()內部就是調用的substring(beginIndex, endIndex)笋籽,只是返回值不同蹦漠。
subString返回的是String,subSequence返回的是實現了CharSequence接口的類车海,也就是說使用subSequence得到的結果笛园,只能使用CharSequence接口中的方法。不過在String類中已經重寫了subSequence侍芝,調用subSequence方法研铆,可以直接轉為String對象,如我們例子中的做法竭贩。
-----------------public String replace(char oldChar, char newChar)和public String replaceAll(String regex, String replacement)-------------------
publicStringreplace(charoldChar,charnewChar){
if(oldChar != newChar) {
intlen = count;
inti = -1;
char[] val = value;/* avoid getfield opcode */
intoff = offset;/* avoid getfield opcode */
while(++i < len) {
if(val[off + i] == oldChar) {
break;
}
? ? }
if(i < len) {
charbuf[] =newchar[len];
for(intj =0; j < i ; j++) {
? ? buf[j] = val[off+j];
}
while(i < len) {
charc = val[off + i];
? ? buf[i] = (c == oldChar) ? newChar : c;
? ? i++;
}
returnnewString(0, len, buf);
? ? }
}
returnthis;
? ? }
publicStringreplaceAll(String regex, String replacement){
returnPattern.compile(regex).matcher(this).replaceAll(replacement);
? ? }
前者參數為兩個字符串蚜印,用newChar替換原串里的所有oldChar。
后者從第一個參數可以看出留量,需要替換的東西可以用正則表達式描述窄赋。例子如下:
packagecom.xtfggef.string;
publicclassReplaceTest{
publicstaticvoidmain(String[] args){
String s ="hello world";
String s1 = s.replace("l","d");
System.out.println(s1);
String s2 ="a78e5opx587";
String s3 = s2.replaceAll("[0-9]","");//用空串替換原串里所有的0-9的數字
System.out.println(s3);
}
}
輸出:
heddo wordd
aeopx
-------------public String[] split(String regex)-----------
該方法用于分割字符串,得到一個String類型的數組楼熄,根據regex可知忆绰,參數是個正則表達式。請看下面的例子:
packagecom.xtfggef.string;
publicclassSpiltTest{
publicstaticvoidmain(String[] args){
String s ="hello world";
String s1 ="hello.worldd";
String[] s2 = s.split(" ");
String[] s3 = s1.split("\\.");
for(inti=0; i
System.out.print(s2[i]+" ");
}
System.out.println();
for(intj=0; j
System.out.print(s3[j]+" ");
}
}
}
輸出:
hello world?
hello worldd
關于spilt()的其他重載方法可岂,可參見JDK的String類的實現错敢。
spilt()需要注意的事項,就是當分隔符為 . 的話缕粹,處理起來不一樣稚茅,必須寫成\\.因為.是正則表達式里的一個特殊符號,必須進行轉義
--------------------public native String intern();--------------------(補充知識點:經網友java2000_wl提醒平斩,特此補充亚享,歡迎廣大讀者及時提出建議,我必將虛心接受;婷妗)
intern()方法和前面說的equals()方法關系密切欺税,從public native String intern()看出,它是Java的本地方法揭璃,我們先來看看Java文檔里的描述:
Returns a canonical representationforthe string object.
? ? A pool of strings, initially empty, is maintained privately by the
classString.Whentheinternmethodisinvoked,ifthepoolalreadycontainsa
stringequaltothisStringobjectasdeterminedby
theequals(Object)method,thenthestringfromthepoolis
returned.Otherwise,thisStringobjectisaddedtothe
poolandareferencetothisStringobjectisreturned.
Itfollowsthatforanytwostringssandt,
s.intern()==t.intern() istrueifand onlyifs.equals(t) istrue.
? ? All literal strings and string-valued constant expressions are interned.
@returna string that has the same contents asthisstring, but is
? ? guaranteed to be from a pool of unique strings.
意思就是說晚凿,返回字符串一個規(guī)范的表示。進一步解釋:有兩個字符串s和t瘦馍,s.equals(t),則s.intern()==t.intern().舉個例子:
publicclassStringTest{
publicstaticvoidmain(String[] args){
String s =newString("abc");
String s1 ="abc";
String s2 ="abc";
? ? String s3 = s.intern();
System.out.println(s == s1);//false
System.out.println(s == s2);//false
System.out.println(s == s3);//false
System.out.println(s1 == s3);//true? ? ?
}
}
輸出結果如注釋所示歼秽,前兩個結果前面已經說的很清楚了,現在拿最后一個說明情组,首先看看s3 = s.intern()這句哲银,當調用s.intern()這句的時候扛吞,先去字符串常量池中找,是否有abc這個串荆责,如果沒有,則新增亚脆,同時返回引用做院,如果有,則返回已經存在的引用濒持,此處s1和s2都指向常量池中的abc對象键耕,所以此處是存在的,調用s.intern()后柑营,s3和s1屈雄、s2指向同一個對象,所以s1==s3返回的是true官套。
intern()做到了一個很不尋常的行為:在運行期動態(tài)的在方法區(qū)創(chuàng)建對象酒奶,一般只有像new關鍵字可以在運行期在堆上面創(chuàng)建對象,所以此處比較特殊奶赔。屬于及時編譯的概念惋嚎。
一般常見的字符串處理函數就這些,其它的還有很多站刑,就不一一列舉另伍。
3、一些常見的問題绞旅,處理結果
在我們日常的開發(fā)中摆尝,總會遇到一些問題,在此我總結一下:
String s = "123" + "456"內存中產生幾個字符串對象因悲?
這是個比較有爭議的問題堕汞,面試的時候,老師還挺喜歡問囤捻,論壇上大家說幾個的也有臼朗,我給大家分析一下,因為我們前面有提到Java字符串的緩存機制蝎土,編譯器在編譯的時候會進行優(yōu)化视哑,所以在編譯的過程中123和456被合成了一個字符串"123456",因此誊涯,如果緩存池中目前沒有123456這個對象挡毅,那么會產生一個,即""123456"暴构,且棧中產生一個引用s指向它跪呈,如果緩存池中已經存在"123456"段磨,那么將產生0個對象,直接用s指向它耗绿。
如果spilt()函數的參數在要分割的字符串中沒有怎么辦苹支?如String s = "helloworld" ,我現在調用String[] s2 = s.spilt("abc"),返回什么误阻?
這個問題是我曾經參加紅帽軟件面試的時候遇到的相關題债蜜,當時懵了,像這樣的題目究反,如果不親自遇到過寻定,或者看過源代碼,很難準確的寫出來精耐。
做一個簡單的測試狼速,就可以看得出來:
packagecom.xtfggef.string;
publicclassStringSpilt{
publicstaticvoidmain(String[] args){
String s ="helloworld";
String[] s2 = s.split("abc");
for(inti =0; i < s2.length; i++) {
System.out.println(s2[i] +" "+ i);
}
}
}
輸出:helloworld 0
說明當遇到源字符串中沒有的字符時,會把它整個串放入到數組中卦停。spilt()的內部實現還是挺復雜的向胡,多層嵌套,不便于放到這兒分析沫浆。
關于字符串自動類型轉換分析
首先看一下題的類型:
inti =2;
intj =3;
String s ="9";
System.out.println(i+j+s);
System.out.println("-----------------------");
System.out.println(i+s+j);
以上運算各輸出什么捷枯?不妨猜猜
59
-----------------------
293
首先i+j=5,然后5和9自然連接专执,這里涉及到java的自動類型轉換淮捆,此處int型的直接轉成String類型的。第二個依次連接本股,都轉化為String類型的了攀痊。
補充(細節(jié)):看下面的程序:
String s ="ab";
String s1 ="a";
String s2 = s1 +"b";
String s3 ="ab";
System.out.println(s == s2);//false
System.out.println(s2 == s3);//false
System.out.println(s2.hashCode() == s3.hashCode());
String s4 ="ad";
String s5 ="a"+"d";
String s6 ="ad";
System.out.println(s4 == s5);//true
System.out.println(s4 == s6);//true
此處主要是想說明:s1+"b"和"a"+"b"的不同,再看一段代碼:
? ? ? ? ? ? ? ? ? System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
System.out.println(s4.hashCode());
System.out.println(s5.hashCode());
輸出:
97
3105
3105
3107
3107
說明s1+"b"的過程創(chuàng)建了新的對象拄显,所以地址不一樣了苟径。所以用==比較的話,返回的是false躬审。
此處繼續(xù)補充:為什么s1+"b"會產生新的對象棘街?而沒有去常量池查找是否已經存在ab對象,以致于s==s2返回false承边。因為我們說過常量池(下文會講常量池)是在編譯期確定好的遭殉,所以如果我們的語句時String s5 = "ab"的話,這個是在編譯期確定的博助,會去常量池查找险污,而此處我們的語句時s2 = s1+"b",s2的值只有在運行期才能確定,所以不會去常量池查找蛔糯,也就是產生新串拯腮。再次提問:那么這里s2的值是在哪兒分配的呢?堆蚁飒、JVM棧還是運行時常量池动壤?正確回答:s2在堆上分配,因為+的內部實現是用StringBuilder來實現的飒箭。String s2 =?s1+"b" 內部是這樣實現的:String s2 = new StringBuilder(s1).append("b").toString();所以是在堆上來分配的
此處網友cowmich補充:調用s2.hashCode() == s3.hashCode()返回true狼电。我解釋下:
==比較的是他們的地址,s1+"b"會產生一個新的串弦蹂,所以和s和s2用==比,返回false强窖,如果用equals的話凸椿,返回肯定是true,因為equals()比較的是對象的內容(String類是這樣的)翅溺。至于hashCode脑漫,是這樣的:如果沒有重寫Object的hashCode(),那么如果對象調用equals()放回true咙崎,則這兩個對象調用hashCode()后返回的整數一定相等优幸。此處繼續(xù)補充:對于Object類而言,原生的equals()方法褪猛,必須兩個對象的地址和內容都一樣才返回true网杆,同時Object類原生的hashCode()是參照對象的地址和內容根據一定的算法生產的。所以原生的hashCode()只有調用equals()返回true才相等伊滋。而String類不同碳却,String類重寫了Object的equals(),放松了條件笑旺,只要對象地址或者內容相等就返回true昼浦,我們看看源碼:
publicbooleanequals(Object anObject){
if(this== anObject) {
returntrue;
}
if(anObjectinstanceofString) {
? ? String anotherString = (String)anObject;
intn = count;
if(n == anotherString.count) {
charv1[] = value;
charv2[] = anotherString.value;
inti = offset;
intj = anotherString.offset;
while(n-- !=0) {
if(v1[i++] != v2[j++])
returnfalse;
}
returntrue;
? ? }
}
returnfalse;
? ? }
同時,String類重寫了hashCode()方法筒主,只要內容相等关噪,則調用hashCode返回的整數值也相等,所以此處:s3和s2雖然地址不等乌妙,但是內容相等使兔,所以會有:s2.hashCode() == s3.hashCode()返回true。但是這句話反過來講就不一定成立了冠胯,因為畢竟hashCode()只是一種算法火诸。繼續(xù)補充:剛剛說了Object類和String類,此處補充下Integer類:Integer類荠察,返回的哈希碼就是Integer對象里所包含的那個整數的數值置蜀,例如Integer a=new Integer(50),則a.hashCode的值就是50 奈搜。由此可見,2個一樣大小的Integer對象盯荤,返回的哈希碼也一樣馋吗。
補充:應網友 ?KingBoxing ?的要求,我做下關于常量池秋秤、字符串常量池宏粤、運行時常量池的介紹:
常量池一般就是指字符串常量池,是用來做字符串緩存的一種機制灼卢,當我們在程序中寫了形如String s = "abc"這樣的語句后绍哎,JVM會在棧上為我們分配空間,存放變量s和對象”abc“鞋真,當我們再次需要abc對象時崇堰,如果我們寫下:String s1 = "abc"的語句時,JVM會先去常量池中找涩咖,如果不存在海诲,則新創(chuàng)建一個對象。如果存在檩互,則直接將s1指向之前的對象”abc“特幔,此時,如果我們用==來判斷的話闸昨,返回的true蚯斯。這樣做的好處就是節(jié)省內存,系統(tǒng)響應的速度加快零院,(因為省去了對象的創(chuàng)建時間)這也是緩存系統(tǒng)存在的原因溉跃。常量池是針對在編譯期間就確定下來的常量而言的,如上所說的String類的一些對象告抄。但是撰茎,當類被加載后,常量池會被搬到方法區(qū)的運行時常量池打洼,此時就不再是靜態(tài)的了龄糊,那么是不是就不能向常量池中添加新的內容了呢(因為我們剛剛說過,常量池是在編譯期確定好的)募疮?答案是否定的炫惩,我們依然可以在運行時向常量池添加內容!這就是我們說過的String類有個方法叫intern()阿浓,它可以在運行時將新的常量放于常量池他嚷。因為我在上文中已經詳細介紹過intern(),此處不再贅述!
2筋蓖、StringBuffer卸耘、StringBuilder
?????1、初始化
????????StringBuffer和StringBuilder就是所謂的可變字符串類粘咖,共四個構造方法:
? ??????StringBuffer()
? ??????public StringBuffer(int paramInt)
? ??????public StringBuffer(String paramString)
? ??????public StringBuffer(CharSequence paramCharSequence)
????????觀察其源碼發(fā)現蚣抗,使用StringBuffer()時,默認開辟16個字符的長度的空間瓮下,使用public StringBuffer(int paramInt)時開辟指定大小的空間翰铡,使用public StringBuffer(String paramString)時,開辟paramString.length+16大小的空間讽坏。都是調用父類的構造器super()來開辟內存锭魔。這方面StringBuffer和StringBuilder都一樣,且都實現AbstractStringBuilder類路呜。
?????2赂毯、主要方法
????????二者幾乎沒什么區(qū)別,基本都是在調用父類的各個方法拣宰,一個重要的區(qū)別就是StringBuffer是線程安全的,內部的大多數方法前面都有關鍵字synchronized烦感,這樣就會有一定的性能消耗巡社,StringBuilder是非線程安全的,所以效率要高些手趣。
publicstaticvoidmain(String[] args)throwsException{
String string ="0";
intn =10000;
longbegin = System.currentTimeMillis();
for(inti =1; i < n; i++) {
string += i;
}
longend = System.currentTimeMillis();
longbetween = end - begin;
System.out.println("使用String類耗時:"+ between+"ms");
intn1 =10000;
StringBuffer sb =newStringBuffer("0");
longbegin1 = System.currentTimeMillis();
for(intj =1; j < n1; j++) {
sb.append(j);
}
longend1 = System.currentTimeMillis();
longbetween1 = end1 - begin1;
System.out.println("使用StringBuffer類耗時:"+ between1+"ms");
intn2 =10000;
StringBuilder sb2 =newStringBuilder("0");
longbegin2 = System.currentTimeMillis();
for(intk =1; k < n2; k++) {
sb2.append(k);
}
longend2 = System.currentTimeMillis();
longbetween2 = end2 - begin2;
System.out.println("使用StringBuilder類耗時:"+ between2+"ms");
}
輸出:
使用String類耗時:982ms
使用StringBuffer類耗時:2ms
使用StringBuilder類耗時:1ms
雖然這個數字每次執(zhí)行都不一樣晌该,而且每個機子的情況也不一樣,但是有幾點是確定的绿渣,String類消耗的明顯比另外兩個多得多朝群。還有一點就是,StringBuffer要比StringBuilder消耗的多中符,盡管相差不明顯姜胖。
接下來介紹一些常用的方法。
-----------------------public synchronized int length()--------------------------
-------------------------public synchronized int capacity()---------------------------
二者都是獲取字符串的長度淀散,length()獲取的是當前字符串的長度右莱,capacity()獲取的是當前緩沖區(qū)的大小。舉個簡單的例子:
StringBuffer sb =newStringBuffer();
System.out.println(sb.length());;
System.out.println(sb.capacity());
輸出:
0
16
StringBuffer sb =newStringBuffer("hello");
System.out.println(sb.length());;
System.out.println(sb.capacity());
輸出:
5
21
因為默認分配16個字符大小的空間档插,所以不難解釋上面的結果慢蜓。
------------------public boolean equals(Object paramObject)---------------------
StringBuffer sb =newStringBuffer("hello");
StringBuffer sb2 =newStringBuffer("hello");
System.out.println(sb.equals(sb2));
以上程序輸出false,是不是有點驚訝郭膛?記得之前我們的文章說過晨抡,equals()比較的是字符串的內容,按理說此處應該輸出的是true才對。
究其原因耘柱,String類重寫了Object的equals()如捅,所以只需要看內容是否相等即可,但是StringBuffer沒有重寫equals()帆谍,此處的equals()仍然是調用的Object類的伪朽,所以,調用StringBuffer類的equals()汛蝙,只有地址和內容都相等的字符串烈涮,結果才會返回true。
另外StringBuffer有一系列追加窖剑、插入坚洽、刪除字符串的方法,首先append()西土,就是在原來的字符串后面直接追加一個新的串讶舰,和String類相比有明顯的好處:
String類在追加的時候,源字符串不變(這就是為什么說String是不可變的字符串類型)需了,和新串連接后跳昼,重新開辟一個內存。這樣就會造成每次連接一個新串后肋乍,都會讓之前的串報廢鹅颊,因此也造成了不可避免的內存泄露。
//append()
StringBuffer sb =newStringBuffer("helloworld, ");
sb.append("I'm ").append("erqing ").append("who ").append("are you ?");
System.out.println(sb);
//public synchronized StringBuffer insert(int paramInt, Object paramObject)
sb.insert(12,/*9*/"nice! ");
System.out.println(sb);
//public synchronized StringBuffer reverse()
sb.reverse();
System.out.println(sb);
sb.reverse();
System.out.println(sb);
//public synchronized StringBuffer delete(int paramInt1, int paramInt2)
//public synchronized StringBuffer deleteCharAt(int paramInt)
sb.delete(12,18);
System.out.println(sb);
sb.deleteCharAt(5);
System.out.println(sb);
輸出:
helloworld, I'm erqing who are you ?
helloworld, nice! I'm erqing who are you ?
? uoy era ohw gniqre m'I !ecin ,dlrowolleh
helloworld, nice! I'm erqing who are you ?
helloworld, I'm erqing who are you ?
helloorld, I'm erqing who are you ?
-----------------public synchronized void trimToSize()---------------------
該方法用于將多余的緩沖區(qū)空間釋放出來墓造。
StringBuffer sb =newStringBuffer("hello erqing");
System.out.println("length:"+sb.length());
System.out.println("capacity:"+sb.capacity());
sb.trimToSize();
System.out.println("trimTosize:"+sb.capacity());
輸出:
length:12
capacity:28
trimTosize:12
StringBuffer類還有很多方法堪伍,關于字符查找,截取觅闽,替換方面的方法帝雇,有興趣的童鞋可以去研究研究源碼,定會學到不少知識蛉拙!
三尸闸、字符串處理類StringTokenizer
StringTokenizer是java.util包下的一個類,用來對字符串做簡單的處理刘离。
舉個簡單的例子:
String s ="Tonight is the answer !";
StringTokenizer st =newStringTokenizer(s," ");
intcount = st.countTokens();
System.out.println("個數為:"+count);
while(st.hasMoreTokens()) {
String token = st.nextToken();
System.out.println(token);
}
輸出:
個數為:5
Tonight
is
the
answer
!