1. 程式人生 > >Java之美[從菜鳥到高手演變]之字符串

Java之美[從菜鳥到高手演變]之字符串

tween gin new 有關 菜鳥 article user 再看 use

一、String

1、String簡介

初始化:

一般由String聲明的字符串,長度是不可變的,這也是它與StringBuffer和StringBuilder最直觀的一個區別。一般初始化方式:String s = "hello world";經過這條語句,JVM的棧內存中產生一個s變量,堆內存中產生hello world字符串對象。s指向了hello world的地址。像上面這種方式產生的字符串屬於直接量字符串對象,JVM在處理這類字符串的時候,會進行緩存,產生時放入字符串池,當程序需要再次使用的時候,無需重新創建一個新的字符串,而是直接指向已存在的字符串。看下面程序:

[java]
view plain copy
  1. package com.xtfggef.string;
  2. public class StringTest4 {
  3. public static void main(String[] args) {
  4. String s = "hello world";
  5. String s2 = "hello world";
  6. System.out.println(s == s2);
  7. }
  8. }

該程序輸出:true 因為s和s2都指向了hello world字符串,他們的地址是同一個。 我們常說,String的一個很大的特點,就是它是一個“不可變的字符串”,就是說,當一個String對象完成創建後,該對象的內容就固定下來了,但是為什麽還會有下面這種情況呢?

[java] view plain copy
  1. package com.xtfggef.string;
  2. public class StringInit {
  3. public static void main(String[] args) {
  4. String str = "I like";//---------1--------
  5. System.out.println(System.identityHashCode(str));
  6. str = str + "java";//--------2---------
  7. System.out.println(System.identityHashCode(str));
  8. }
  9. }

該程序輸出:

14576877
12677476

說明:str似乎是變了,這是為什麽呢?其實是這樣的:str只是一個引用變量,當程序執行完1後,str指向“I like”。當程序執行完2之後,連接運算符會將兩個字符串連在一起,並且讓str指向新的串:"I like java",所以,從這裏應該可以看得出來,最初的對象確實沒有改變,只是str所指向的對象在不斷改變。

String對象的另一種初始化方式,就是采用String類提供的構造方法進行初始化。String類提供了16種構造方法,常用的有五種:

String() --------- 初始化一個String對象,表示一個空字符序列

String(String value) --------- 利用一個直接量創建一個新串

String(char[] value) --------- 利用一個字符數組創建

String(char[] value,int offset,int count) --------- 截取字符數組,從offset開始count個字符創建

String(StringBuffer buffer) --------- 利用StringBuffer創建

形如:

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()--------

該方法用於獲取字符串的長度,實現如下:

[java] view plain copy
  1. /**
  2. * Returns the length of this string.
  3. * The length is equal to the number of <a href="Character.html#unicode">Unicode
  4. * code units</a> in the string.
  5. *
  6. * @return the length of the sequence of characters represented by this
  7. * object.
  8. */
  9. public int length() {
  10. return count;
  11. }

這是JDK種的原始實現,count在String類裏被定義為一個整型常量:private final int count;並且不論采用哪種構造方法,最終都會為count賦值。

使用方法:

[java] view plain copy
  1. String s = "hello world";
  2. int length = s.length();

這個比較簡單。

-----------public boolean equals(Object anObject)-----------

該方法用於比較給定對象是否與String相等。

JDK裏是這樣實現的:

[java] view plain copy
  1. /**
  2. * Compares this string to the specified object. The result is {@code
  3. * true} if and only if the argument is not {@code null} and is a {@code
  4. * String} object that represents the same sequence of characters as this
  5. * object.
  6. *
  7. * @param anObject
  8. * The object to compare this {@code String} against
  9. *
  10. * @return {@code true} if the given object represents a {@code String}
  11. * equivalent to this string, {@code false} otherwise
  12. *
  13. * @see #compareTo(String)
  14. * @see #equalsIgnoreCase(String)
  15. */
  16. public boolean equals(Object anObject) {
  17. if (this == anObject) {
  18. return true;
  19. }
  20. if (anObject instanceof String) {
  21. String anotherString = (String)anObject;
  22. int n = count;
  23. if (n == anotherString.count) {
  24. char v1[] = value; //---------1---------
  25. char v2[] = anotherString.value;//-------2----------
  26. int i = offset;
  27. int j = anotherString.offset;
  28. while (n-- != 0) {
  29. if (v1[i++] != v2[j++])
  30. return false;
  31. }
  32. return true;
  33. }
  34. }
  35. return false;
  36. }

從1和2處也看出來,String的底層是基於字符數組的。我們可以像下面這種方式使用equals():

[java] view plain copy
  1. String s1 = new String("hello world");
  2. String s2 = new String("hello world");
  3. String s3 = new String("hello");
  4. System.out.println(s1.equals(s2));;
  5. 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方法的代碼。

稍後我再做說明,請先再看個例子:

[java] view plain copy
  1. package com.xtfggef.string;
  2. /**
  3. * 字符串比較:equals()和==的區別
  4. * @author 二青
  5. *
  6. */
  7. public class StringInit {
  8. public static void main(String[] args) {
  9. String s = "hello world";
  10. String s1 = new String("hello world");
  11. String s2 = new String("hello world");
  12. String s3 = new String("hello");
  13. String s4 = "hello world";
  14. System.out.println(s.equals(s1));;
  15. System.out.println(s1.equals(s2));
  16. System.out.println(s1.equals(s3));
  17. System.out.println("------------------");
  18. System.out.println(s == s1);
  19. System.out.println(s == s3);
  20. System.out.println(s == s4);
  21. }
  22. }

輸出:

true
true
false
------------------
false
false
true

此處驗證了一個問題,就是比較方法equals()和==的區別,一句話: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<T>,而Comparable接口裏有唯一的方法:public int compareTo(T o)。所以,String類還有另一個字符串比較方法:compareTo()

-----------------public int compareTo(String anotherString)---------------

compareTo()可實現比較兩個字符串的大小,源碼如下:

[java] view plain copy
  1. public int compareTo(String anotherString) {
  2. int len1 = count;
  3. int len2 = anotherString.count;
  4. int n = Math.min(len1, len2);
  5. char v1[] = value;
  6. char v2[] = anotherString.value;
  7. int i = offset;
  8. int j = anotherString.offset;
  9. if (i == j) {
  10. int k = i;
  11. int lim = n + i;
  12. while (k < lim) {
  13. char c1 = v1[k];
  14. char c2 = v2[k];
  15. if (c1 != c2) {
  16. return c1 - c2;
  17. }
  18. k++;
  19. }
  20. } else {
  21. while (n-- != 0) {
  22. char c1 = v1[i++];
  23. char c2 = v2[j++];
  24. if (c1 != c2) {
  25. return c1 - c2;
  26. }
  27. }
  28. }
  29. return len1 - len2;
  30. }

compareTo是怎麽實現的呢?

首先,會對兩個字符串左對齊,然後從左到右一次比較,如果相同,繼續,如果不同,則計算不同的兩個字符的ASCII值的差,返回就行了。與後面的其他字符沒關系。

舉個例子:

[java] view plain copy
  1. package com.xtfggef.string;
  2. /**
  3. * compareTo()測試
  4. * @author 二青
  5. *
  6. */
  7. public class CompareToTest {
  8. public static void main(String[] args) {
  9. String s = "hallo";
  10. String s2 = "ha";
  11. String s3 = "haeeo";
  12. int a = s.compareTo(s2);
  13. System.out.println("a:"+a);
  14. int b = s.compareTo(s3);
  15. System.out.println("b:"+b);
  16. int c = s2.compareTo(s3);
  17. System.out.println("c:"+c);
  18. }
  19. }

程序輸出:

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)-----------

獲取指定位置的字符,比較容易理解,源碼為:

[java] view plain copy
  1. public char charAt(int index) {
  2. if ((index < 0) || (index >= count)) {
  3. throw new StringIndexOutOfBoundsException(index);
  4. }
  5. return value[index + offset];
  6. }

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()------------

[java] view plain copy
  1. public String substring(int beginIndex) {
  2. return substring(beginIndex, count);
  3. }

用於截取字符串,此處與另一個方法對比:

[java] view plain copy
  1. public String substring(int beginIndex, int endIndex) {
  2. if (beginIndex < 0) {
  3. throw new StringIndexOutOfBoundsException(beginIndex);
  4. }
  5. if (endIndex > count) {
  6. throw new StringIndexOutOfBoundsException(endIndex);
  7. }
  8. if (beginIndex > endIndex) {
  9. throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
  10. }
  11. return ((beginIndex == 0) && (endIndex == count)) ? this :
  12. new String(offset + beginIndex, endIndex - beginIndex, value);
  13. }

前者調用後者來實現,前者截取從指定位置到字符串結束的子字符串,後者截取從指定位置開始,到endIndex-1位置的子字符串。

[java] view plain copy
  1. public class CompareToTest {
  2. public static void main(String[] args) {
  3. String s = "helloworld";
  4. String s1 = s.substring(2);
  5. String s2 = s.substring(2, 7);
  6. String s3 = (String) s.subSequence(2, 7);
  7. System.out.print("s1:"+s1+"\n"+"s2:"+s2+"\n"+"s3:"+s3);
  8. }
  9. }

輸出:

s1:lloworld
s2:llowo
s3:llowo

細心的讀者應該看出來,該類裏面包含一個subSequence(),而且該方法與substring(int,int)返回的結果一樣,觀察下源碼,不難發現的區別:

[java] view plain copy
  1. public CharSequence subSequence(int beginIndex, int endIndex) {
  2. return this.substring(beginIndex, endIndex);
  3. }
  4. }

其實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)-------------------

[java] view plain copy
  1. public String replace(char oldChar, char newChar) {
  2. if (oldChar != newChar) {
  3. int len = count;
  4. int i = -1;
  5. char[] val = value; /* avoid getfield opcode */
  6. int off = offset; /* avoid getfield opcode */
  7. while (++i < len) {
  8. if (val[off + i] == oldChar) {
  9. break;
  10. }
  11. }
  12. if (i < len) {
  13. char buf[] = new char[len];
  14. for (int j = 0 ; j < i ; j++) {
  15. buf[j] = val[off+j];
  16. }
  17. while (i < len) {
  18. char c = val[off + i];
  19. buf[i] = (c == oldChar) ? newChar : c;
  20. i++;
  21. }
  22. return new String(0, len, buf);
  23. }
  24. }
  25. return this;
  26. }

[java] view plain copy
  1. public String replaceAll(String regex, String replacement) {
  2. return Pattern.compile(regex).matcher(this).replaceAll(replacement);
  3. }

前者參數為兩個字符串,用newChar替換原串裏的所有oldChar。

後者從第一個參數可以看出,需要替換的東西可以用正則表達式描述。例子如下:

[java] view plain copy
  1. package com.xtfggef.string;
  2. public class ReplaceTest {
  3. public static void main(String[] args) {
  4. String s = "hello world";
  5. String s1 = s.replace("l", "d");
  6. System.out.println(s1);
  7. String s2 = "a78e5opx587";
  8. String s3 = s2.replaceAll("[0-9]", "");//用空串替換原串裏所有的0-9的數字
  9. System.out.println(s3);
  10. }
  11. }


輸出:

heddo wordd
aeopx

-------------public String[] split(String regex)-----------

該方法用於分割字符串,得到一個String類型的數組,根據regex可知,參數是個正則表達式。請看下面的例子:

[java] view plain copy
  1. package com.xtfggef.string;
  2. public class SpiltTest {
  3. public static void main(String[] args) {
  4. String s = "hello world";
  5. String s1 = "hello.worldd";
  6. String[] s2 = s.split(" ");
  7. String[] s3 = s1.split("\\.");
  8. for(int i=0; i<s2.length; i++){
  9. System.out.print(s2[i]+" ");
  10. }
  11. System.out.println();
  12. for(int j=0; j<s3.length; j++){
  13. System.out.print(s3[j]+" ");
  14. }
  15. }
  16. }

輸出:

hello world
hello worldd
關於spilt()的其他重載方法,可參見JDK的String類的實現。

spilt()需要註意的事項,就是當分隔符為 . 的話,處理起來不一樣,必須寫成\\.因為.是正則表達式裏的一個特殊符號,必須進行轉義

--------------------public native String intern();--------------------(補充知識點:經網友java2000_wl提醒,特此補充,歡迎廣大讀者及時提出建議,我必將虛心接受!)

intern()方法和前面說的equals()方法關系密切,從public native String intern()看出,它是Java的本地方法,我們先來看看Java文檔裏的描述:

[java] view plain copy
  1. Returns a canonical representation for the string object.
  2. A pool of strings, initially empty, is maintained privately by the
  3. class String.When the intern method is invoked, if the pool already contains a
  4. string equal to this String object as determined by
  5. theequals(Object) method, then the string from the pool is
  6. returned. Otherwise, this String object is added to the
  7. pool and a reference to this String object is returned.
  8. It follows that for any two strings s and t,
  9. s.intern()==t.intern() is true if and only if s.equals(t) is true.
  10. All literal strings and string-valued constant expressions are interned.
  11. @return a string that has the same contents as this string, but is
  12. guaranteed to be from a pool of unique strings.


意思就是說,返回字符串一個規範的表示。進一步解釋:有兩個字符串s和t,s.equals(t),則s.intern()==t.intern().舉個例子:

[java] view plain copy
  1. public class StringTest {
  2. public static void main(String[] args) {
  3. String s = new String("abc");
  4. String s1 = "abc";
  5. String s2 = "abc";
  6. String s3 = s.intern();
  7. System.out.println(s == s1);//false
  8. System.out.println(s == s2);//false
  9. System.out.println(s == s3);//false
  10. System.out.println(s1 == s3);//true
  11. }
  12. }


輸出結果如註釋所示,前兩個結果前面已經說的很清楚了,現在拿最後一個說明,首先看看s3 = s.intern()這句,當調用s.intern()這句的時候,先去字符串常量池中找,是否有abc這個串,如果沒有,則新增,同時返回引用,如果有,則返回已經存在的引用,此處s1和s2都指向常量池中的abc對象,所以此處是存在的,調用s.intern()後,s3和s1、s2指向同一個對象,所以s1==s3返回的是true。

intern()做到了一個很不尋常的行為:在運行期動態的在方法區創建對象,一般只有像new關鍵字可以在運行期在堆上面創建對象,所以此處比較特殊。屬於及時編譯的概念。

一般常見的字符串處理函數就這些,其它的還有很多,就不一一列舉。

3、一些常見的問題,處理結果

在我們日常的開發中,總會遇到一些問題,在此我總結一下:

String s = "123" + "456"內存中產生幾個字符串對象?

這是個比較有爭議的問題,面試的時候,老師還挺喜歡問,論壇上大家說幾個的也有,我給大家分析一下,因為我們前面有提到Java字符串的緩存機制,編譯器在編譯的時候會進行優化,所以在編譯的過程中123和456被合成了一個字符串"123456",因此,如果緩存池中目前沒有123456這個對象,那麽會產生一個,即""123456",且棧中產生一個引用s指向它,如果緩存池中已經存在"123456",那麽將產生0個對象,直接用s指向它。

如果spilt()函數的參數在要分割的字符串中沒有怎麽辦?如String s = "helloworld" ,我現在調用String[] s2 = s.spilt("abc"),返回什麽?

這個問題是我曾經參加紅帽軟件面試的時候遇到的相關題,當時懵了,像這樣的題目,如果不親自遇到過,或者看過源代碼,很難準確的寫出來。

做一個簡單的測試,就可以看得出來:

[java] view plain copy
  1. package com.xtfggef.string;
  2. public class StringSpilt {
  3. public static void main(String[] args) {
  4. String s = "helloworld";
  5. String[] s2 = s.split("abc");
  6. for (int i = 0; i < s2.length; i++) {
  7. System.out.println(s2[i] + " " + i);
  8. }
  9. }
  10. }

輸出:helloworld 0

說明當遇到源字符串中沒有的字符時,會把它整個串放入到數組中。spilt()的內部實現還是挺復雜的,多層嵌套,不便於放到這兒分析。

關於字符串自動類型轉換分析

首先看一下題的類型:

[java] view plain copy
  1. int i = 2;
  2. int j = 3;
  3. String s = "9";
  4. System.out.println(i+j+s);
  5. System.out.println("-----------------------");
  6. System.out.println(i+s+j);

以上運算各輸出什麽?不妨猜猜
59
-----------------------
293

首先i+j=5,然後5和9自然連接,這裏涉及到java的自動類型轉換,此處int型的直接轉成String類型的。第二個依次連接,都轉化為String類型的了。

補充(細節):看下面的程序:

[java] view plain copy
  1. String s = "ab";
  2. String s1 = "a";
  3. String s2 = s1 + "b";
  4. String s3 = "ab";
  5. System.out.println(s == s2);//false
  6. System.out.println(s2 == s3);//false
  7. System.out.println(s2.hashCode() == s3.hashCode());
  8. String s4 = "ad";
  9. String s5 = "a" + "d";
  10. String s6 = "ad";
  11. System.out.println(s4 == s5);//true
  12. System.out.println(s4 == s6);//true



此處主要是想說明:s1+"b"和"a"+"b"的不同,再看一段代碼:

[java] view plain copy
  1. System.out.println(s1.hashCode());
  2. System.out.println(s2.hashCode());
  3. System.out.println(s3.hashCode());
  4. System.out.println(s4.hashCode());
  5. System.out.println(s5.hashCode());

輸出:

97
3105
3105
3107
3107

說明s1+"b"的過程創建了新的對象,所以地址不一樣了。所以用==比較的話,返回的是false。

此處繼續補充:為什麽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()後返回的整數一定相等。此處繼續補充:對於Object類而言,原生的equals()方法,必須兩個對象的地址和內容都一樣才返回true,同時Object類原生的hashCode()是參照對象的地址和內容根據一定的算法生產的。所以原生的hashCode()只有調用equals()返回true才相等。而String類不同,String類重寫了Object的equals(),放松了條件,只要對象地址或者內容相等就返回true,我們看看源碼:

[java] view plain copy
  1. public boolean equals(Object anObject) {
  2. if (this == anObject) {
  3. return true;
  4. }
  5. if (anObject instanceof String) {
  6. String anotherString = (String)anObject;
  7. int n = count;
  8. if (n == anotherString.count) {
  9. char v1[] = value;
  10. char v2[] = anotherString.value;
  11. int i = offset;
  12. int j = anotherString.offset;
  13. while (n-- != 0) {
  14. if (v1[i++] != v2[j++])
  15. return false;
  16. }
  17. return true;
  18. }
  19. }
  20. return false;
  21. }

同時,String類重寫了hashCode()方法,只要內容相等,則調用hashCode返回的整數值也相等,所以此處:s3和s2雖然地址不等,但是內容相等,所以會有:s2.hashCode() == s3.hashCode()返回true。但是這句話反過來講就不一定成立了,因為畢竟hashCode()只是一種算法。繼續補充:剛剛說了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會先去常量池中找,如果不存在,則新創建一個對象。如果存在,則直接將s1指向之前的對象”abc“,此時,如果我們用==來判斷的話,返回的true。這樣做的好處就是節省內存,系統響應的速度加快,(因為省去了對象的創建時間)這也是緩存系統存在的原因。常量池是針對在編譯期間就確定下來的常量而言的,如上所說的String類的一些對象。但是,當類被加載後,常量池會被搬到方法區的運行時常量池,此時就不再是靜態的了,那麽是不是就不能向常量池中添加新的內容了呢(因為我們剛剛說過,常量池是在編譯期確定好的)?答案是否定的,我們依然可以在運行時向常量池添加內容!這就是我們說過的String類有個方法叫intern(),它可以在運行時將新的常量放於常量池。因為我在上文中已經詳細介紹過intern(),此處不再贅述!

個人的力量是有限的,歡迎大家積極補充,同時也歡迎讀者隨時批評指正!

有問題請聯系:egg

郵箱:[email protected] 微博:http://weibo.com/xtfggef

You have to believe in yourself.That‘s the secretof success!

二、StringBuffer、StringBuilder

1、初始化

StringBuffer和StringBuilder就是所謂的可變字符串類,共四個構造方法:

StringBuffer()

public StringBuffer(int paramInt)

public StringBuffer(String paramString)

public StringBuffer(CharSequence paramCharSequence)

觀察其源碼發現,使用StringBuffer()時,默認開辟16個字符的長度的空間,使用public StringBuffer(int paramInt)時開辟指定大小的空間,使用public StringBuffer(String paramString)時,開辟paramString.length+16大小的空間。都是調用父類的構造器super()來開辟內存。這方面StringBuffer和StringBuilder都一樣,且都實現AbstractStringBuilder類。

2、主要方法

二者幾乎沒什麽區別,基本都是在調用父類的各個方法,一個重要的區別就是StringBuffer是線程安全的,內部的大多數方法前面都有關鍵字synchronized,這樣就會有一定的性能消耗,StringBuilder是非線程安全的,所以效率要高些。

[java] view plain copy
  1. public static void main(String[] args) throws Exception {
  2. String string = "0";
  3. int n = 10000;
  4. long begin = System.currentTimeMillis();
  5. for (int i = 1; i < n; i++) {
  6. string += i;
  7. }
  8. long end = System.currentTimeMillis();
  9. long between = end - begin;
  10. System.out.println("使用String類耗時:" + between+"ms");
  11. int n1 = 10000;
  12. StringBuffer sb = new StringBuffer("0");
  13. long begin1 = System.currentTimeMillis();
  14. for (int j = 1; j < n1; j++) {
  15. sb.append(j);
  16. }
  17. long end1 = System.currentTimeMillis();
  18. long between1 = end1 - begin1;
  19. System.out.println("使用StringBuffer類耗時:" + between1+"ms");
  20. int n2 = 10000;
  21. StringBuilder sb2 = new StringBuilder("0");
  22. long begin2 = System.currentTimeMillis();
  23. for (int k = 1; k < n2; k++) {
  24. sb2.append(k);
  25. }
  26. long end2 = System.currentTimeMillis();
  27. long between2 = end2 - begin2;
  28. System.out.println("使用StringBuilder類耗時:" + between2+"ms");
  29. }

輸出:

使用String類耗時:982ms
使用StringBuffer類耗時:2ms
使用StringBuilder類耗時:1ms

雖然這個數字每次執行都不一樣,而且每個機子的情況也不一樣,但是有幾點是確定的,String類消耗的明顯比另外兩個多得多。還有一點就是,StringBuffer要比StringBuilder消耗的多,盡管相差不明顯。

接下來介紹一些常用的方法。

-----------------------public synchronized int length()--------------------------

-------------------------public synchronized int capacity()---------------------------

二者都是獲取字符串的長度,length()獲取的是當前字符串的長度,capacity()獲取的是當前緩沖區的大小。舉個簡單的例子:

[java] view plain copy
  1. StringBuffer sb = new StringBuffer();
  2. System.out.println(sb.length());;
  3. System.out.println(sb.capacity());

輸出:

0

16

[java] view plain copy
  1. StringBuffer sb = new StringBuffer("hello");
  2. System.out.println(sb.length());;
  3. System.out.println(sb.capacity());

輸出:

5

21

因為默認分配16個字符大小的空間,所以不難解釋上面的結果。

------------------public boolean equals(Object paramObject)---------------------

[java] view plain copy
  1. StringBuffer sb = new StringBuffer("hello");
  2. StringBuffer sb2 = new StringBuffer("hello");
  3. System.out.println(sb.equals(sb2));

以上程序輸出false,是不是有點驚訝?記得之前我們的文章說過,equals()比較的是字符串的內容,按理說此處應該輸出的是true才對。

究其原因,String類重寫了Object的equals(),所以只需要看內容是否相等即可,但是StringBuffer沒有重寫equals(),此處的equals()仍然是調用的Object類的,所以,調用StringBuffer類的equals(),只有地址和內容都相等的字符串,結果才會返回true。

另外StringBuffer有一系列追加、插入、刪除字符串的方法,首先append(),就是在原來的字符串後面直接追加一個新的串,和String類相比有明顯的好處:

String類在追加的時候,源字符串不變(這就是為什麽說String是不可變的字符串類型),和新串連接後,重新開辟一個內存。這樣就會造成每次連接一個新串後,都會讓之前的串報廢,因此也造成了不可避免的內存泄露。

[java] view plain copy
  1. //append()
  2. StringBuffer sb = new StringBuffer("helloworld, ");
  3. sb.append("I‘m ").append("erqing ").append("who ").append("are you ?");
  4. System.out.println(sb);
  5. //public synchronized StringBuffer insert(int paramInt, Object paramObject)
  6. sb.insert(12, /*9*/"nice! ");
  7. System.out.println(sb);
  8. //public synchronized StringBuffer reverse()
  9. sb.reverse();
  10. System.out.println(sb);
  11. sb.reverse();
  12. System.out.println(sb);
  13. //public synchronized StringBuffer delete(int paramInt1, int paramInt2)
  14. //public synchronized StringBuffer deleteCharAt(int paramInt)
  15. sb.delete(12, 18);
  16. System.out.println(sb);
  17. sb.deleteCharAt(5);
  18. 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()---------------------

該方法用於將多余的緩沖區空間釋放出來。

[java] view plain copy
  1. StringBuffer sb = new StringBuffer("hello erqing");
  2. System.out.println("length:"+sb.length());
  3. System.out.println("capacity:"+sb.capacity());
  4. sb.trimToSize();
  5. System.out.println("trimTosize:"+sb.capacity());

輸出:

length:12
capacity:28
trimTosize:12

StringBuffer類還有很多方法,關於字符查找,截取,替換方面的方法,有興趣的童鞋可以去研究研究源碼,定會學到不少知識!

三、字符串處理類StringTokenizer

StringTokenizer是java.util包下的一個類,用來對字符串做簡單的處理。

舉個簡單的例子:

[java] view plain copy
  1. String s = "Tonight is the answer !";
  2. StringTokenizer st = new StringTokenizer(s," ");
  3. int count = st.countTokens();
  4. System.out.println("個數為:"+count);
  5. while (st.hasMoreTokens()) {
  6. String token = st.nextToken();
  7. System.out.println(token);
  8. }

輸出:

個數為:5
Tonight
is
the
answer
!

轉自http://blog.csdn.net/zhangerqing/article/details/8093919

Java之美[從菜鳥到高手演變]之字符串