發發牢騷
在工作中遇到不少Java編碼問題,每次解決亂碼問題,都花了較長的時間,非常影響工作效率!
在工作中會經常遇到各種各樣的問題,每次一個問題總是搗騰了很久,然後就解決了,但其實沒有弄明白問題的本質。當下次遇到類似的問題,還是繼續折騰,非常浪費時間。
想要高效解決編碼問題,當然要究其根源。以後遇到類似問題,可以順藤摸瓜,輕鬆解決!
典型問題
- Java採用哪種字符集
- Java中一個字符需要幾個字節存儲
- String.getBytes()方法哪種編碼方式?IOS-8895-1、還是UTF-8?
- 亂碼產生的原因
如果能夠回答以上問題,那麼這篇文章可以Pass了!
將Unicode字符串轉化成UTF-8、ISO-8859-1字符串
Java採用Unicode字符集,一個字符用兩個字節存儲。
以下是程序片段:將Unicode字符串轉化成UTF-8、ISO-8859-1字符串
String cnName = "I am 小佳";
//Unicode字符集
System.out.println(printHexString(cnName.toCharArray()));
//ISO-8859-1編碼
System.out.println(printHexString(cnName.getBytes("ISO-8859-1")));
//UTF-8編碼
System.out.println(printHexString(cnName.getBytes("UTF-8")));
/**
* 字節數組轉換成16進制
* @param b
* @return
*/
private String printHexString( byte[] b) {
String a = "";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
a = a+hex;
}
return a;
}
/**
* 字符數組轉化成16進制
* @param b
* @return
*/
private String printHexString(char[] b) {
String a = "";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFFFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
a = a+hex;
}
return a;
}
字符串”I am 小佳”,由Unicode轉換成UTF-8和ISO-8859-1,得到不同的字節(16進製表示),主要區別如下:
- 對於英文字符,無論採用何種編碼方式,得到的編碼結果相同。所以,英文字符不存在亂碼問題
- 對於中文字符,不同的編碼方式得到不同的結果;Unicode用2個字節存儲中文,UTF-8用3個字節存儲中文
- ISO-8859-1不支持中文,任何中文,採用該編碼,都會轉換成3F(3F映射的字符爲’?’); 所以當中文用ISO-8859-1編碼時,會生成一些列帶問號的亂碼’????????????????’
編碼方式 | I | a | m | 小 | 佳 | ||
---|---|---|---|---|---|---|---|
Unicode | 49 | 20 | 61 | 6D | 20 | 5C0F | 4F73 |
ISO-8859-1 | 49 | 20 | 61 | 6D | 20 | 3F | 3F |
UTF-8 | 49 | 20 | 61 | 6D | 20 | E5B08F | E4BDB3 |
亂碼的產生
採用不同的編碼方式進行 編碼 和 解碼 , 是生成亂碼的根源。下面分析兩種常見的亂碼
String cnName = "I am 小佳";
//採用ISO-8859-1編碼、UTF-8解碼
String iso88591Str = new String (cnName.getBytes("ISO-8859-1"), "UTF-8");
//採用UTF-8編碼、ISO-8859-1解碼
String utf8Str = new String (cnName.getBytes("UTF-8"), "ISO-8859-1");
System.out.println(iso88591Str);//打印結果,亂碼:I am ??
System.out.println(utf8Str);//打印結果,亂碼:I am å°ä½³
源字符串 | 編碼 | 解碼 | 轉換結果 |
---|---|---|---|
I am 小佳 | UTF-8 | ISO-8859-1 | I am å°ä½³ |
I am 小佳 | ISO-8859-1 | UTF-8 | I am ?? |
產生亂碼”I am å°ä½³”的原因是:ISO-8859-1只能對單個字節編碼
產生亂碼”I am ??”的原因是:ISO-8859-1會把所有的中文字符轉化成3F,3F映射的字符爲’?’
getBytes()的編碼方式
String.getBytes()採用操作系統默認的編碼方式,請看getBytes()源碼
獲取默認編碼在這個方法: defaultCharset(),該方法從關鍵
字“file.encoding”獲取默認編碼方式
題外話:defaultCharset方法值得研究,該方法是線程安全的,並且用了緩存!
如果你使用getBytes(),發生了中文亂碼問題, 則需要設置file.encoding爲UTF-8, 即可解決亂碼問題
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
static byte[] encode(char[] ca, int off, int len) {
//獲取默認得編碼方式
String csn = Charset.defaultCharset().name();
try {
// use charset name encode() variant which provides caching.
return encode(csn, ca, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
try {
return encode("ISO-8859-1", ca, off, len);
} catch (UnsupportedEncodingException x) {
// If this code is hit during VM initialization, MessageUtils is
// the only way we will be able to get any kind of error message.
MessageUtils.err("ISO-8859-1 charset not available: "
+ x.toString());
// If we can not find ISO-8859-1 (a required encoding) then things
// are seriously wrong with the installation.
System.exit(1);
return null;
}
}
public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
//從操作系統獲取默認得編碼方式
String csn = AccessController.doPrivileged
new GetPropertyAction("file.encoding"));
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = forName("UTF-8");
}
}
return defaultCharset;
}
總結
我這裏只是拋磚引玉,講述了Unicode轉換UTF-8、ISO-8859-1的過程,以及亂碼問題產生的根源。
如果對編碼問題感興趣的話,可以進一步探討GBK、GB2313、UTF-8的編碼轉換是否會產生亂碼
另外,編碼問題很考驗思維深度,有不少面試官會專門出編碼的題目。所以,編碼問題值得研究!