参考链接:
Unicode 和 UTF-8 有什么区别?
Python2和Python3中字符串编码问题详解
Unicode 和 UTF-8 的区别
Unicode 是字符集,也叫编码表
UTF-8 是编码规则
字符集:为每一个「字符」分配一个唯一的 ID(学名为码位/码点/ Code Point)
编码规则:将「码位」转换为字节序列的规则(编码/解码)
字符集和编码表
一套字符集必然至少有一套字符编码。
-
ASCII字符集 :
-
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
-
基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
-
-
ISO-8859-1字符集:
-
拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
-
ISO-5559-1使用单字节编码,兼容ASCII编码。
-
-
GBxxx字符集:
-
GB就是国标的意思,是为了显示中文而设计的一套字符集。
-
GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
-
GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
-
GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
-
-
Unicode字符集 :
-
Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。
-
它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。
-
UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:
- 128个US-ASCII字符,只需一个字节编码。
- 拉丁文等字符,需要二个字节编码。
- 大部分常用字(含中文),使用三个字节编码。
- 其他极少使用的Unicode辅助字符,使用四字节编码。
-
编码和解码
字符需要有效传输,所以需要将字符编码为二进制的字节序列,解码就是从二进制序列,按照指定的规则,恢复出原始的内容(Unicode)。
编码:字符(能看懂的)→编码 encode→指定字节序列(看不懂的)
解码:指定字节序列(看不懂的) →解码 decode→字符(能看懂的)
UTF-8 编码例子
看个例子:It's 知乎日报
你看到的 Unicode 字符集是这样的编码表:
I 0049
t 0074
' 0027
s 0073
0020
知 77e5
乎 4e4e
日 65e5
报 62a5
每一个字符对应一个十六进制数字。
计算机只懂二进制,因此,严格按照 Unicode 的方式,应该这样存储:
I 00000000 01001001
t 00000000 01110100
' 00000000 00100111
s 00000000 01110011
00000000 00100000
知 01110111 11100101
乎 01001110 01001110
日 01100101 11100101
报 01100010 10100101
这个字符串总共占用了 18 个字节,但是对比中英文的二进制码,可以发现,英文前 9 位都是 0!浪费啊,浪费硬盘,浪费流量。
UTF-8 为了节省空间,这样做:
- 单字节的字符,字节的第一位设为 0,对于英语文本,UTF-8 码只占用一个字节,和 ASCII 码完全相同。
- n 个字节的字符(n>1),第一个字节的前 n 位设为 1,第 n+1 位设为 0,后面字节的前两位都设为 10,这 n 个字节的其余空位填充该字符 Unicode 码,高位用 0 补足。
这样就形成了如下的 UTF-8 标记位:
0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
... ...
于是,”It's 知乎日报“就变成了:
I 01001001
t 01110100
' 00100111
s 01110011
00100000
知 11100111 10011111 10100101
乎 11100100 10111001 10001110
日 11100110 10010111 10100101
报 11100110 10001010 10100101
和上边的方案对比一下,英文短了,每个中文字符却多用了一个字节。但是整个字符串只用了 17 个字节,比上边的 18 个短了,所以 UTF-8 编码所占用的空间少了。
Java 中关于编码的理解
在 Java 的内存中,字符(String,char)都以 Unicode 编码存储,使用 Java 内码,UTF-16。虽然 .java
和 .class
文件是 UTF-8 格式,加载进内存的时候,会将字符解码为 Unicode。
当进行编码的时候,会改变二进制在内存中的样子,只能通过对应的解码规则进行解码,在内存中变成 Unicode 字符,我们才能正常看到。
String.getBytes()
是一个用于将String的内码转换为指定的外码的方法。
无参数使用平台的默认编码作为外码,有参数版使用参数指定的编码作为外码。
将String的内容用外码编码好,结果放在一个新byte[]返回。
内存存储的是UTF-16编码的字节,那么String.getBytes("UTF-8")
应该是从UTF-16到UTF-8的转换过程。
验证编码:
package package1;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
String name1 = "小aa";
String name2 = "小aa";
byte[] bytes1 = name1.getBytes("utf8");//5
System.out.println(bytes1.length);
byte[] bytes2 = name1.getBytes("gbk");//4
System.out.println(bytes2.length);
}
}
编码和解码时规则不一样时:
package package1;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
String name = "小aa";
byte[] bytes = name.getBytes("utf-8");
String s = new String(bytes, "utf-8");
System.out.println(s);//小aa
byte[] bytes2 = name.getBytes("utf-8");
String s2 = new String(bytes, "gbk");
System.out.println(s2);//灏廰a
}
}