JAVA字符謎題8:字符串奶酪

字號:

下面的程序從一個字節(jié)序列創(chuàng)建了一個字符串,然后迭代遍歷字符串中的字符,并將它們作為數字打印。請描述一下程序打印出來的數字序列:
    public class StringCheese {
     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) + " ");
     }
    }
     首先,byte數組用從0到255每一個可能的byte數值進行了初始化,然后這些byte數值通過String構造器被轉換成了char數值。最后,char數值被轉型為int數值并被打印。打印出來的數值肯定是非負整數,因為char數值是無符號的,因此,你可能期望該程序將按順序打印出0到255的整數。
    如果你運行該程序,可能會看到這樣的序列。但是在運行一次,可能看到的就不是這個序列了。我們在四臺機器上運行它,會看到四個不同的序列,包括前面描述的那個序列。這個程序甚至都不能保證會正常終止,比打印其他任何特定字符串都要缺乏這種保證。它的行為完全是不確定的。
    這里的罪魁禍首就是String(byte[])構造器。有關它的規(guī)范描述道:“在通過解碼使用平臺缺省字符集的指定byte數組來構造一個新的String時,該新String的長度是字符集的一個函數,因此,它可能不等于byte數組的長度。當給定的所有字節(jié)在缺省字符集中并非全部有效時,這個構造器的行為是不確定的”[Java-API]。
    到底什么是字符集?從技術角度上講,它是“被編碼的字符集合和字符編碼模式的結合物”[Java-API]。換句話說,字符集是一個包,包含了字符、表示字符的數字編碼以及在字符編碼序列和字節(jié)序列之間來回轉換的方式。轉換模式在字符集之間存在著很大的區(qū)別:某些是在字符和字節(jié)之間做一對一的映射,但是大多數都不是這樣。ISO-8859-1是能夠讓該程序按順序打印從0到255的整數的缺省字符集,它更為大家所熟知的名字是Latin-1[ISO-8859-1]。
     J2SE運行期環(huán)境(JRE)的缺省字符集依賴于底層的操作系統(tǒng)和語言。如果你想知道你的JRE的缺省字符集,并且你使用的是5.0或更新的版本,那么你可以通過調用java.nio.charset.Charset.defaultCharset()來了解。如果你使用的是較早的版本,那么你可以通過閱讀系統(tǒng)屬性“file.encoding”來了解。
    幸運的是,你沒有被強制要求必須去容忍各種稀奇古怪的缺省字符集。當你在char序列和byte序列之間做轉換時,你可以且通常是應該顯式地指定字符集。除了接受byte數字之外,還可以接受一個字符集名稱的String構造器就是專為此目的而設計的。如果你用下面的構造器去替換在最初的程序中的String構造器,那么不管缺省的字符集是什么,該程序都保證能夠按照順序打印從0到255的整數:
    String str = new String(bytes, "ISO-8859-1");
     這個構造器聲明會拋出UnsupportedEncodingException異常,因此你必須捕獲它,或者更適宜的方式是聲明main方法將拋出它,要不然程序不能通過編譯。盡管如此,該程序實際上不會拋出異常。Charset的規(guī)范要求Java平臺的每一種實現(xiàn)都要支持某些種類的字符集,ISO-8859-1就位列其中。
    這個謎題的教訓是:每當你要將一個byte序列轉換成一個String時,你都在使用某一個字符集,不管你是否顯式地指定了它。如果你想讓你的程序的行為是可預知的,那么就請你在每次使用字符集時都明確地指定。對API的設計者來說,提供這么一個依賴于缺省字符集的String(byte[])構造器可能并非是一個好主意。