字符编码
字符编码
在早期的计算机系统中,为了给字符编码,美国国家标准学会(American National Standard Institute:ANSI)制定了一套英文字母、数字和常用符号的编码,它占用一个字节,编码范围从0到127,最高位始终为0,称为ASCII编码。
例如,字符A
的编码是0x41
,字符1
的编码是0x31
。
ASCII是能表示英文了,但是中文、日文、韩文怎么办?
但是,如果要把汉字也纳入计算机编码,很显然一个字节是不够的。各个国家都有了自己的标准。
GB2312
标准使用两个字节表示一个汉字,其中第一个字节的最高位始终为1,以便和ASCII编码区分开。例如,汉字中
的GB2312
编码是0xd6d0
。
类似的,日文有Shift_JIS
编码,韩文有EUC-KR
编码,这些编码因为标准不统一,同时使用,就会产生冲突。
那各个国家交流不就乱套了吗
为了统一全球所有语言的编码,全球统一码联盟发布了Unicode
编码,它把世界上主要语言都纳入同一个编码,这样,中文、日文、韩文和其他语言就不会冲突。
Unicode编码需要两个或者更多字节表示,我们可以比较中英文字符在ASCII、GB2312和Unicode的编码:
英文字符’A’的ASCII编码和Unicode编码:
1 | ┌────┐ |
英文字符的Unicode
编码就是简单地在前面添加一个00
字节。
中文字符’中’的GB2312
编码和Unicode
编码:
1 | ┌────┬────┐ |
那我们经常使用的UTF-8
又是什么编码呢?
因为英文字符的Unicode
编码高字节总是00
,包含大量英文的文本会浪费空间,所以,出现了UTF-8
编码,
它是一种变长编码,用来把固定长度的Unicode编码变成1~4字节的变长编码。
通过UTF-8编码,英文字符A
的UTF-8编码变为0x41
,正好和ASCII码一致,而中文’中’的UTF-8编码为3字节0xe4b8ad
。
UTF-8
编码的另一个好处是容错能力强。如果传输过程中某些字符出错,不会影响后续字符,因为UTF-8编码依靠高字节位来确定一个字符究竟是几个字节,它经常用来作为传输编码。
Java和编码
在Java中,char
类型实际上就是两个字节的Unicode
编码。如果我们要手动把字符串转换成其他编码,可以这样做:
1 | byte[] b1 = "Hello中".getBytes(); // 按系统默认编码转换,不推荐 |
如果要把已知编码的byte[]
转换为String
,可以这样做:
1 | String s1 = new String(arr, "GBK"); // 按GBK转换 |
始终牢记:Java的String
和char
在内存中总是以Unicode编码表示。
String类的演变
对于不同版本的JDK,String
类在内存中有不同的优化方式。
具体来说,早期JDK版本的String
总是以char[]
存储,它的定义如下:
1 | public final class String { |
而较新的JDK版本的String
则以byte[]
存储:如果String
仅包含ASCII字符,则每个byte
存储一个字符,否则,每两个byte
存储一个字符,这样做的目的是为了节省内存,因为大量的长度较短的String
通常仅包含ASCII字符:
1 | public final class String { |
对于使用者来说,String
内部的优化不影响任何已有代码,因为它的public
方法签名是不变的。
小结
- Java内存中使用
Unicode
表示char
和String
- 转换编码就是将
String
和byte[]
转换,需要指定编码 - 转换为
byte[]
时,始终优先考虑UTF-8
编码。
改编自廖雪峰的官方网站,原文链接