string,stringbuffer,stringbuild詳解

Java中的字符串處理主要有下面三個類來處理的:StringStringBufferStringBuilderhtml

1、String java

一、String簡介 面試

初始化: 正則表達式

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

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

public class StringTest4 {

	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對象完成建立後,該對象的內容就固定下來了,可是爲何還會有下面這種狀況呢? express

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

public class StringInit {

	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只是一個引用變量,當程序執行完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類的基本初始化方法。

二、String類的一些經常使用方法

字符串是最經常使用的對象,因此,咱們有必要完全的瞭解下它,下面我會列舉經常使用的字符串裏的方法,由於有不少,就不一一列舉。

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

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

  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.     }  
/**
     * Returns the length of this string.
     * The length is equal to the number of <a href="Character.html#unicode">Unicode
     * code units</a> in the string.
     *
     * @return  the length of the sequence of characters represented by this
     *          object.
     */
    public int length() {
        return count;
    }

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

使用方法:

  1. String s = "hello world";  
  2. int length = s.length();  
String s = "hello world";
int length = s.length();

這個比較簡單。

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

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

JDK裏是這樣實現的:

  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.     }  
/**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    public boolean equals(Object anObject) {
	if (this == anObject) {
	    return true;
	}
	if (anObject instanceof String) {
	    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():

  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));  
String s1 = new String("hello world");
String s2 = new String("hello world");
String s3 = new String("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方法的代碼。

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

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

/**
 * 字符串比較:equals()和==的區別
 * @author 二青
 *
 */
public class StringInit {

	public static void main(String[] args) {
		
		String s = "hello world";
		String s1 = new String("hello world");
		String s2 = new String("hello world");
		String s3 = new String("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()和==的區別,一句話:equals()比較的是對象的內容,也就是JVM堆內存中的內容,==比較的是地址,也就是棧內存中的內容。

如上述代碼中,s、s一、s二、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()可實現比較兩個字符串的大小,源碼以下:

  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.   
  10.     if (i == j) {  
  11.         int k = i;  
  12.         int lim = n + i;  
  13.         while (k < lim) {  
  14.         char c1 = v1[k];  
  15.         char c2 = v2[k];  
  16.         if (c1 != c2) {  
  17.             return c1 - c2;  
  18.         }  
  19.         k++;  
  20.         }  
  21.     } else {  
  22.         while (n-- != 0) {  
  23.         char c1 = v1[i++];  
  24.         char c2 = v2[j++];  
  25.         if (c1 != c2) {  
  26.             return c1 - c2;  
  27.         }  
  28.         }  
  29.     }  
  30.     return len1 - len2;  
  31.     }  
public int compareTo(String anotherString) {
	int len1 = count;
	int len2 = anotherString.count;
	int n = Math.min(len1, len2);
	char v1[] = value;
	char v2[] = anotherString.value;
	int i = offset;
	int j = anotherString.offset;

	if (i == j) {
	    int k = i;
	    int lim = n + i;
	    while (k < lim) {
		char c1 = v1[k];
		char c2 = v2[k];
		if (c1 != c2) {
		    return c1 - c2;
		}
		k++;
	    }
	} else {
	    while (n-- != 0) {
		char c1 = v1[i++];
		char c2 = v2[j++];
		if (c1 != c2) {
		    return c1 - c2;
		}
	    }
	}
	return len1 - len2;
    }

compareTo是怎麼實現的呢?

首先,會對兩個字符串左對齊,而後從左到右一次比較,若是相同,繼續,若是不一樣,則計算不一樣的兩個字符的ASCII值的差,返回就好了。與後面的其餘字符不要緊。

舉個例子:

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

/**
 * compareTo()測試
 * @author 二青
 *
 */
public class CompareToTest {

	public static void main(String[] args) {
		String s = "hallo";
		String s2 = "ha";
		String s3 = "haeeo";
		int a = s.compareTo(s2);
		System.out.println("a:"+a);
		int b = s.compareTo(s3);
		System.out.println("b:"+b);
		int c = 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)-----------

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

  1. public char charAt(int index) {  
  2.         if ((index < 0) || (index >= count)) {  
  3.             throw new StringIndexOutOfBoundsException(index);  
  4.         }  
  5.         return value[index + offset];  
  6.     }  
public char charAt(int index) {
        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()------------

  1. public String substring(int beginIndex) {  
  2.     return substring(beginIndex, count);  
  3.     }  
public String substring(int beginIndex) {
	return substring(beginIndex, count);
    }

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

  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.     }  
public String substring(int beginIndex, int endIndex) {
	if (beginIndex < 0) {
	    throw new StringIndexOutOfBoundsException(beginIndex);
	}
	if (endIndex > count) {
	    throw new StringIndexOutOfBoundsException(endIndex);
	}
	if (beginIndex > endIndex) {
	    throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
	}
	return ((beginIndex == 0) && (endIndex == count)) ? this :
	    new String(offset + beginIndex, endIndex - beginIndex, value);
    }

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

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

	public static void main(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)返回的結果同樣,觀察下源碼,不難發現的區別:

  1. public CharSequence subSequence(int beginIndex, int endIndex) {  
  2.        return this.substring(beginIndex, endIndex);  
  3.    }  
  4.    }  
public CharSequence subSequence(int beginIndex, int endIndex) {
        return this.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)-------------------

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

	    while (++i < len) {
		if (val[off + i] == oldChar) {
		    break;
		}
	    }
	    if (i < len) {
		char buf[] = new char[len];
		for (int j = 0 ; j < i ; j++) {
		    buf[j] = val[off+j];
		}
		while (i < len) {
		    char c = val[off + i];
		    buf[i] = (c == oldChar) ? newChar : c;
		    i++;
		}
		return new String(0, len, buf);
	    }
	}
	return this;
    }
  1. public String replaceAll(String regex, String replacement) {  
  2.     return Pattern.compile(regex).matcher(this).replaceAll(replacement);  
  3.     }  
public String replaceAll(String regex, String replacement) {
 	return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }

前者參數爲兩個字符串,用newChar替換原串裏的全部oldChar。

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

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

public class ReplaceTest {

	public static void main(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可知,參數是個正則表達式。請看下面的例子:

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

public class SpiltTest {

	public static void main(String[] args) {
		String s = "hello world";
		String s1 = "hello.worldd";
		String[] s2 = s.split(" ");
		String[] s3 = s1.split("\\.");
		for(int i=0; i<s2.length; i++){
			System.out.print(s2[i]+" ");
		}
		System.out.println();
		for(int j=0; j<s3.length; 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文檔裏的描述:

  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.  
Returns a canonical representation for the string object.
    A pool of strings, initially empty, is maintained privately by the
    class String.When the intern method is invoked, if the pool already contains a
    string equal to this String object as determined by
    theequals(Object) method, then the string from the pool is
    returned. Otherwise, this String object is added to the
    pool and a reference to this String object is returned.
    It follows that for any two strings s and t,
    s.intern()==t.intern() is true if and only if s.equals(t) is true.
    All literal strings and string-valued constant expressions are interned. 
    @return  a string that has the same contents as this string, but is
    guaranteed to be from a pool of unique strings.


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

  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. }  
public class StringTest {
	public static void main(String[] args) {
	    String s = new String("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和s一、s2指向同一個對象,因此s1==s3返回的是true。

intern()作到了一個很不尋常的行爲:在運行期動態的在方法區建立對象,通常只有像new關鍵字能夠在運行期在堆上面建立對象,因此此處比較特殊。屬於及時編譯的概念。

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

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

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

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

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

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

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

作一個簡單的測試,就能夠看得出來:

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

public class StringSpilt {
	public static void main(String[] args) {
		String s = "helloworld";
		String[] s2 = s.split("abc");
		for (int i = 0; i < s2.length; i++) {
			System.out.println(s2[i] + " " + i);
		}
	}
}

輸出:helloworld 0

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

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

首先看一下題的類型:

  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);  
int i = 2;
int j = 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類型的了。

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

  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  
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"的不一樣,再看一段代碼:

  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());  
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"的過程建立了新的對象,因此地址不同了。因此用==比較的話,返回的是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,咱們看看源碼:

  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.     }  
public boolean equals(Object anObject) {
	if (this == anObject) {
	    return true;
	}
	if (anObject instanceof String) {
	    String anotherString = (String)anObject;
	    int n = count;
	    if (n == anotherString.count) {
		char v1[] = value;
		char v2[] = anotherString.value;
		int i = offset;
		int j = anotherString.offset;
		while (n-- != 0) {
		    if (v1[i++] != v2[j++])
			return false;
		}
		return true;
	    }
	}
	return false;
    }

同時,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

郵箱:xtfggef@gmail.com    微博:http://weibo.com/xtfggef

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

2、StringBuffer、StringBuilder

  一、初始化

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類。

  二、主要方法

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

  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.   
  12.         int n1 = 10000;  
  13.         StringBuffer sb = new StringBuffer("0");  
  14.         long begin1 = System.currentTimeMillis();  
  15.         for (int j = 1; j < n1; j++) {  
  16.             sb.append(j);  
  17.         }  
  18.         long end1 = System.currentTimeMillis();  
  19.         long between1 = end1 - begin1;  
  20.         System.out.println("使用StringBuffer類耗時:" + between1+"ms");  
  21.   
  22.         int n2 = 10000;  
  23.         StringBuilder sb2 = new StringBuilder("0");  
  24.         long begin2 = System.currentTimeMillis();  
  25.         for (int k = 1; k < n2; k++) {  
  26.             sb2.append(k);  
  27.         }  
  28.         long end2 = System.currentTimeMillis();  
  29.         long between2 = end2 - begin2;  
  30.         System.out.println("使用StringBuilder類耗時:" + between2+"ms");  
  31.     }  
public static void main(String[] args) throws Exception {
		String string = "0";
		int n = 10000;
		long begin = System.currentTimeMillis();
		for (int i = 1; i < n; i++) {
			string += i;
		}
		long end = System.currentTimeMillis();
		long between = end - begin;
		System.out.println("使用String類耗時:" + between+"ms");

		int n1 = 10000;
		StringBuffer sb = new StringBuffer("0");
		long begin1 = System.currentTimeMillis();
		for (int j = 1; j < n1; j++) {
			sb.append(j);
		}
		long end1 = System.currentTimeMillis();
		long between1 = end1 - begin1;
		System.out.println("使用StringBuffer類耗時:" + between1+"ms");

		int n2 = 10000;
		StringBuilder sb2 = new StringBuilder("0");
		long begin2 = System.currentTimeMillis();
		for (int k = 1; k < n2; k++) {
			sb2.append(k);
		}
		long end2 = System.currentTimeMillis();
		long between2 = end2 - begin2;
		System.out.println("使用StringBuilder類耗時:" + between2+"ms");
	}

輸出:

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

雖然這個數字每次執行都不同,並且每一個機子的狀況也不同,可是有幾點是肯定的,String類消耗的明顯比另外兩個多得多。還有一點就是,StringBuffer要比StringBuilder消耗的多,儘管相差不明顯。

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

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

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

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

  1. StringBuffer sb = new StringBuffer();  
  2.         System.out.println(sb.length());;  
  3.         System.out.println(sb.capacity());  
StringBuffer sb = new StringBuffer();
		System.out.println(sb.length());;
		System.out.println(sb.capacity());
輸出:

0

16

  1. StringBuffer sb = new StringBuffer("hello");  
  2.         System.out.println(sb.length());;  
  3.         System.out.println(sb.capacity());  
StringBuffer sb = new StringBuffer("hello");
		System.out.println(sb.length());;
		System.out.println(sb.capacity());
輸出:

5

21

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

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

  1. StringBuffer sb = new StringBuffer("hello");  
  2.         StringBuffer sb2 = new StringBuffer("hello");  
  3.         System.out.println(sb.equals(sb2));  
StringBuffer sb = new StringBuffer("hello");
		StringBuffer sb2 = new StringBuffer("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是不可變的字符串類型),和新串鏈接後,從新開闢一個內存。這樣就會形成每次鏈接一個新串後,都會讓以前的串報廢,所以也形成了不可避免的內存泄露。

  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(1218);  
  16. System.out.println(sb);  
  17. sb.deleteCharAt(5);  
  18. System.out.println(sb);  
//append()
		StringBuffer sb = new StringBuffer("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()---------------------

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

  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());  
StringBuffer sb = new StringBuffer("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類還有不少方法,關於字符查找,截取,替換方面的方法,有興趣的童鞋能夠去研究研究源碼,定會學到很多知識!

3、字符串處理類StringTokenizer

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

舉個簡單的例子:

  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.         }  
String s = "Tonight is the answer !";
		StringTokenizer st = new StringTokenizer(s," ");
		int count = st.countTokens();
		System.out.println("個數爲:"+count);
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			System.out.println(token);
 		}
輸出:

個數爲:5 Tonight is the answer !

相關文章
相關標籤/搜索