首页 今日头条正文

孤岛惊魂4,让编码问题不再困惑你,程

1. ASCII编码#

上个世纪60年代,美国拟定了一套字符编码,对英语字符与二进制位之间的联系,做了共同规矩。这被称为ASCII码,一向沿用至今。ASCII码一共规矩了128个字符的编码,比方空格"SPACE"是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包含32个不能打印出来的操控符号),只占用了一个字节的后边7位,最前面的1位共同规矩为0。0~31 是操控字符如换行回车删去等,32~126 是打印字符,能够经过键盘输入而且能够显现出来。

英语用128个符号编码就够了,可是用来标明其他言语,128个符号是不行的。比方,在法语中,字母上方有注音符号,它就无法用ASCII码标明。所以,一些欧洲国家就决议,运用字节中搁置的最高位编入新的符号。比方,法语中的的编码为130(二进制10000010)。这样一来,这些欧洲国家运用的编码体系,能够标明最多256个符号。

可是,这儿又呈现了新的问题。不同的国家有不同的字母,因而,哪怕它们都运用256个符号的编码办法,代表的字母却不相同。比方,130在法语编码中代表了,在希伯来语编码中却代表了字母Gimel (),在俄语编码中又会代表另一个符号。可是不论怎样,一切这些编码办法中,0—127标明的符号是相同的,不相同的仅仅128—255的这一段。

至于亚洲国家的文字,运用的符号就更多了,汉字就多达10万左右。一个字节只能标明256种符号,必定是不行的,就有必要运用多个字节表达一个符号。比方,简体中文常见的编码办法是GB2312,mu5362运用两个字节标明一个汉字,所以理论上最多能够标明65536个符号。

2. Unicode编码#

能够幻想,假如有一种编码,将国际上一切的符号都归入其间。每一个符号都给予一个绝无仅有的编码,那么就不会呈现上面的问题。Unicode编码便是这样一种编码。

Unicode是一个很大的字符调集,现在的规划能够包容100多万个符号。每个符号的编码都不相同,比方,U+0639标明阿拉伯字母Ain,U+0041标明英语的大写字母A,U+4E25标明汉字“严”。

需求留意的是,Unicode仅仅一个符号集,它只规矩了符号的二进制代码,却没有规矩这个二进制代码应该怎样存储。这就形成了两个问题:

  • 榜首个问题是,怎样才干差异unicode和ascii?计算机怎样知道三个字节标明一个符号,而不是别离标明三个符号呢?
  • 第孤岛惊魂4,让编码问题不再困惑你,程二个问题是,咱们现已知道,英文字母只用一个字节标明就够了,假如unicode共同规矩,每个符号用三个或四个字节标明,那么每个英文字母前都必定有二到三个字节是0,这关于存储来说是极大的糟蹋,文本文件的大小会因而大出二三倍,这是无法承受的。

记住,Unicode仅仅一个用来映射字符和数字的规范。它对支撑字符的数量没有约束,也不要求字符有必要占两个、三个或许其它恣意数量的字节。Unicode字符是怎样被编码成内存中的字节这是别的的论题,它是被UTF(Unicode Transformation Formats)界说的。

3. UTF-8编码#

互联网的遍及,强烈要求呈现一种共同的编码办法。UTF-8便是在互联网上运用最广的一种unicode的完结办法。其他完结办法还包含UTF-16和UTF-32,不过在互联网上根本不必。重复一遍,这儿的联系是,UTF-8是Unicode的完结办法之一。

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可哈宝530变长度字符编码,又称万国码。由Ken Thompson于1992年创立。现在现已规范化为RFC 3629。UTF-8用1到4个字节编码Unicode字符。用在网页上能够共同页面显现中文简体繁体及其它言语(如英文,日文,韩文)。

UTF-8最大的一个特色,便是它是一种变长的编码办法。它能够运用1~4个字节标明一个符号,依据不同的符号而改变字节长度(UTF-8编码能够包容2^21个字符,一共200多万个字符)。

UTF-8的编码规矩很简单,只需二条:

  1. 关于单字节的符号,字节的榜首位设为0,后边7位为这个符号的unicode码。因而关于英语字母,UTF-8编码和ASCII码是相同的。
  2. 关于n字节的符号(n>1),榜首个字节的前n位都设为1,第n+1位设为0,后边字节的前两位一概设为10。剩余的没有提及的二进制位,悉数为这个符号的unicode码。

下表总结了编码规矩,字母x标明可用编码的位。

Unicode符号规模 | UTF-8编码办法

UTF字节数 (十六进制) | (二进制)

--------------------+---------------------------------------------一个字节 00足踩00 0000-0000 007F | 0xxxxxxx

两个字节 0000 0080-0000 07FF | 110xxxxx 10xxxxxx

三个字节 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

四个字节 0001 0000-0010 FFFF | 11110xxx 10xxx孤岛惊魂4,让编码问题不再困惑你,程xxx 10xxxxxx 10xxxxxx

下面, 仍是以汉字“严”为例,演示怎样完结UTF-8编码。

已知“严”的unicode是4E25(100111000100101),依据上表,能够发现4E25处在第三行的规模内(0000 0800-0000 FFFF),因而“严”的UTF-8编码需求三个字节,即格局是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的终究一个二进制位开端,顺次从后向前填入格局中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转化成十六进制便是E4B8A5。

4.孤岛惊魂4,让编码问题不再困惑你,程 UTF8、UTF16和UTF32之间的差异#

首要咱们要确认一个概念便是Unicode是一个字符妈妈的朋集,这个字符集国际上一切的字符界说了一个仅有编码。其仅仅规矩了每个符号的二进制代码,没有拟定细化的存储规矩。UTF-8、UTF-16、UTF-32才是Unicode的存储格局界说。(拿一个通信中的列子做个比照,一个信号(类比成Unicode编码指),经过不同的编码办法,会被编码成不同的凹凸信号)

4.1 UCS-2和UCS-4#

Unicode是为整合全国际的一切言语文字而诞生的。任何文字在Unicode中都对应一个值, 这个值称为代码点(code point)。代码点的值一般写成 U+ABCD 的格局。而文字和代码点之间的对应联系便是UCS-2(Universal Character Set coded in 2 octets禁漫)。望文生义,UCS-2是用两个字节来标明代码点,其取值规模为 U+0000~U+FFFF。

为了能标明更多的文字,人们又提出了UCS-4,即用四个字节标明代码点。它的规模为 U+00000000~U+7FFFFFFF,其间 U+00000000~U+0000FFFF和UCS-2是相同的。

要留意,UCS-2和UCS-4只规矩了代码点和文字之间的对应联系,并没有规矩代码点在计算机中怎样存储。规矩存储办法的称为UTF(Unicode Transformati六幺水调家家唱下一句on Format),其间运用较多的便是UTF-16和UTF-8了。

4.2 UTF-16#

UTF-16由RFC2781规矩,它运用两个字节来标明一个代码点。不难猜到,UTF-16是彻底对应于UCS-2的,即把UCS-2规矩的代码点经过Big Endian或Little Endian办法直接保存下来。UTF-16包含三种:UTF-16,UTF-16BE(Big Endian),UTF-16LE(Little Endian)。UTF-16BE和UTF-16LE不难理解,而UTF-16就需求经过在文件最初以名为BOM(Byte Order Mark)的字符来标明文件是Big Endian仍是Little Endian。BOM为U+FEFF这个字符。其实BOM是个小聪明的主意。因为UCS-2没有界说U+FEFF,因而只需呈现 FF FE 或许 FE FF 这样的字节序列,就能够以为它是U+FEFF,而且能够判别出是Big Endian仍是Little Endian。

BOM(Byte Order孤岛惊魂4,让编码问题不再困惑你,程 Mark)用来放在文档的最初告知阅读器该文档的字节序。UTF-8不需求BOM来标明字节次序,但能够用BOM来标明编码办法。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF。所以假如接收者收到以EF BB BF最初的字节省,就知道这是UTF-8编码了。UTF-16才需求加bom。因为它是按unicode次序编码,在BMP规模内是二字节,需求识别是大或小字节序。

低字节序(Little Endian)和高字节序(Big Endian)

低字节序和高字节序仅仅一个关于在内存中存储和读取一段字节(被称作words)的约好。这意味着当你让计算机用UTF-16把字母A(占两个字节)存在内存中时,运用哪种字节序计划决议了你把榜首个omoani字节放在第二个字节的前面仍是后边。这么说有点不太容易懂,让咱们来看一个比如:当你运用UTF-16存下某段内容时,在不同的体系中它的后半部分或许是这样的:

00 68 00 65 00 6C 00 6C 00 6F(高字节序,高位字节被存在前面)

68 00 65 00 6C 00 6C 00 6F 00(低字节序,低位字节被存在前面)

字节序计划仅仅一个微处理器架构规划者的偏好问题,例如,Intel运用低字节序,Motorola运用高字节序。

举个比如。“ABC”这三个字符用各种办法编码后的成果如下:

4.3 UTF-32#

UTF-32用四个字节标明代码点,这样就能够彻底标明UCS-4的一切代码点,而无需像UTF-16那样运用杂乱的算法。 与UTF-16相似,UTF-32也包含UTF-32、UTF-32BE、UTF-32LE三种编码,UTF-32也相同需求BOM字符。

4.4 文本编辑器孤岛惊魂4,让编码问题不再困惑你,程怎样知道文本的编码#

当一个软件翻开一个文本时,它要做的榜首件事是决议这个文陆小誉本究竟是运用哪种字符集的哪种编码保存的。软件一般选用三种办法来决议文本的字符集和编码:

  1. 检测文件头标识(BOM)
  2. EF BB BF UTF-8
  3. FE FF UTF-16/UCS-2, big endian
  4. FF FE UTF-16/UCS-2, little endian
  5. FF FE 00 00 UTF-32/UCS-4, little endian.
  6. 00 00 FE FF UTF-32/UCS-4, big-endian.
  7. 软件自己依据编码规矩猜想当时文件的编码
  8. 提示用户自己输入当时文件的编码

5. GBK、GB2312和GB18030之间的差异#

GB2312是对ASCll码的扩展,占用两个字节。一个小于127的字符的含义与原本相同,但两个大于127的字符连在一起时,就标明一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后边一个字节(低字节)从0xA1到0xFE,这样咱们就能够组合出大约7000多个简体汉字了。在这些编码里,咱们还把数学符号、罗马希腊的字母、日文的化名们都编进去了,连在 ASCII 段家女将里原本就有的数字、标点、字母都通通从头编了两个字节长的编码,这便是常说的"罗援激辩吴建民视频全角"字符,而原本在127号以下的那些就叫"半角"字符了。

GB2312能标明的字符仍是不行用,所以GBK呈现了。GBK是对GB1212的扩展,也是占用2个字节,GBK不再要求低字节一定是127号之后的内码,只需榜首个字节是大于127就固定标明这是一个汉字的开端,不论后边跟的是不是扩展字符集里的内容。成果扩展之后的编码计划被称为 GBK 规范,GBK 包含了 GB2312 的一切内容,一起又增加了近20000个新的汉字(包含繁体字)和符号。

GB18030选用变长编码,能够是1个字节、2个字节和4个字节。是对GB2312和GBK的扩展,彻底兼容两者。

经过上面介绍,咱们能够看出Unicode是一个国际规范,针对国际上一切言语符号拟定编码表,而GBK、GB2312等则主要是针对我国的字符进行编码。

6. Java中的编码问题#

咱们知道涉及到编码的当地一般都在字符到字节或许字节到字符的转化上,而需求这种转化的场景主要是在 I/O 的时分,这个 I/O 包含磁盘 I/O 和网络 I/O。而大部分 I/O 引起的乱码都是网络 I/O。

用户从浏览器端建议一个 HTTP 恳求,需求存在编码的当地是 URL、Cookie、Parameter。服务器端承受到 HTTP 恳求后要解析 HTTP 协议,其间 URI、Cookie 和 POST 表单参数需求解码,服务器端或许还需求读取数据库中的数据,本地或网络中其它当地的文本文件,这些数据都或许存在编码问题,当 Servlet 处理完一切恳求的数据后,需求将这些数据再编码经过 Socket 发送到用户恳求的浏览器里,再经过浏览器解码成为文本。这些进程如下图所示:

如上图所示一次 HTTP 恳求规划到许多当地需求编解码,它们编解码的规矩是什么?下面将会要点论述一下:

URL 的编解码

用户提交一个 URL,这个 URL 中或许存在中文,因而需求编码,怎样对这个 URL 进行编码?依据什么规矩来编码?有怎样来解码?如下图一个 URL:

Port 对应在 Tomcat 的 中装备,而 Context Path 在 中装备,Servlet Path 在 Web 运用的 web.xml 中的

  
junshanExample
/servlets/servlet/*

中装备,PathInfo 是咱们恳求的详细的 Servlet,QueryString 是要传递的参数,留意这儿是在浏览器里直接输入 URL 所所以经过 Get 办法恳求的,假如是 POST 办法恳求的话,QueryString 将经过表单办法提交到服务器端,这个将在后边再介绍。

上图中 PathInfo 和 QueryString 呈现了中文,当咱们在浏览器中直接输入这个 URL 时,在浏览器端和服务端会怎样编码和解析这个 URL 呢?为了验证浏览器是怎样编码 URL 的咱们挑选 FireFox 浏览器并通软娘驯渣夫过 HTTPFox 插件调查咱们恳求的 URL 的实践的内容,以下是 URL:HTTP://localhost:8080/examples/servlets/servlet/君山?author= 君山 在中文 FireFox3.6.12 的测验成果:

君山的编码成果别离是:e5 90 9b e5 b1 b1,be fd c9 bd,查阅上一届的编码可知,PathInfo 是 UTF-8 编码而 QueryString 是经过 GBK 编码,至于为什么会有“%”?查阅 URL 的编码规范 RFC3986 可知浏览器编码 URL 是将非 ASCII 字符依照某种编码格局编码成 16 进制数字然后将每个 16 进制标明的字节前加上“%”,所以终究的 URL 就成了上图的格局了。

从上面测验成果可知浏览器对 PathInfo 和 QueryString 的编码是不相同的,不同浏览器对 PathInfo 也或许不相同,这就对服务器的解码形成很大的困难,下面咱们以 Tomcat 为例看一下,Tomcat 承受到这个 URL 是怎样解码的。
protected void convertURI(MessageBytes uri, Request request)
thr急浪的终航ows Exception {
ByteChunk bc = uri.g打边炉资料清单孤岛惊魂4,让编码问题不再困惑你,程etByteChunk();
int length = bc.getLength();
CharChunk cc = uri.getCharChunk();
cc.allocate(length, -1);
String enc = connector.getURIEncoding();
if (enc != null) {
B2CConverter 孤岛惊魂4,让编码问题不再困惑你,程conv = request.getURIConverter();
try {
if (conv == null) {
conv = new B2CConverter(enc);
request.setURIConverter(conv);
}
} catch (IOException e) {...}
if (conv != null) {
try {
conv.convert(bc, cc, cc.getBuf仁果网fer().length -
cc.getEnd());
uri.setChars(cc.getBuffer(), cc.getStart(),
cc.getLength());
return;
} catch (IOException e) {...}
}
}
// Default encoding: fast conversion
byte[] bbuf = bc.getBuffer();
char[] cbuf = cc.getBuffer();
int start = bc.getStart()降服花心大少;
for (int i = 0; i < length; i++) {
cbuf[i] = (char) (bbuf[i + start] & 0xff);
}
uri.setChars(cbuf, 0, length);
}

从上面的代码中能够知道对 URL 的 URI 部分进行解码的字符集是在connector的 中界说的,假如没有界说,那么将以默许编码 ISO-8859-1 解析。所以假如有中文 URL 时最好把 URIEncoding 设置成 UTF-8 编码。

QueryString 又怎样解析? GET 办法 HTTP 恳求的 QueryString 与 POST 办法 HTTP 恳求的表单参数都是作为 Parameters 保存,都是经过 request.getParameter 获取参数值。对它们的解码是在 request.getParameter 办法榜首次被调用时进行的。request.getParameter 办法被调用时将会调用 org.apache.catalina.connector.Request 的 parseParameters 办法。这个办法将会对 GET 和 POST 办法传递的参数进行解码,可是它们的解码字符集有或许不相同。POST 表单的解码将在后边介绍,QueryString 的解码字符集是在哪界说的呢?它自身是经过 HTTP 的 Header 传到服务端的,而且也在 URL 中,是否和 URI 的解码字符集相同呢?从前面浏览器对 PathInfo 和 QueryString 的编码采用不同的编码格局不同能够猜想到解码字符集必定也不会是共同的。的确是这样 QueryString 的解码字符集要么是 Header 中 ContentType 中界说的 Charset 要么便是默许的 ISO-8859-1,要运用 ContentType 中界说的编码就要设置 connector 的 中的 useBodyEncodingForURI 设置为 true。这个装备项的姓名有点让人发作混杂,它并不是对整个 URI 都选用 BodyEncoding 进行解码而仅仅是对 QueryString 运用 BodyEncoding 解码,这一点还要特别留意。

从上面的 URL 编码和解码进程来看,比较杂乱,而且编码和解码并不是咱们在运用程序中能彻底操控的,所以在咱们的运用程序中应该尽量防止在 URL 中运用非 ASCII 字符,否则很或许会碰到乱码问题,当然在咱们的服务器端最好设置 中的 URIEncoding 和 useBodyEncodingForURI 两个参数。

HTTP Header 的编解码

当客户端建议一个 HTTP 恳求除了上面的 URL 外还或许会在 Header 中传递其它参数如 Cookie、redirectPath 等,这些用户设置的值很或许也会存在编码问题,Tomcat 对它们又是怎样解码的呢?

对 Header 中的项进行解码也是在调用 request.getHeader 是进行的,假如恳求的 Header 项没有解码则调用 MessageBytes 的 toString 办法,这个办法将从 byte 到 char 的转化运用的默许编码也是 ISO-8859-1,而咱们也不能设置 Header 的其它解码格局,所以假如你设置 Header 中有非 ASCII 字符解码必定会有乱码。

咱们在增加 Header 时也是相同的道理,不要在 Header 中传递非 ASCII 字符,假如一定要传递的话,咱们能够先将这些字符用 org.apache.catalina.util.URLEncoder 编码然后再增加到 Header 中,这样在浏览器到服务器的传递进程中就不会丢掉信息了,假如咱们要拜访这些项时再依照相应的字符集解码体罚故事就好了。

POST 表单的编解码

在前面提到了 PO郑恩智ST 表单提交的参数的解码是在榜首次调用 request.getParameter 发作的,POST 表单参数传递办法与 QueryString 不同,它是经过 HTTP 的 BODY 传递到服务端的。当咱们在页面上点击 submit 按钮时浏览器首要将依据 ContentType 的 Charset 编码格局对表单填的参数进行编码然后提交到服务器端,在服务器端相同也是用 ContentType 中字符集进行解码。所以经过 POST 表单提交的参数一般不会呈现问题,而且这个字符集编码是咱们自己设置的,能够经过 request.setCharacterEncoding(charset) 来设置。

别的针对 大棚歌舞团multipart/form-data 类型的参数,也便是上传的文件编码相同也是运用 ContentType 界说的字符集编码,值得留意的当地是上传文件是用字节省的办法传输到服务器的本地暂时目录,这个进程并没有涉及到字符编码,而真实编码是在将文件内容增加到 parameters 中,假如用这个编码不能编码时将会用默许编码 ISO-8859-1 来编码。

HTTP BODY 的编解码

当用户恳求的资源现已成功获取后,这些内容将经过 Response 回来给客户端浏览器,这个进程先要经过编码再到浏览器进行解码。这个进程的编解码字符集能够经过 response.setCharacterEncoding 来设置,它将会掩盖 request.getCharacterEncoding 的值,而且经过 Header 的 Content-Type 回来客户端,浏览器承受到回来的 socket 流时将经过 Content-Type 的 charset 来解码,假如回来的 HTTP Header 中 Content-Type 没有设置 charset,那么浏览器将依据 Html 的 中的 charset 来解码。假如也没有界说的话,那么浏览器将运用默许的编码来解码。

其他需求留意编码的当地

除了 URL 和参数编码问题外,在服务端还有许多当地或许存在编码,如或许需求读取 xml、velocity 模版引擎、JSP 或许从数据库读取数据等。

xml 文件能够经过设置头来拟定编码格局

 

Velocity 模版设置编码格局:

services.VelocityService.input.encoding=UTF-8 

JSP 设置编码格局:

<%@page contentType="text/html; charset=UTF-8"%>

拜访数据库都是经过客户端 JDBC 驱动来完结,用 JDBC 来存取数据要和数据的内置编码保持共同,能够经过设置 JDBC URL 来拟定如 MySQL:

url="jdbc:mysql://localhost:3306/DB?useUnicode=true&characterEncoding=GBK"

8. 乱码问题剖析#

下面看一下,当咱们碰到一些乱码时,应该怎样处理这些问题?呈现乱码问题仅有的原因都是在 char 到 byte 或 byte 到 char 转化中编码和解码的字符集不共同导马广儒与陈晓旭的爱情致的,因为往往一次操作涉及到屡次编解码,所以呈现乱码时很难查找到底是哪个环节呈现了问题。依据自己的经历,往往从最源头开端一步步查原因是最快的。

9. 参阅#

编码博客

为什么Java最多只能标识65535个字符

Unicode自身仅仅一个规范,不是详细完结,并没有限制字节数。现在用于有用的 Unicode 版别对应于 UCS-2,运用16位的编码空间,因而最大能标明65535个字符。Unicode是开展的,6万个的确不行,事实上现在的Unicode现已支撑超越10万个字符(第10万个于2005年被采用,为马来亚拉姆语。当时的Unicode版别为6.3,2013年9月30日拟定。Java中运用的仍是UCS-2。

版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。