《java解惑》讀書筆記2——字符串之謎

1.字符拼接:

問題:

程序員幾乎在每天編程中都遇到和處理字符串拼接的問題,但是是否對其瞭解的足夠深入,且看下面的程序:

public static void main(String[] args) {
		System.out.print("H" + "a");
		System.out.print('H' + 'a');
	}
很多人覺得輸出結果應是:HaHa,但是真實的程序運行結果是:Ha169。

原因:

程序第一個輸出是兩個字符串拼接,所以結果毫無疑問。

第二個輸出是字符相加而非字符串拼接,因此會使用字符的ASCII碼(H是72,a是97)進行相加然後將char類型的結果進行數據類型拓寬爲int類型輸出,因此輸出結果是169.

結論:

很多人認爲字符串和字符是一樣的,其實這是個錯誤認識,字符char是個無符號的16位整數,而字符串是將一系列字符以unicode方法處理。

因此,對於字符拼接可以使用下面的方法:

方法一:

使用StringBuffer,StringBuilder等的append方法拼接字符,代碼如下:

StringBuffer sb = new StringBuffer();
sb.append('H');
sb.append('a');
System.out.println(sb);
方法二:

在字符拼接中拼接中以一個空的字符串開頭,代碼如下:

System.out.println("" + 'H' + 'a');
方法三:

使用格式化的打印輸出printf,代碼如下:

System.out.printf("%c%c", 'H', 'a');

方法四:

使用字符串API-String.valueOf(char c),代碼如下:

System.out.printf(String.valueOf('H') + String.valueOf('a'));

2.字符數組打印:

問題:

很多人使用下面的小程序打印字符數組:

public static void main(String[] args) {
		String letters = "ABC";
		char[] numbers = {'1', '2', '3'};
		System.out.println(letters + " easy as " + numbers);
	}
本來期望程序輸出結果爲:ABC easy as 123,但是真實程序運行結果類似爲:ABC easy as [C@1db05b2。

原因:

儘管字符是一個16位的無符號整數類型,但是它通常表示的是字符而不是整數,因此很多程序對其進行了特殊處理,輸出其Unicode字符而非其ASCII碼,System.out.println,String.valueOf和StringBuffer.append會將字符數組中所包含的所有字符打印輸出,而字符串鏈接操作符+沒有在這些方法中定義,因此會現將要拼接的兩個操作數轉換爲字符串(調用toString方法),然後執行字符串拼接,因此字符數組在沒有重寫toString方法時,將會打印其Object的默認toString,由此產生上述的結果。

結論:

解決上述問題有如下方法:

方法1:

將字符串分開打印,使用JDK API的重載方法,代碼如下:

public static void main(String[] args) {
		String letters = "ABC";
		char[] numbers = {'1', '2', '3'};
		System.out.print(letters + " easy as ");
		System.out.print(numbers);
	}
方法2:

使用String.valueOf(char[])方法將字符數組轉化爲字符串,代碼如下:

public static void main(String[] args) {
		String letters = "ABC";
		char[] numbers = {'1', '2', '3'};
		System.out.print(letters + " easy as " + String.valueOf(numbers));
	}
當字符數組和字符串進行拼接時,一定千萬注意,很多時候結果並不是我們所期望的。


3.字符串比較問題:

測測一下下面程序的輸出結果:

public static void main(String[] args) {
		final String pig = "length: 10";
		final String dog = "length: " + pig.length();
		System.out.print("Animals are equal:" + pig == dog);
	}
有人可能覺得應該輸出:Animals are equals:true,因爲dog字符串也是“length: 10”,並且兩個字符串都是final類型的,java中相同的字符串會保存在字符串常量池中,因此是同一個對象,因此比較應該爲true。

也有人可能局的應該輸出:Animals are equals:false,因爲java中字符串是不可變的,dog字符串是由兩個字符拼接的,隱私dog是另一個對象,因此比較應該爲false。

但是程序真實運行結果爲:false,沒有Animals are equals:字符串打印出來。

原因:

+不論用做加法還是字符串拼接運算符,它都比==操作符優先級更高,因此上面的println語句是按照如下的方式運算:

System.out.print(("Animals are equal:" + pig) == dog);
這個表達式當然輸出的結果是false。

結論:

解決上面問題的方法有:

方法1:

使用括號顯式指定操作符優先級,代碼如下:

public static void main(String[] args) {
		final String pig = "length: 10";
		final String dog = "length: " + pig.length();
		System.out.print("Animals are equal:" + (pig == dog));
	}

該程序輸出結果爲:Animals are equals:false。

但是這種做法使用了JVM字符串常量池限制,如果dog字符串直接使用了字符串常量池,則有可能會輸出Animals are equals:true的結果,程序不應該依賴與JVM字符串常量池限制,因此不推薦使用該方法。

方法2:

使用equals方法比較字符串,代碼如下:

public static void main(String[] args) {
		final String pig = "length: 10";
		final String dog = "length: " + pig.length();
		System.out.print("Animals are equal:" + pig.equals(dog));
	}
該程序輸出結果爲:Animals are equals:true。

當對字符串比較時,最好使用equals方法,除非是比較兩個對象是否相等時才使用==,另外使用==時特別要注意運算符優先級。


4.轉義字符:

問題:

下面的程序輸出結果應該是什麼:

public static void main(String[] args) {
		System.out.print("a\u0022.length() + \u0022b".length());
	}
\u0022是unicode轉義字符雙引號,有人覺得應該輸出26,因爲在兩個雙引號直接有26個字符,有人覺得是16,因爲一個轉義字符\u0022表示一個字符。

真實程序運行結果爲:2。

原因:

將\u0022是轉義字符替換爲雙引號,在上面程序可以被替換爲如下代碼:

public static void main(String[] args) {
		System.out.print("a".length() + "b".length());
	}
因此程序運行結果爲2.

如果期望輸出16,則使用如下代碼:

public static void main(String[] args) {
		System.out.print("a\".length() + \"b".length());
	}
結論:

java中使用有很多普通轉義字符:單引號(\'),換行(\n),製表符(\t),反斜線(\\)等,使用轉義字符比使用Unicode的轉義字符更清楚,因此在程序中優先選用普通轉義字符。


5.註釋中的特殊字符:

注意Java註釋中特殊字符,例如”\u“表示Unicode轉義字符開始。

特別注意”\u“不會出現在一個Unicode轉義字符上下文之外,註釋中也要特別注意,除非特別必要,否則儘量不要使用Unicode轉義字符。


6.字符串的編碼問題:

問題:

猜猜下面的程序運行結果是什麼:

public static void main(String[] args){
		byte bytes[] = new byte[256];
		for(int i = 0; i < 256; i++){
			bytes[i] = (byte)i;
		}
		String str = new String(bytes);
		for(int i = 0, n = str.length(); i < n; i++){
			System.out.println((int)str.charAt(i) + " ");
		}
	}
程序的邏輯很簡單,先將從0到255的正整數放入一個字節數組中,然後將字節數組轉換爲字符串,最後再將字符串中每位字符轉換爲int數值打印出來,所以程序應該打印出從0到255的數值。

很遺憾告訴你真正程序的運行結果不可預測,甚至程序不能正常終止。

原因:

java的字符串構造函數String(byte[])規範中規定:在通過解碼使用平臺缺省字符集的指定byte數組來構造一個新的String時,該新String的長度是字符集的一個函數,因此字符串的長度可能不等於byte數組的長度,當給定的所有字節在缺省字符集中並非全部有效時,這個構造器的行爲是不確定的。

字符集從技術角度來講是被編碼的字符集合和字符編碼模式的結合物,也就是說字符集和一個包,包含了字符、表示字符的數字編碼以及在字符編碼序列和字節編碼序列之間相互轉換的方式,轉換模式在字符集之間存在着很大的區別:某些是在字符和字節之間做一對一的映射,但是大多數字符集都不是這樣。ISO-8859-1是唯一能讓上述程序按順序打印從0到255的整數的缺省字符集。

結論:

將程序顯式指定ISO-8859-1字符編碼集,即可讓程序達到我們期望的結果,代碼如下:

public static void main(String[] args){
		byte bytes[] = new byte[256];
		for(int i = 0; i < 256; i++){
			bytes[i] = (byte)i;
		}
		String str;
		try {
			str = new String(bytes, "ISO-8859-1");
			for(int i = 0, n = str.length(); i < n; i++){
				System.out.println((int)str.charAt(i) + " ");
			}
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
如果不相信可以指定UTF-8,GBK等字符編碼集,程序都不能順序打印從0到255的整數。

J2SE運行是環境JRE的缺省字符編碼集依賴於底層操作系統和語言,在java中獲取默認字符編碼集方法:

JDK5之前的版本:使用System.getProperty("file.encoding")方法來獲取。

JDK5以後的版本:可以使用java.nio.charset.Charset.defaultCharset()方法來獲得。

每當需要將一個byte序列轉換成一個String時,不論是否顯式指定字符編碼集,程序都在使用某一字符集,如果想讓程序的行爲可預知,那麼最好在每次使用的時候都顯式明確第指定所使用的字符編碼集。

發佈了185 篇原創文章 · 獲贊 63 · 訪問量 146萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章