問題
Java 的 char 是兩個(gè)字節(jié), 如何存 UTF-8 的字符的?
考什么
- 是否熟悉 Java char 和字符串. (初級(jí))
- 是否了解字符的映射和儲(chǔ)存細(xì)節(jié). (中級(jí))
- 是否能觸類旁通, 橫向?qū)Ρ绕渌Z言. (高級(jí))
剖析
先分析題目中兩個(gè)名詞:
- char 是什么?
- UTF-8 是什么?
- 然后各占幾個(gè)字節(jié)?
- 和 Unicode 什么關(guān)系?
char 是什么
甲骨文官方對于原始數(shù)據(jù)類型 char 定義:
char: The char data type is a single 16-bit Unicode character. It has a minimum value of '\u0000' (or 0) and a maximum value of '\uffff' (or 65,535 inclusive).
可以知道主要以下三點(diǎn):
- char 類型是原始類型
- Unicode 字符 (額外補(bǔ)充, 采用 UTF-16 的形式)
- 16 bit 即 2 個(gè)字節(jié)表示一個(gè)字符
char 里面存的是什么:
System.out.println(Integer.toHexString('慶'));
打印出來的 0x5e, 0x86, 它們 2 個(gè)就是 Unicode 的碼點(diǎn).
Java 中的 char 占用 2 個(gè)字節(jié).
UTF-8 是什么
UTF-8 是一種 Unicode 的編碼形式. 它可能占用 1~4 個(gè)字節(jié). 細(xì)節(jié)可以參考Unicode ASCII UTF-8 GBK關(guān)系
例如 char test = '慶'
是用 2 個(gè)字節(jié)進(jìn)行表示的. 而它是通過 UTF-8 編碼為 e5ba86
儲(chǔ)存的, 是 3 個(gè)字節(jié).
Unicode 和 ASCII 是字符集, 而 UTF-8, UTF-16 等是一種編碼. 是 unicode 的表現(xiàn)形式.
如何存儲(chǔ)字符
字符(人類認(rèn)知) -> char (字符集) -> byte (計(jì)算機(jī)存儲(chǔ))
char 中其實(shí)是 unicode 的字符集, 然后在計(jì)算機(jī)儲(chǔ)存中通過 UTF-8 進(jìn)行編碼儲(chǔ)存. 但請注意, 實(shí)際上 Java 中使用的是 UTF-16! 具體參考編碼之JVM之外與之內(nèi)
UTF-8與UTF-16的區(qū)別 -
UTF-8 最小的單位是 1 個(gè)字節(jié), UTF-16 最小的單位是 2 個(gè)字節(jié).
也就是說, 如果這個(gè)字符是 a甘萧,a 對應(yīng) 65嘹承,在 UTF-8 中它實(shí)際上和 ASCII 碼相同, 也就是7個(gè)比特就可以表示. 但在 UTF-16 中仍要2個(gè)字節(jié)的.
如果把字符換成中文:
類似于“中”字宋税,占1個(gè)char就可以了,也就是2個(gè)字節(jié)忠荞。同樣是用UTF-16。
額外的, 注意字節(jié)序的問題
byte[] bytes= "中".getBytes("utf-16");
System.out.println(bytes.length);
for(byte b : bytes) {
System.out.print(Integer.toHexString(Byte.toUnsignedInt(b)));
}
結(jié)果是:
4
fe ff 4e 2d
其中 fe ff
是字節(jié)序的標(biāo)志. 不是代表的真正的內(nèi)容, 而只是讓讀取這個(gè)數(shù)據(jù)的人知道字節(jié)序是什么.
觸類旁通 Python 的字符串
byteString = "I'm a byte string."
# 例如utf-8,字面量會(huì)用utf-8編碼成字節(jié)存入字符串
# coding = utf-8
byteString = "中國" # .py文件中存放的是 UTF-8 編碼后的字節(jié)
unicodeString = u"中國" # .py文件中存放的是 UCS2(~UTF-16) 編碼后的字節(jié)
Python 是解釋執(zhí)行的, 源文件與執(zhí)行時(shí)內(nèi)存中字符串內(nèi)容一致夫嗓。
Javac指定編碼將字符串統(tǒng)一轉(zhuǎn)為 MUTF-8 (調(diào)用這個(gè)命令的時(shí)候需要給個(gè)encoding的參數(shù))
// Java
byte[] byteString= "中".getBytes("utf-8");
// 對應(yīng) Python
byteString = "中國"
// Java
String unicodeString = "中國"
// 對應(yīng) Python
unicodeString = u"中國"
unicodeString可以調(diào)用encode(),byteString得decode().
讓人迷惑的字符串長度
字符串長度不等于字符數(shù)!
// emoji 代表表情
String emoji= "emoji"
System.out.println(emoji.length());
我們會(huì)認(rèn)為看到的是 1, 所以輸出是 1, 但表情占用 2 個(gè) char. 輸出 2. 在 Java 中看到的字符串長度不一定是輸出的字符串長度.
但在 Python 中
String emoji= u"emoji"
print(len(emoji))
python >= 3.2, 結(jié)果為 1, 低版本為 2.
所以有時(shí)候輸入框有字符限制, 用戶輸入一個(gè)表情減 2 個(gè)字符, 這不合理.
Java 9并沒有改變字符串長度和字符數(shù)不一致的情況桐玻。
但是對拉丁(Latin)字符的存儲(chǔ)空間做了優(yōu)化. UTF-16 對于 a, b, c 等可以1個(gè)字節(jié)的還是需要兩個(gè)字節(jié)來存. 會(huì)改用 byte 來存. 節(jié)省一般空間.
字符串長度 篙挽!= 字符數(shù)
題目結(jié)論
- Java char 不存 UTF-8 的字節(jié),而是 UTF-16 的.
- Unicode 通用字符集占2個(gè)字節(jié)镊靴,例如“中”.
- Unicode 是擴(kuò)展字符集需要用一對char來表示铣卡,例如“emoji”
- Unicode 是字符集, 不是編碼.
- Java String 字符串長度不等于字符數(shù).