编码是什么

字符集与字符编码

​ 字符是各种文字和符号的总称,包括各个国家文字、标点符号、图形符号、数字等。字符集是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集有:ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集、unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。 ​ 编码(encoding)和字符集不同。字符集只是字符的集合,不一定适合作网络传送、处理,有时须经编码(encode)后才能应用。如unicode可依不同需要以UTF-8、UTF-16、UTF-32等方式编码。

​ 字符编码就是以二进制的数字来对应字符集的字符。因此,对字符进行编码,是信息交流的技术基础。

​ 使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫做“字符集”。规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”。

编码问题的由来、相关概念的理解

从计算机对多国语言的支持角度看,大致可以分为三个阶段:

系统内码说明系统
阶段一ASCII计算机刚开始只支持英语,其它语言不能够在计算机上存储和显示。英文 DOS
阶段二ANSI编码 (本地化)为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。 不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。 不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。中文 DOS,中文 Windows 95/98,日文 Windows 95/98
阶段三UNICODE (国际化)为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。Windows NT/2000/XP,Linux,Java

字符串在内存中的存放方法:

在 ASCII 阶段,单字节字符串使用一个字节存放一个字符(SBCS)。比如,“Bob123” 在内存中为:

426F6231323300
Bob123\0

在使用 ANSI 编码支持多种语言阶段,每个字符使用一个字节或多个字节来表示(MBCS),因此,这种方式存放的字符也被称作多字节字符。比如,“中文123” 在中文 Windows 95 内存中为7个字节,每个汉字占2个字节,每个英文和数字字符占1个字节:

D6D0CEC431323300
123\0

在 unicode 被采用之后,计算机存放字符串时,改为存放每个字符在 unicode 字符集中的序号。目前计算机一般使用 2 个字节(16 位)来存放一个序号(DBCS),因此,这种方式存放的字符也被称作宽字节字符。比如,字符串 “中文123” 在 Windows 2000 下,内存中实际存放的是 5 个序号:

2D4E87653100320033000000
123\0

一共占10个字节。

对编码的误解
误解一在将“字节串”转化成“unicode 字符串”时,比如在读取文本文件时,或者通过网络传输文本时,容易将“字节串”简单地作为单字节字符串,采用每“一个字节”就是“一个字符”的方法进行转化。 而实际上,在非英文的环境中,应该将“字节串”作为 ANSI 字符串,采用适当的编码来得到 unicode 字符串,有可能“多个字节”才能得到“一个字符”。 通常,一直在英文环境下做开发的程序员们,容易有这种误解。
误解二在 DOS,Windows 98 等非 unicode 环境下,字符串都是以 ANSI 编码的字节形式存在的。这种以字节形式存在的字符串,必须知道是哪种编码才能被正确地使用。这使我们形成了一个惯性思维:“字符串的编码”。 当 unicode 被支持后,Java 中的 String 是以字符的“序号”来存储的,不是以“某种编码的字节”来存储的,因此已经不存在“字符串的编码”这个概念了。只有在“字符串”与“字节串”转化时,或者,将一个“字节串”当成一个 ANSI 字符串时,才有编码的概念。 不少的人都有这个误解。

网页提交字符串

​ 当页面中的表单提交字符串时,首先把字符串按照当前页面的编码,转化成字节串。然后再将每个字节转化成 “%XX” ,通常使用百分比编码(Percent Encoding,也称为URL编码)来表示特殊字符。百分比编码使用百分号(%)加上两位十六进制数表示字符的ASCII码的格式提交到 Web 服务器。比如,一个编码为 GB2312 的页面,提交 “中” 这个字符串时,提交给服务器的内容为 “%D6%D0”。 ​ 在服务器端,Web 服务器把收到的 “%D6%D0” 转化成 [0xD6, 0xD0] 两个字节,然后再根据 GB2312 编码规则得到 “中” 字。 ​ 在 Tomcat 服务器中,request.getParameter() 得到乱码时,常常是因为前面提到的“误解一”造成的。默认情况下,当提交 “%D6%D0” 给 Tomcat 服务器时,request.getParameter() 将返回 [0x00D6, 0x00D0] 两个 unicode 字符,而不是返回一个 “中” 字符。因此,我们需要使用 bytes = string.getBytes(“iso-8859-1”) 得到原始的字节串,再用 string = new String(bytes, “GB2312”) 重新得到正确的字符串 “中”。

​ 处理查询参数:浏览器在处理URL时,如果存在查询参数(位于URL中问号后面的部分),也会对查询参数中的特殊字符进行编码,以避免可能引起混淆的情况。

百分号编码规则

  1. 对于非ASCII字符:将字符的每个字节转换成两位十六进制数,并在前面加上百分号(%)。例如,中文字符"国"(unicode码点是U+56FD)会被编码成"%E5%9B%BD"。
  2. 对于特殊字符:一些特殊字符在URL中具有特殊含义,因此它们需要被编码,以免产生歧义。例如,空格会被编码成"%20",问号会被编码成"%3F",等等。
  3. 对于保留字符:一些字符在URL中具有特殊用途,但也可以在路径中使用,因此它们只在特定情况下被编码。例如,斜杠"/“不需要编码,但如果它在查询参数中作为数据的一部分出现,则需要被编码成”%2F"。

根据上述规则,将"你好世界"编码成URL安全的格式后,得到的查询参数是:

1
%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C

所以完整的URL是

1
https://www.example.com/searchquery=%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C

​ 这样的编码方式确保了中文字符正确地传递给服务器,并且符合URL的规范。请注意,这里使用的是UTF-8编码,并且每个中文字符都分别被编码为多个连续的百分比编码,而不是之前错误提供的"%u"格式。对于中文字符,UTF-8编码的百分比编码通常是正确的编码方式。

数据库读取字符串

​ 通过数据库客户端(比如 ODBC 或 JDBC)从数据库服务器中读取字符串时,客户端需要从服务器获知所使用的 ANSI 编码。当数据库服务器发送字节流给客户端时,客户端负责将字节流按照正确的编码转化成 unicode 字符串。 ​ 如果从数据库读取字符串时得到乱码,而数据库中存放的数据又是正确的,那么往往还是因为前面提到的“误解一”造成的。解决的办法还是通过 string = new String( string.getBytes(“iso-8859-1”), “GB2312”) 的方法,重新得到原始的字节串,再重新使用正确的编码转化成字符串。