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


寫程序就像生活,有酸甜苦辣,關鍵在於過程,任何事情的過程都是美好的,是值得我們回味的!有人說,編程是一種藝術,藝術出於生活卻高於生活,每一個細節都值得細細品味...程序員無非就是兩件事:學習和分享!獨樂樂與人樂樂,孰樂?於是,程序員對着電腦,開始寫作。一盞台燈、一杯清茶,躺在旁邊,默默的,聽着。從本次博文起,進行Java之美[從菜鳥到高手演變]系列,本文系第一篇,Java字符串的處理。字符串在任何語言中都是一個非常重要的概念,我們有必要掌握她的一切!

本博客永久更新,如有轉載,

請說明出處:http://blog.csdn.net/zhangerqing 或者 http://www.cnblogs.com/xtfggef/archive/2012/12/13/java_string.html

如有問題,請聯系本人: egg

郵箱:xtfggef@gmail.com

微博:http://weibo.com/xtfggef

如需進行探討,除上面的方式外,請到我的原博CSDN博客(http://blog.csdn.net/zhangerqing)進行留言!

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

String

StringBuffer

StringBuilder

一、String

1、String簡介

初始化:

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

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對象完成創建后,該對象的內容就固定下來了,但是為什么還會有下面這種情況呢?

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類的基本初始化方法。

2、String類的一些常用方法

字符串是最常用的對象,所以,我們有必要徹底的了解下它,下面我會列舉常用的字符串里的方法,因為有很多,就不一一列舉。

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

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

/**
     * 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賦值。

使用方法:

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

這個比較簡單。

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

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

JDK里是這樣實現的:

/**
     * 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():

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方法的代碼。

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

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、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()可實現比較兩個字符串的大小,源碼如下:

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值的差,返回就行了。與后面的其他字符沒關系。

舉個例子:

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

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

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

public String substring(int beginIndex) {
	return substring(beginIndex, count);
    }

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

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位置的子字符串。

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

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

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;
    }
public String replaceAll(String regex, String replacement) {
 	return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }

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

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

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

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文檔里的描述:

    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().舉個例子:

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和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"),返回什么?

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

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

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()的內部實現還是挺復雜的,多層嵌套,不便於放到這兒分析。

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

首先看一下題的類型:

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類型的了。

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

 

		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"的過程創建了新的對象,所以地址不一樣了。所以用==比較的話,返回的是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,我們看看源碼:

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!

二、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是非線程安全的,所以效率要高些。

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()獲取的是當前緩沖區的大小。舉個簡單的例子:

StringBuffer sb = new StringBuffer();
		System.out.println(sb.length());;
		System.out.println(sb.capacity());

輸出:

0

16

StringBuffer sb = new StringBuffer("hello");
		System.out.println(sb.length());;
		System.out.println(sb.capacity());

輸出:

5

21

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

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

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是不可變的字符串類型),和新串連接后,重新開辟一個內存。這樣就會造成每次連接一個新串后,都會讓之前的串報廢,因此也造成了不可避免的內存泄露。

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

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

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

三、字符串處理類StringTokenizer

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

舉個簡單的例子:

 

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 !

本章節完!后續會不斷的增加。

如果大家有什么問題,請到我的原博CSDN博客(http://blog.csdn.net/zhangerqing)提問,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM