字符编码-用Cheat Engine查看内存线性地址及系统内部编码

Windows操作系统,使用的编码是不是UTF-16

答案:应该是utf-16编码。

实验验证:

  • 新建一个txt文本文档,然后写一段英文字符 比如 helloword,默认保存方式是ANSI也就是常见的ASCII编码。
  • 保存完再重新打开该文档。接着打开Cheat Engine ,选择注入文本编辑器的进程,然后搜索hellword字符串,记得勾选utf-16,找到内存地址后,选择查看内存空间,记住该文本内容存放在内存中的地址是虚拟地址(也叫线性地址),且内存上存放文本内容采用的是utf-16 编码,不再是ANSI编码了。

 

字符编码-windows内部编码探讨用notepad++  查看十六进制代码如下:

字符编码-windows内部编码探讨

字符编码-windows内部编码探讨

字符编码-windows内部编码探讨

这里我们可以看到 hellword 字符串在内存中的虚拟地址(即线性地址)是000B386A


参考文章链接:https://social.microsoft.com/Forums/zh-TW/dc75573f-85c3-47a1-b761-97dda06ee847/windows2580520316319953247965292203512999230340325343072126159199?forum=windowsxpzhchs


windows操作系统区别于大多数其他操作系统的特点之一是,它的大多数内部文本串是以16位宽的Unicode字符来存储和处理的。Unicode(统一的字符编码标准)是一个国际字符集标准,它为世界上绝大多数一直的字符集定义了唯一的16位值(有关Unicode的更多信息,请参考www.unicode.org以及MSDN Library中有关的程序车技文档)
因为许多应用程序只处理8位(单字节)ANSI字符串,所以,接受字符串参数的windows的函数都有两个入口点:一个Unicode(宽字符,16位)和一个ANSI(窄字符,8位)。windows的以下几个版本,即windows95、windows98、windowsME,并没有为所有的windows函数实现Unicode的接口,所有如果所设计的应用程序要考虑到运行在这些操作系统之上,则往往使用窄字符版本。如果你调用一个windows函数的窄字符版本,则输入的字符串参数在被系统处理之前,先转成Unicode,而输出的参数则在被返回给应用程序之前,从Unicode转成ANSI字符串。因此,如果你有一个老的服务程序或者一段代码需要运行在windows上,而这份代码是按照ANSI字符串来编写的,那么windows将把ANSI字符转成Unicode,以便于内部使用。然而,windows永远不会转换文件内部的数据——由应用程序来决定是否要存储为Unicode或者ANIS。
在windows的以前版本中,亚洲和中东版本是美国和欧洲核心版本的一个超集,其中也包含了额外的windows函数,它可以满足处理更加复杂的文本输入和排版需求(比如从右至左的文本输入)。到了windows2000,所有语种的版本都包含同样的windows函数。windows不再使用单独的语言版本,而是有一份全球统一的二进制代码,因此,同一份安装可以支持多种语言(只需求加入各种语言包即可)。应用程序也可以利用这一套windows函数,做到同一份应用程序二进制代码可以支持多种语言!

字符编码-url编码介绍

摘要:url编码(百分号编码)是对字节流编码的,而网页信息编码成字节流有很多种编码方式,我们探讨的是浏览器、JavaScript及服务器在不同情况下,会将网页信息如何编码成字节流。

从目前的http抓包来看,网页发送的请求最终都是ASCII编码的二进制数据流,ASCII解码数据流时,我们能看到请求行,请求头和请求体。

目前的浏览器中,http请求头中 已经不存在  Accept-Charset 字段了。

Accept-Charset:GB2312,utf-8;q=0.7,*;q=0.7

post提交表单时:下面的 charset=utf-8 字段 ,现在的浏览器已经没这个字段了。
也就是说:http请求体中的编码方式不指定,但浏览器会根据网页编码情况和表单编码的设置,对请求体【也就是post提交的表单数据】进行编码。服务器解析时,就默认这个是由本网站的网页提交的,解码时就采用本网站事先规定好的编码方式。

Content-Type: application/x-www-form-urlencoded;charset=utf-8

 

一、url与uri的区别

URI:统一资源标志符URI,就是在某一规则下能把一个资源独一无二地标识出来。
URL:统一资源定位符URL,就是用定位的方式实现的URI。
URN:统一资源命名符URN,就是用独一无二的名字来实现URI。

大白话:URI 包括了 URL 和 URN。【URL和URN采用不同的方式来标识资源】
当然因为协议的要求,如JAVA 的URI转换中,不希望出现”[“和”]”。当然有些URL中可能出现 “[“或”]”,这个东西不符合 URI。所以 不能说 全部的URL 都是 URI。

二、url格式

通常而言,我们所熟悉的 URL 的常见定义格式为:
scheme://host[:port#]/path/…/[url-params][?query-string][#anchor]

scheme //有我们很熟悉的http、https、ftp以及著名的ed2k,迅雷的thunder等。
host   //HTTP服务器的IP地址或者域名
port#  //HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如tomcat的默认端口是8080 http://localhost:8080/
path   //访问资源的路径
url-params  //所带参数
query-string    //发送给http服务器的数据
anchor //锚点定位  就是文章里面的标题,浏览器可以直接跳到具体的标题那节

三、url编码规范史

url的目的就是定位资源,而url编码 的目的就是  为了 创建 url。url编码里面有保留字,就是为了对url里面的冲突内容 进行转义。

核心总结:

以前的url编码,默认字节流是 ASCII编码。url解码字节流后再用ASCII码解码就能知道原来的信息。后来因为各国的文字编码不同,如果用不同的文字编码生成字节流然后url编码,当url解码后不知道用哪种方式解码了。

所以,后来决定 RFC 3986,建议所有新的URI必须对未保留字符不加以百分号编码;其它字符建议先转换为UTF-8字节序列, 然后对其字节值使用百分号编码。此前的URI不受此标准的影响。【按道理来说,编码问题应该解决了】

但是因为历史原因,部分浏览器的url ,还没有完全采用utf-8编码。说白了大家统一认为path路径是用utf-8编码的没有问题,但是querystring 查询串该怎么编码,意见不统一。

这时,JavaScript 因为内部编码是(ucs-2 或者 utf-16 不确定),JavaScript的URL编码输出结果是UTF-8【或者Unicode排列值,不过这个已经废弃了】

所以,前端开发,要先了解一些浏览器url编码问题,然后尽量用JavaScript编码url。 

二进制数据

1994年发布的RFC 1738规定, URI中的二进制数据应该表示为字节的序列,然后对每个字节按照上述方式百分号编码. 例如,字节值0F (十六进制)应表示为”%0F“, 字节值41(十六进制)应表示为”A“或”%41“. 优先使用未保留字符来表示这些字节值,因为这使得URL更短.【这些都是ASCII编码  字符”A”对应 十六进制41】

RFC1738 规定:

“…Only alphanumerics [0-9a-zA-Z], the special characters “$-_.+!*'(),” [not including the quotes – ed], and reserved characters used for their reserved purposes may be used unencoded within a URL.”

“只有字母和数字[0-9a-zA-Z]、一些特殊符号”$-_.+!*'(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。”

字符数据

二进制数据的百分号编码过程已经被外推到字符数据,甚至到不适合或未被完全规范的地步. 在WWW初创阶段,仅仅处理ASCII字符是否编码问题,还没有什么问题。但随后发展到对非ASCII字符如何在URI中编码,缺少标准规范的情况下导致了歧义性的解释URI的错误。

大白话, 如基于RFC 1738与2396的协议规定,字符数据先要根据某种字符编码转换为字节流,然后再表示为URI。如果URI不提供是何种字符编码的提示信息,那么这个URI难以可靠的解析。【互联网早期,URI默认都是ASCII编码的字节流,所以解析没问题。后来各国文字编码的字节流,编码成url后,服务器解析url,然后将字节流解码成哪种编码就不知道了】

当前标准

2005年1月发布的RFC 3986,建议所有新的URI必须对无需编码的字符(下面红字RFC3986的规定中可出现的字符)不加以百分号编码;其它字符建议先转换为UTF-8字节序列, 然后对其字节值使用百分号编码(即URL编码)。此前的URI不受此标准的影响。

RFC3986文档规定,Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符。

非标准的实现

有一些不符合标准的把Unicode字符在URI中表示为: %uxxxx, 其中xxxx是用4个十六进制数字表示的Unicode的码位值。任何RFC都没有这样的字符表示方法,并且已经被W3C拒绝。第三版的ECMA-262仍然包含函数escape(string)使用这种语法, 但也有函数encodeURI(uri)转换字符到UTF-8字节序列并用百分号编码每个字节。

application/x-www-form-urlencoded类型

当HTML表单中的数据被提交时,表单的域名与值被编码并通过HTTP的GET或者POST方法甚至更古远的email把请求发送给服务器。这里的编码方法采用了一个非常早期的通用的URI百分号编码方法,并且有很多小的修改如新行规范化以及把空格符的编码”%20“替换为”+” . 按这套方法编码的数据的MIME类型是application/x-www-form-urlencoded, 当前仍用于(虽然非常过时了)HTML与XForms规范中. 此外,CGI规范包括了web服务器如何解码这类数据、利用这类数据的内容。

如果发送的是HTTP GET请求, application/x-www-form-urlencoded数据包含在所请求URI的查询成分中. 如果发送的是HTTP POST请求或通过email, 数据被放置在消息体中,媒体类型的名字被包含在消息的Content-Type头内部。

字符编码-教程(8)-字符串长度与字符编码

字符串长度与字符编码


在双字节中文编码(DBCS)中,“一个汉字算两个英文字符!一个汉字算两个英文字符……”【其实这句话的本意是:在双字节中文编码中,兼容了ASCII,导致英文编码只占一个字节,中文编码占二个字节,所以一个汉字算两个英文】

比如下面的C代码:
如果用gbk编码保存文件,”我爱你祖国”编码的二进制流占10个字节。程序输出字符串长度为10
如果用utf-8编码保存文件,”我爱你祖国”编码的二进制流占15个字节。程序输出字符串长度为15

#include <stdio.h>
#include <string.h>

int main(){
	
char str[20]="我爱你祖国";

printf("%d\n",strlen(str)); 

getchar(); 
}

/************

在C语言中:strlen 在处理汉字串时,与处理一般的英文串是一样的,
就是计算从串首地址开始检查到'\0'字符的位置,然后计算两个地址的差,返回差值,
也就是字符串中有多个字符(字节)'\0'字符不是汉字的组成部分!
对于一个汉字占几个字节,不同的字符集是不同的,
如果环境变量设置为utf8,则一个汉字占三个字节
如果设置成gbk类,则一个汉字占两个字节

************/

一个字符和两个字节的故事: 这时候,从旧社会里走过来的程序员开始发现一个奇怪的现象:他们的strlen函数靠不住了,一个汉字不再是相当于两个字符了,而是一个!是的,从 UNICODE 开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的“一个字符”!同时,也都是统一的“两个字节”,请注意“字符”和”字节”两个术语的不同,“字节”是一个8位的物理存贮单元,而“字符”则是一个文化相关的符号。在UNICODE 中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。【备注:前面这段话不靠谱,Unicode是字符集,Unicode不是两个字节,具体要看编码方式,测试字符串长度函数:判断长度依据是计算字符数 还是计算字节数】

字符编码-教程(7)-国际化-Unicode与ANSI

一、编码国际化产生原因:

全世界很多个国家都在为自己的文字编码,并且互不想通,不同的语言字符编码值相同却代表不同的符号(例如:韩文编码EUC-KR中“한국어”的编码值正好是汉字编码GBK中的“茄惫绢”)。因此,同一份文档,拷贝至不同语言的机器,就可能成了乱码,这给各国和各地区交换信息带来了很大的困难,同时也给国际化(本地化)编程造成了很大的麻烦。

二、UNICODE和UCS方案

于是人们就想:我们能不能定义一个超大的字符集,它可以容纳全世界所有的文字字符,再对它们统一进行编码,让每一个字符都对应一个不同的编码值,从而就不会再有乱码了。

如果说“各个国家都在为自己文字独立编码”是百家争鸣,那么“建立世界统一的字符编码”则是一统江湖,谁都想来做这个武林盟主。早前就有两个机构试图来做这个事:
(1) 国际标准化组织(ISO),他们于1984年创建ISO/IEC JTC1/SC2/WG2工作组,试图制定一份“通用字符集”(Universal Character Set,简称UCS),并最终制定了ISO 10646标准。
(2) 统一码联盟,他们由Xerox、Apple等软件制造商于1988年组成,并且开发了Unicode标准(The Unicode Standard,这个前缀Uni很牛逼哦—Unique, Universal, and Uniform)。

1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。1991年,不包含CJK统一汉字集的Unicode 1.0发布。随后,CJK统一汉字集的制定于1993年完成,发布了ISO 10646-1:1993,即Unicode 1.1。

从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都独立存在,并独立地公布各自的标准。不过由于Unicode这一名字比较好记,因而它使用更为广泛。

备注:

ISO 10646 定义了 UCS-4 和UCS-2 两种编码形式。
其中UCS-4其编码固定占用4个字节,UCS-2其编码固定占用2个字节

Unicode编码点分为17个平面(plane),每个平面包含216(即65536)个码位(code point)。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从0016到1016,共计17个平面。且第一个平面称为“基本多语言平面”(Basic Multilingual Plane,简称BMP)【ISO 10646 不会超出这些平面赋值,0016代表二进制0000 0000,1016代表二进制0001 0000,所以Unicode 17个平面只占用了三个字节不到,也就是占了21位 编码位

UCS 是所有其他字符集标准的一个超集. 它保证与其他字符集是双向兼容的. 就是说, 如果你将任何文本字符串翻译到 UCS格式, 然后再翻译回原编码, 你不会丢失任何信息。UCS 包含了用于表达所有已知语言的字符。对于还没有加入的语言,由于正在研究怎样在计算机中最好地编码它们, 因而最终它们都将被加入。

1、编码方式产生原因

需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如UTF-8是Unicode的实现方式之一。

比如,汉字的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:1)出现了 Unicode 的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示 Unicode。2)Unicode 在很长一段时间内无法推广,直到互联网的出现。

2、编码方式分类

 UTF-32与UCS-4

在Unicode与ISO 10646合并之前,ISO 10646标准为“通用字符集”(UCS)定义了一种31位的编码形式(即UCS-4),其编码固定占用4个字节(实际上只用了31位,最高位必须为0),编码空间为0x00000000~0x7FFFFFFF(可以编码20多亿个字符)。下面让我们做一些简单的数学游戏:

UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。

UCS-4有20多亿个编码空间,但实际使用范围并不超过0x10FFFF,并且为了兼容Unicode标准,ISO也承诺将不会为超出0x10FFFF的UCS-4编码赋值。由此UTF-32编码被提出来了,它的编码值与UCS-4相同,只不过其编码空间被限定在了0~0x10FFFF之间。因此也可以说:UTF-32是UCS-4的一个子集备注: UTF-32编码长度是固定的,UTF-32中的每个32位值代表一个Unicode码位,并且与该码位的数值完全一致。

UTF-16与UCS-2

除了UCS-4,ISO 10646标准为“通用字符集”(UCS)定义了一种16位的编码形式(即UCS-2),其编码固定占用2个字节,它包含65536个编码空间(可以为全世界最常用的63K字符编码,为了兼容Unicode,0xD800-0xDFFF之间的码位未使用)。例:“汉”的UCS-2编码为6C49。

UCS-2对于ascii里的那些“半角”字符,UNICODE 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于“半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。

但俩个字节并不足以正真地“一统江湖”(a fixed-width 2-byte encoding could not encode enough characters to be truly universal),于是UTF-16诞生了,与UCS-2一样,它使用两个字节为全世界最常用的63K字符编码,不同的是,它使用4个字节对不常用的字符进行编码。UTF-16属于变长编码。

前面提到过:Unicode编码点分为17个平面(plane),每个平面包含216(即65536)个码位(code point),而第一个平面称为“基本多语言平面”(Basic Multilingual Plane,简称BMP),其余平面称为“辅助平面”(Supplementary Planes)。其中“基本多语言平面”(0~0xFFFF)中0xD800~0xDFFF之间的码位作为保留,未使用。UCS-2只能编码“基本多语言平面”中的字符,此时UTF-16与UCS-2的编码一样(都直接使用Unicode的码位作为编码值),例:“汉”在Unicode中的码位为6C49,而在UTF-16编码也为6C49。另外,UTF-16还可以利用保留下来的0xD800-0xDFFF区段的码位来对“辅助平面”的字符的码位进行编码,因此UTF-16可以为Unicode中所有的字符编码。

UTF-16中如何对“辅助平面”进行编码呢?

Unicode的码位区间为0~0x10FFFF,除“基本多语言平面”外,还剩0xFFFFF个码位(并且其值都大于或等于0x10000)。对于“辅助平面”内的字符来说,如果用它们在Unicode中码位值减去0x10000,则可以得到一个0~0xFFFFF的区间(该区间中的任意值都可以用一个20-bits的数字表示)。该数字的前10位(bits)加上0xD800,就得到UTF-16四字节编码中的前两个字节;该数字的后10位(bits)加上0xDC00,就得到UTF-16四字节编码中的后两个字节。例如:

(这个字念啥?^_^)
上面这个汉字的Unicode码位值为2AEAB,减去0x10000得到1AEAB(二进制值为0001 1010 1110 1010 1011),前10位加上D800得到D86B,后10位加上DC00得到DEAB。于是该字的UTF-16编码值为D86BDEAB(该值为大端表示,小端为6BD8ABDE)。


UTF-16是Unicode字符集的一种转换方式,即把Unicode的码位转换为16比特长的码元串行,以用于数据存储或传递。UTF-16编码规则如下:

2.2.1 从U+D800到U+DFFF的码位(代理区)

因为Unicode字符集的编码值范围为0-0x10FFFF,而大于等于0x10000的辅助平面区的编码值无法用2个字节来表示,所以Unicode标准规定:基本多语言平面内,U+D800..U+DFFF的值不对应于任何字符,为代理区。因此,UTF-16利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。

但是在使用UCS-2的时代,U+D800..U+DFFF内的值被占用,用于某些字符的映射。但只要不构成代理对,许多UTF-16编码解码还是能把这些不符合Unicode标准的字符映射正确的辨识、转换成合规的码元. 按照Unicode标准,这种码元串行本来应算作编码错误.

2.2.2 从U+0000至U+D7FF以及从U+E000至U+FFFF的码位

第一个Unicode平面(BMP),码位从U+0000至U+FFFF(除去代理区),包含了最常用的字符。UTF-16与UCS-2编码在这个范围内的码位为单个16比特长的码元,数值等价于对应的码位。BMP中的这些码位是仅有的码位可以在UCS-2被表示。

2.2.3 从U+10000到U+10FFFF的码位

辅助平面(Supplementary Planes)中的码位,大于等于0x10000,在UTF-16中被编码为一对16比特长的码元(即32bit,4Bytes),称作 code units called a 代理对(surrogate pair),具体方法是:

Ø 码位减去0x10000, 得到的值的范围为20比特长的0..0xFFFFF(因为Unicode的最大码位是0x10ffff,减去0x10000后,得到的最大值是0xfffff,所以肯定可以用20个二进制位表示),写成二进制形式:yyyy yyyy yyxx xxxx xxxx。

Ø 高位的10比特的值(值的范围为0..0x3FF)被加上0xD800得到第一个码元或称作高位代理(high surrogate), 值的范围是0xD800..0xDBFF。由于高位代理比低位代理的值要小,所以为了避免混淆使用,Unicode标准现在称高位代理为前导代理(lead surrogates)。

Ø 低位的10比特的值(值的范围也是0..0x3FF)被加上0xDC00得到第二个码元或称作低位代理(low surrogate), 现在值的范围是0xDC00..0xDFFF。 由于低位代理比高位代理的值要大,所以为了避免混淆使用,Unicode标准现在称低位代理为后尾代理(trail surrogates)。

Ø 最终的UTF-16(4字节)的编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有两个WORD,第一个WORD的高6位是110110,第二个WORD的高6位是110111。可见,第一个WORD的取值范围(二进制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二个WORD的取值范围(二进制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。上面所说的从U+D800到U+DFFF的码位(代理区),就是为了将一个WORD(2字节)的UTF-16编码与两个WORD的UTF-16编码区分开来。

由于高位代理、低位代理、BMP中的有效字符的码位,三者互不重叠,搜索是简单的: 一个字符编码的一部分不可能与另一个字符编码的不同部分相重叠。这意味着UTF-16是自同步(self-synchronizing):可以通过仅检查一个码元就可以判定给定字符的下一个字符的起始码元。 UTF-8也有类似优点,但许多早期的编码模式就不是这样,必须从头开始分析文本才能确定不同字符的码元的边界。

由于最常有的字符都在基本多文种平面中,许多软件的处理代理对的部分往往得不到充分的测试。这导致了一些长期的bug与潜在安全漏洞,甚至在广为流行得到良好评价的应用软件。


UTF-8

从前述内容可以看出:无论是UTF-16/32还是UCS-2/4,一个字符都需要多个字节来编码,这对那些英语国家来说多浪费带宽啊!(尤其在网速本来就不快的那个年代。。。)由此,UTF-8产生了。在UTF-8编码中,ASCII码中的字符还是ASCII码的值,只需要一个字节表示,其余的字符需要2字节、3字节或4字节来表示。

UTF-8的编码规则:

(1) 对于ASCII码中的符号,使用单字节编码,其编码值与ASCII值相同(详见:U0000.pdf)。其中ASCII值的范围为0~0x7F,所有编码的二进制值中第一位为0(这个正好可以用来区分单字节编码和多字节编码)。

(2) 其它字符用多个字节来编码(假设用N个字节),多字节编码需满足:第一个字节的前N位都为1,第N+1位为0,后面N-1 个字节的前两位都为10,这N个字节中其余位全部用来存储Unicode中的码位值。

字节数 Unicode UTF-8编码
1 000000-00007F 0xxxxxxx
2 000080-0007FF 110xxxxx 10xxxxxx
3 000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx
4 010000-10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8 有以下编码规则:

  1. 如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 – 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。
  2. 如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节。
  3. 如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节

3、编码方式总结

(1) 简单地说:Unicode属于字符集,不属于编码,UTF-8、UTF-16等是针对Unicode字符集的编码。
(2) UTF-8、UTF-16、UTF-32、UCS-2、UCS-4对比:

对比 UTF-8 UTF-16 UTF-32 UCS-2 UCS-4
编码空间 0-10FFFF 0-10FFFF 0-10FFFF 0-FFFF 0-7FFFFFFF
最少编码字节数 1 2 4 2 4
最多编码字节数 4 4 4 2 4
是否依赖字节序

4、编码的储存和传输


  • windows换装: 从前多种字符集存在时,那些做多语言软件的公司遇上过很大麻烦,他们为了在不同的国家销售同一套软件,就不得不在区域化软件时也加持那个双字节字符集咒语,不仅要处处小心不要搞错,还要把软件中的文字在不同的字符集中转来转去。UNICODE 对于他们来说是一个很好的一揽子解决方案,于是从 Windows NT 开始,MS 趁机把它们的操作系统改了一遍,把所有的核心代码都改成了用 UNICODE 方式工作的版本,从这时开始,WINDOWS 系统终于无需要加装各种本土语言系统,就可以显示全世界上所有文化的字符了。
  • UNICODE和DBCS的转换: 但是,UNICODE 在制订时没有考虑与任何一种现有的编码方案保持兼容,这使得 GBK 与UNICODE 在汉字的内码编排上完全是不一样的,没有一种简单的算术方法可以把文本内容从UNICODE编码和另一种编码进行转换,这种转换必须通过查表来进行。
  • BMP
    • UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
    • group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。

  • UTF: UNICODE 来到时,一起到来的还有计算机网络的兴起,UNICODE 如何在网络上传输也是一个必须考虑的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到UTF时并不是直接的对应,而是要过一些算法和规则来转换。
  • UCS和UTF的区别: UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。
  • Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如,汉字“严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。
  • 这里就有两个严重的问题
    • 第一个问题是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?
    • 第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。
  • 它们造成的结果是:
    • 出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。
    • unicode在很长一段时间内无法推广,直到互联网的出现。
  • UTF-8
    • 互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。
    • UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
    • 下面是Unicode和UTF-8转换的规则
       1 Unicode 
       2    
       3 UTF-8 
       4    
       5 0000 - 007F 
       6    
       7 0xxxxxxx 
       8    
       9 0080 - 07FF 
      10    
      11 110xxxxx 10xxxxxx 
      12    
      13 0800 - FFFF 
      14    
      15 1110xxxx 10xxxxxx 10xxxxxx

例如”汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 1100 0100 1001,将这个比特流按三字节模板的分段方法分为0110 110001 001001,依次代替模板中的x,得到:1110-0110 10-110001 10-001001,即E6 B1 89,这就是其UTF8的编码。

三、ANSI编码方案


  • 字节: 很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们看到8个开关状态是好的,于是他们把这称为”字节”。
  • 每个字节可表示256个不同的状态: 再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为”计算机”。开始时,计算机只在美国用。八位的字节一共可以组合出256(2的8次方)种不同的状态。
  • 控制码: 在ASCII码中,第0~31号及第127号(共33个)是控制字符或通讯专用字符,如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(振铃)等;通讯专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等。
  • ASCII: 他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。大家看到这样,都感觉很好,于是大家都把这个方案叫做“Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。
  • 扩展字符集: 后来,就像建造巴比伦塔一样,世界各地的都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称“扩展字符集”。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0—127表示的符号是一样的,不一样的只是128—255的这一段。从此之后,贪婪的人类再没有新的状态可以用了,美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧!
  • 疑问:ANSI编码是什么
      • ANSI,American National Standard Institite,美国国家标准协会。
      • ANSI编码是一种字符代码,为使计算机支持更多语言,通常使用 0x00~0x7f 范围的1 个字节来表示 1 个英文字符。超出此范围的使用0x80~0xFFFF来编码,即扩展的ASCII编码。
      • ANSI编码 为使计算机支持更多语言,通常使用 0x80~0xFFFF 范围的 2 个字节来表示 1 个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。
      • 不同的国家和地区制定了不同的标准,由此产生了 GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的编码标准。这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码。
      • 不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。ANSI编码表示英文字符时用一个字节,表示中文用两个或四个字节。

很长, 终于讲完了我想要说后些名字和细节,但是还有一些名词在上边没有提到,这里再单独解释一下。

大白话:关于代码页(code page)问题。处理多语言的编码方案有两种。

1、将所有语言的每一个符号都统一编码到一个字符集里面。如Unicode方案。

2、将文件编码抽象成一种,根据系统语言环境,再用具体的编码保存。如ANSI方案。但“ANSI编码”确实只存在于Windows系统。ANSI其实是多种编码的集合,具体的编码与解码方案则是根据代码页(code page)来实现的。而code page 则与本地计算机的 语言有关。

那么Windows系统是如何区分ANSI背后的真实编码的呢?

微软用一个叫“Windows code pages”(在命令行下执行chcp命令可以查看当前code page的值)的值来判断系统默认编码,比如:简体中文的code page值为936(它表示GBK编码,win95之前表示GB2312,详见:Microsoft Windows’ Code Page 936),繁体中文的code page值为950(表示Big-5编码)。

我们能否通过修改Windows code pages的值来改变“ANSI编码”呢?

命令提示符下,我们可以通过chcp命令来修改当前终端的active code page,例如:
(1) 执行:chcp 437,code page改为437,当前终端的默认编码就为ASCII编码了(汉字就成乱码了);
(2) 执行:chcp 936,code page改为936,当前终端的默认编码就为GBK编码了(汉字又能正常显示了)。
上面的操作只在当前终端起作用,并不会影响系统默认的“ANSI编码”。(更改命令行默认codepage参看:设置cmd的codepage的方法)。

Windows下code page是根据当前系统区域(locale)来设置的,要想修改系统默认的“ANSI编码”,我们可以通过修改系统区域来实现(“控制面板” =>“时钟、语言和区域”=>“区域和语言”=>“管理”=>“更改系统区域设置…”):

图中的系统locale为简体中文,意味着当前“ANSI编码”实际是GBK编码。当你把它改成Korean(Korea)时,“ANSI编码”实际是EUC-KR编码,“한국어”就能正常显示了;当你把它改成English(US)时,“ANSI编码”实际是ASCII编码,“汉字”和“한국어”都成乱码了。(改了之后需要重启系统的。。。)

说明:locale是国际化与本地化中重要的概念,本文不深入讲解该内容。


  • 内码: 字符必须编码后才能被计算机处理。 计算机使用的缺省编码方式就是计算机的内码。上文提到的ASCII, GB2312, big5等都可以叫做内码
  • Code page
    • UNICODE跟Code Page应该说是显示全世界语言的两个解决方案
    • 前一个方案是将全世界的语言的每一个编码都映射成一个编号
    • 后一种解决方案则是根据不同的采取重复的编号,根据不同的Code Page来决定一个编号是什么字符。同一个编号在不同的Code Page下代表不同的字符
    • 如果你安装的是英文版的XP,Code Page选择的是简体中文,那么你可以正常显示Unicode字符和Code page为中文的编号,无法显示Code Page为繁体和日语的编号。如果要显示后者,则必须切换成相应的Code page,或者将字符编码换成Unicode。
  • 字符集和代码页
    对于ANSI编码方式,存在不同的字符集(Charset)。同样的字节序列,在不同的字符集下表示的字符不一样。要正确解析一个ANSI字符串,还要选择正确的字符集,否则就可能导致所谓的乱码现象。不同语言版本的操作系统,都有一个默认的字符集。在不指定字符集的情况下,系统会使用此字符集来解析 ANSI 字符串。也就是说,如果我们在简体中文版的Windows下打开了一个由日文操作系统保存的 ANSI 文本文件(仅包含 ANSI 字符串的文本文件),我们看到的将是乱码。但是,如果我们使用Visual Studio之类的带编码选择的文本编辑器打开此文件,并且选择正确的字符集,我们将可以看到它的原貌。注意:简体中文字符集中的繁体字和繁体中文字符集中的繁体字,编码不一定相同(事实证明,似乎是完全不同)。
    每个字符集都有一个唯一的编号,称为代码页(Code Page)。简体中文(GB2312)的代码页为936,而系统默认字符集的代码页为0,它表示根据系统的语言设置来选择一个合适的字符集。

字符编码-教程(6)-简体汉字的区位码、国标码、内码、外码及字型码

GB2312、GBK、GB18030等GB类汉字编码方案的具体实现方式是怎样的?区位码是什么?国标码是什么?内码、外码、字形码又是什么意思?它们是如何转换的,又为什么要这样转换?

下面以GB2312为例来加以说明(由于GBK、GB18030是以GB2312为基础扩展而来,因此编码实现方式与GB2312一样)。

一、区位码

1、整个GB2312字符集分成94个区,每区有94个位,每个区位上只有一个字符,即每区含有94个汉字或符号,用所在的区和位来对字符进行编码(实际上就是字符编号、码点编号),因此称为区位码(或许叫“区位号”更为恰当)。

换言之,GB2312将包括汉字在内的所有字符编入一个94 * 94的二维表,行就是“区”、列就是“位”,每个字符由区、位唯一定位,其对应的区、位编号合并就是区位码。比如“万”字在45区82位,所以“万”字的区位码是:45 82(注意,GB类汉字编码为双字节编码,因此,45相当于高位字节,82相当于低位字节)。

2、GB2312字符集中:

1)01~09区(682个):特殊符号、数字、英文字符、制表符等,包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母等在内的682个全角字符;

2)10~15区:空区,留待扩展;

3)16~55区(3755个):常用汉字(也称一级汉字),按拼音排序;

4)56~87区(3008个):非常用汉字(也称二级汉字),按部首/笔画排序;

5)88~94区:空区,留待扩展。

二、国标码(交换码)

1、为了避开ASCII字符中的不可显示字符0000 0000 ~ 0001 1111(十六进制为0 ~ 1F,十进制为0 ~ 31)及空格字符0010 0000(十六进制为20,十进制为32)(至于为什么要避开、又为什么只避开ASCII中0~32的不可显示字符和空格字符,后文有解释),国标码(又称为交换码)规定表示汉字的范围为(0010 0001,0010 0001) ~ (0111 1110,0111 1110),十六进制为(21,21) ~ (7E,7E),十进制为(33,33) ~ (126,126)(注意,GB类汉字编码为双字节编码)。

因此,必须将“区码”和“位码”分别加上32(十六进制为20H,后缀H表示十六进制),作为国标码。也就是说,国标码相当于将区位码向后偏移了32,以避免与ASCII字符中0~32的不可显示字符和空格字符相冲突。

2、注意,国标码中是分别将区位码中的“区”和“位”各自加上32(20H)的,因为GB2312是DBCS双字节字符集,国标码属于双字节码,“区”和“位”各作为一个单独的字节。

这样我们可以算出“万”字的国标码十进制为:(45+32,82+32) = (77,114),十六进制为:(4D,72H),二进制为:(0100 1101,0111 0010)。

三、内码(机内码)

1、不过国标码还不能直接在计算机上使用,因为这样还是会和早已通用的ASCII码冲突(导致乱码)。

比如,“万”字国标码中的高位字节77与ASCII的“M”冲突,低位字节114与ASCII的“r”冲突。因此,为避免与ASCII码冲突,规定国标码中的每个字节的最高位都从0换成1,即相当于每个字节都再加上128(十六进制为80,即80H;二进制为1000 0000),从而得到国标码的“机内码”表示,简称“内码”。

2、由于ASCII码只用了一个字节中的低7位,所以,这个首位(最高位)上的“1”就可以作为识别汉字编码的标志,计算机在处理到首位是“1”的编码时就把它理解为汉字,在处理到首位是“0”的编码时就把它理解为ASCII字符。

比如:

77 + 128 = 205(二进制为1100 1101,十六进制为CD)

114+ 128 = 242(二进制为1111 0010,十六进制为F2)

3、我们可以来检验一下。打开记事本输入“万”字,编码选择为ANSI(Windows记事本中的ANSI编码对于简体汉字而言就是GB类编码,详见后文解释),保存,如下图所示。

然后用二进制编辑器(比如UltraEdit)打开刚才保存的文件,切换到十六进制模式,会看到:CD F2,这就是“万”字的内码,如下图所示。

 4、总结一下:

从区位码(国家标准定义) —> 区码和位码分别+32(即+20H)得到国标码 —> 再分+128(即+80H)得到机内码(与ACSII码不再冲突)。

因此,区位码的区和位分别+160(即+A0H,32+128=160)可直接得到内码。用十六进制表示就是:

区位码(区码,位码) + (20H,20H) + (80H,80H) =区位码(区码,位码) + (A0H,A0H) = 内码(高字节,低字节)。

四、为什么要加上20H和80H?

1、区位码、国标码、内码的转换非常简单,但是令人迷惑的是为什么要这么转换?

首先,需要注意到一点,GB2312虽说是对中文编码,但是里面也有对26个英文字母和一些特殊符号的编码,按理说这些和ASCII重合的字符(33~127)应该无需再重新编码,直接沿用ASCII中的不就行了?

2、原来,当时在制定GB2312时,决定对ASCII中的可打印字符,也就是英文字母、数字和符号部分(33~126,127为不可打印的DEL)重新编入GB2312中,以两个字节表示,称之为全角字符(全角字符在屏幕上的显示宽度为ASCII字符的两倍,后来也因此而将对应的ASCII字符称之为半角字符)。

而对于ASCII中前32个不可显示也不可打印的控制字符(ASCII码为0~31),以及第33个可显示但不可打印的空格字符(ASCII码为32)等共33个不可打印字符的编码则直接沿用,不再重新编码。

3、因为要保留这33个不可打印字符,就不能直接采用区位码作为计算机直接处理的机内码,需要将区位码向后偏移32以避开冲突(为什么是偏移32,而不是偏移33?因为区位码中的区码和位码都是从1开始计数的,不像ASCII码是从0开始计数的)。

十进制数字32的十六进制表示就是20(为区别于十进制,记作20H),这也就是区位码要加上20H(区码和位码各自加上20H)才能得到国标码的原因。

4、很显然,如果直接采用国标码作为计算机直接处理的机内码的话,还将会产生另一个弊端,即用ASCII码编码的英文字符在GB2312编码环境中无法打开,一打开就会乱码。

因为国标码虽然相较于区位码避开了ASCII码中0~32的前33个不可打印字符,但并没有避开ASCII码中的英文字母、数字和符号(33~126,共94个字符,127为不可打印的DEL)等可打印字符。也就是说,国标码并不是完全兼容ASCII码的。

5、为了解决这个弊端,考虑到ASCII码只使用了一个字节中的低7位,最高位(即首位)为0,于是决定将国标码每个字节的最高位设为1(国标码的两个字节中的最高位都恒为0,即国标码中的每个字节实际上也只用了一个字节中的低7位),这就是GB2312的机内码(即内码),简称GB2312码。

这样一来就彻底区分开了ASCII码和GB2312码。这也是为什么国标码还要加上(80H,80H)才能得到机内码的原因。

6、看到这里,有人或许又要问了:如果仅仅是为了避免与ASCII编码相冲突,为什么最初不直接将区位码的区码和位码的最高位从0改为1(相当于各自直接加上128),这样不就无需经过国标码多此一举的中间转换了吗?而且还无需后移32,也就不用浪费这部分编码空间。

对此本人也很困惑,在网上搜了很久也没找到答案,因此具体原因不得而知。或许是一开始考虑不周?或许为了未来扩展所需而预留一部分空间?又或许是有其他不得已的原因?有知道的朋友还望能指点迷津。

五、外码(输入码、输入法编码)

1、外码也叫输入码、输入法编码,是用来将汉字输入到计算机中的一组键盘符号,是作为汉字输入用的编码。

英文字母只有26个,可以把所有的字符都放到键盘上,而使用这种办法把所有的汉字都放到键盘上,是不可能的。所以汉字系统需要有自己的输入码体系,使汉字与键盘能建立对应关系。

2、目前常用的外码分为以下几类:

1)数字编码,比如区位码;

2)拼音编码,比如全拼、双拼、自然码等;

3)字形编码,比如五笔、表形码、郑码等。

六、字形码(字型码、字模码、输出码)

1、字形码,又称为字型码、字模码、输出码,属于点阵代码的一种。

为了将汉字在显示器或打印机上输出,把汉字按图形符号设计成点阵图,就得到了相应的点阵代码(字形码)。

也就是用0、1表示汉字的字形,将汉字放入n行*n列的正方形(点阵)内,该正方形共有n^2个小方格,每个小方格用一位二进制表示,凡是笔划经过的方格值为1,未经过的值为0。

2、显示一个汉字一般采用16×16点阵或24×24点阵或48×48点阵。已知汉字点阵的大小,可以计算出存储一个汉字所需占用的字节空间。

比如,用16×16点阵表示一个汉字,就是将每个汉字用16行,每行16个点表示,一个点需要1位二进制代码,16个点需用16位二进制代码(即2个字节),共16行,所以需要16行×2字节/行=32字节,即16×16点阵表示一个汉字,字形码需用32字节。

因此,字节数=点阵行数×(点阵列数/8)。

3、显然,字形码所表示的字符,相对于抽象字符表ACR里的“抽象”字符,可称之为“具体”字符,因为具有了“具体”的外形。

4、为了将汉字的字形显示输出或打印输出,汉字信息处理系统还需要配有汉字字形库,也称字模库,简称字库,它集中了汉字的字形信息。

字库按输出方式可分为显示字库和打印字库。用于显示输出的字库叫显示字库,工作时需调入内存。用于打印输出的字库叫打印字库,工作时无需调入内存。

字库按存储方式也可分为软字库和硬字库。软字库以文件的形式存放在硬盘上,现多用这种方式。硬字库则将字库固化在一个单独的存储芯片中,再和其它必要的器件组成接口卡,插接在计算机上,通常称为汉卡。这种方式现已淘汰。

七、小结

可以这样理解,为在计算机内表示汉字而采取统一的编码方式所形成的汉字编码叫内码。为方便汉字输入而形成的汉字编码为外码,也叫输入码。为显示输出和打印输出汉字而形成的汉字编码为字形码,也称为字模码、输出码。

计算机通过键盘输入的外码(重码时还需附加选择编号)对应于汉字内码,将汉字外码转换(即映射)为汉字内码,以实现输入汉字的目的;通过汉字内码在字模库(即字库)中找出汉字的字形码,将汉字内码转换(即映射)为汉字字形码,以实现显示输出和打印输出汉字的目的。

事实上,英文字符的输入、处理和显示过程大致上也差不多,只不过英文字符不需要输入码(即外码),直接在键盘上输入对应的英文字母即可。

字符编码-教程(3)-EASCII及ISO 8859字符编码方案

1、计算机出现之后,首先逐渐从美国发展到了欧洲。由于欧洲很多国家所用到的字符中,除了基本的、美国也用的那128个ASCII字符之外,还有很多衍生的拉丁字母等字符。比如,在法语中,字母上方有注音符号;而欧洲其他国家也有各自特有的字符。

考虑到一个字节能够表示的编码实际上有256个(2^8 = 256),而ASCII字符却只用到了一个字节中的低7位(因此在ASCII码中最高位总是为0),编号为0x00~0x7F(十进制为0~127)。也就是说,ASCII只使用了一个字节所能表示的256个编码中的前128个(2^7 = 128)编码,而后128个编码相当于被闲置了。因此,欧洲各国纷纷打起了后面这128个编码的主意。

2、可问题在于,欧洲各国同时都有这样的想法。于是各国针对后面的0x80~0xFF(十进制为128~255)这128个编码分别对应什么样的字符,就有了各自不同的设计。

为了结束欧洲各国这种各自为政的混乱局面,于是又先后设计了两套统一的,既兼容ASCII码,又支持欧洲各国所使用的那些衍生字符的单字节编码方案:一个是EASCII(Extended ASCII)字符编码方案,另一个是ISO/IEC 8859字符编码方案。

3、先来说EASCII码。EASCII码同样也是将ASCII中闲置的最高位(即首位)用来编码新的字符(这些ASCII字符之外的新字符,其最高位总是为1)。换言之,也就是将一个字节中的全部8个比特位用来表示一个字符。比如,法语中的é的编码为130(二进制1000 0010)。

显然,EASCII码虽与ASCII码一样使用单字节编码,但却可以表示最多256个字符(2^8 = 256),比ASCII的128个字符(2^7=128)多了一倍。

因此,在EASCII码中,当第一个比特位(即字节的最高位)为0时,仍表示之前那些常用的ASCII字符(实际的二进制编码为0000 0000 ~ 0111 1111,对应的十进制就是0~127),而为1时就表示补充扩展的其他衍生字符(实际的二进制编码为1000 0000 ~ 1111 1111,对应的十进制就是128~255)。

这样就在ASCII码的基础上,既保证了对ASCII码的兼容性,又补充扩展了新的字符,于是就称之为Extended ASCII(扩展ASCII)码,简称EASCII码。

EASCII码比ASCII码扩充出来的符号包括表格符号、计算符号、希腊字母和特殊的拉丁符号,如下表所示。

扩展ASCII(EASCII)编码表

 

4、不过,EASCII码目前已经很少使用,常用的是ISO/IEC 8859字符编码方案。该方案与EASCII码类似,也同样是在ASCII码的基础上,利用了ASCII的7位编码所没有用到的最高位(首位),将编码范围从原先ASCII码的0x00~0x7F(十进制为0~127),扩展到了0x80~0xFF(十进制为128~255)。

ISO/IEC 8859字符编码方案所扩展的这128个编码中,实际上只有0xA0~0xFF(十进制为160~255)被实际使用。也就是说,只有0xA0~0xFF(十进制为160~255)这96个编码定义了字符,而0x80~0x9F (十进制为128~159)这32个编码并未定义字符。

显然,ISO/IEC 8859字符编码方案同样是单字节编码方案,也同样完全兼容ASCII。

5、注意,与ASCII、EASCII属于单个独立的字符集不同,ISO/IEC 8859是一组字符集的总称,其下共包含了15个字符集,即ISO/IEC 8859-n,其中n=1,2,3,…,15,16(其中12未定义,所以共15个)。

这15个字符集大致上包括了欧洲各国所使用到的字符(甚至还包括一些外来语字符),而且每一个字符集的补充扩展部分(即除了兼容ASCII字符之外的部分)都只实际使用了0xA0~0xFF(十进制为160~255)这96个编码。

其中,ISO/IEC 8859-1收录了西欧常用字符(包括德法两国的字母),目前使用得最为普遍。ISO/IEC 8859-1往往简称为ISO 8859-1,而且还有一个称之为Latin-1(也写作Latin1)的别名。

6、其余从ISO 8859-2到ISO 8859-16各自所收录的字符如下:

ISO 8859-2字符集,也称为Latin-2,收录了东欧字符;

ISO 8859-3字符集,也称为Latin-3,收录了南欧字符;

ISO 8859-4字符集,也称为Latin-4,收录了北欧字符;

ISO 8859-5字符集,也称为Cyrillic,收录了斯拉夫语系字符;

ISO 8859-6字符集,也称为Arabic,收录了阿拉伯语系字符;

ISO 8859-7字符集,也称为Greek,收录了希腊字符;

ISO 8859-8字符集,也称为Hebrew,收录了西伯莱(犹太人)字符;

ISO 8859-9字符集,也称为Latin-5或Turkish,收录了土耳其字符;

ISO 8859-10字符集,也称为Latin-6或Nordic,收录了北欧(主要指斯堪地那维亚半岛)的字符;

ISO 8859-11字符集,也称为Thai,从泰国的TIS620标准字符集演化而来;

ISO 8859-12字符集,目前尚未定义(未定义的原因目前有两种说法:一是原本要设计成一个包含塞尔特语族字符集的“Latin-7”,但后来塞尔特语族变成了ISO 8859-14 / Latin-8;二是原本预留给印度天城体梵文的,但后来却搁置了);

ISO 8859-13字符集,也称为Latin-7,主要函盖波罗的海(Baltic)诸国的文字符号,也补充了一些被Latin-6遗漏的拉脱维亚(Latvian)字符;

ISO 8859-14字符集,也称为Latin-8,它将Latin-1中的某些符号换成塞尔特语(Celtic)的字符;

ISO 8859-15字符集,也称为Latin-9,或者被戏称为Latin-0,它将Latin-1中较少用到的符号删除,换成当初遗漏的法文和芬兰字母,还把英镑和日元之间的金钱符号,换成了欧盟货币符号;

ISO 8859-16字符集,也称为Latin-10,涵盖了阿尔巴尼亚语、克罗地亚语、匈牙利语、意大利语、波兰语、罗马尼亚语及斯洛文尼亚语等东南欧国家语言。

 

字符编码-教程(2)-EBCDIC码与ASCII码的由来

一、为什么需要对字符进行编码

1、计算机一开始发明出来时是用来解决数字计算问题的,后来人们发现,计算机还可以做更多的事,例如文本处理。

但计算机其实挺笨的,它只“认识”010110111000…这样由0和1两个数字组成的二进制数字,这是因为计算机的底层硬件实现就是用电路的开和闭两种状态来表示0和1两个数字的。因此,计算机只可以直接存储和处理二进制数字。

2、为了在计算机上也能表示、存储和处理像文字、符号等等之类的字符,就必须将这些字符转换成二进制数字。

当然,肯定不是我们想怎么转换就怎么转换,否则就会造成同一段二进制数字在不同计算机上显示出来的字符不一样的情况,因此必须得定一个统一的、标准的转换规则。

二、EBCDIC码与ASCII码

1、于是最开始出现了EBCDIC(Extended Binary Coded Decimal Interchange Code扩展二进制编码的十进制交换码)编码标准。EBCDIC码是由国际商用机器公司(IBM)为大型机操作系统而开发设计的,于1964年推出。

在EBCDIC码中,英文字母不是连续排列的,中间出现多次断续,这带来了一些困扰和麻烦。

因此,在后来IBM的个人计算机和工作站操作系统中并没有采用EBCDIC码,而是采用了晚于EBCDIC码推出、且后来成为了英文字符编码工业标准的ASCII编码方案。

EBCDIC编码表

 

2、ASCII码(American Standard Code for Information Interchange美国信息交换标准码),由美国国家标准学会ANSI(American National Standard Institute)于1968年正式制定。后又于1972年被ISO/IEC采用,制定为ISO/IEC 646标准(ISO,即国际标准化组织International Standardization Organization,成立于1946年;IEC,即国际电工技术委员会International Electrotechnical Commission,成立于1906年;ISO/IEC往往用来表示由这两大国际组织联合制定的标准)。

由于ASCII码要晚于EBCDIC码出现(网上也有文章说是ASCII码要早于EBCDIC码开始设计,但1968年ASCII码才正式确定为标准),ASCII码的编码方式参照了EBCDIC码,并吸取了其经验教训,将英文字母进行了连续排列,这方便了程序处理。

3、ASCII编码方案虽然不是最早出现的字符编码方案,但却是最基础、最重要、应用最广泛的字符编码方案。

目前所通行的其他字符编码方案,比如ISO-8859、GB系列(GB2312、GBK、GB18030、GB13000)、Big5、Unicode等等,均直接或间接兼容ASCII码。

而像EBCDIC这样与ASCII完全不兼容的编码方案,基本上处于已淘汰或将要淘汰的境地。

三、ASCII字符编码方案介绍

1、ASCII码使用七个二进制数字(bit比特、位)来表示一个字符,总共表示128个字符(2^7 = 128,二进制编码为0000 0000 ~ 0111 1111,对应的十进制就是0~127)。

由于个人计算机普遍采用8位一个字节来进行存取与处理,因此剩下最高位的那1比特一般为0,但有时也被用作一些通讯系统的奇偶校验位。

ASCII编码表

Bin(二进制)
Oct(八进制)
Dec(十进制)
Hex(十六进制)
缩写/字符
解释
0000 0000
0
0
00
NUL(null)
空字符
0000 0001
1
1
01
SOH(start of headline)
标题开始
0000 0010
2
2
02
STX (start of text)
正文开始
0000 0011
3
3
03
ETX (end of text)
正文结束
0000 0100
4
4
04
EOT (end of transmission)
传输结束
0000 0101
5
5
05
ENQ (enquiry)
请求
0000 0110
6
6
06
ACK (acknowledge)
收到通知
0000 0111
7
7
07
BEL (bell)
响铃
0000 1000
10
8
08
BS (backspace)
退格
0000 1001
11
9
09
HT (horizontal tab)
水平制表符
0000 1010
12
10
0A
LF (NL line feed, new line)
换行键
0000 1011
13
11
0B
VT (vertical tab)
垂直制表符
0000 1100
14
12
0C
FF (NP form feed, new page)
换页键
0000 1101
15
13
0D
CR (carriage return)
回车键
0000 1110
16
14
0E
SO (shift out)
不用切换
0000 1111
17
15
0F
SI (shift in)
启用切换
0001 0000
20
16
10
DLE (data link escape)
数据链路转义
0001 0001
21
17
11
DC1 (device control 1)
设备控制1
0001 0010
22
18
12
DC2 (device control 2)
设备控制2
0001 0011
23
19
13
DC3 (device control 3)
设备控制3
0001 0100
24
20
14
DC4 (device control 4)
设备控制4
0001 0101
25
21
15
NAK (negative acknowledge)
拒绝接收
0001 0110
26
22
16
SYN (synchronous idle)
同步空闲
0001 0111
27
23
17
ETB (end of trans. block)
结束传输块
0001 1000
30
24
18
CAN (cancel)
取消
0001 1001
31
25
19
EM (end of medium)
媒介结束
0001 1010
32
26
1A
SUB (substitute)
代替
0001 1011
33
27
1B
ESC (escape)
换码(溢出)
0001 1100
34
28
1C
FS (file separator)
文件分隔符
0001 1101
35
29
1D
GS (group separator)
分组符
0001 1110
36
30
1E
RS (record separator)
记录分隔符
0001 1111
37
31
1F
US (unit separator)
单元分隔符
0010 0000
40
32
20
(space)
空格
0010 0001
41
33
21
!
叹号
0010 0010
42
34
22
双引号
0010 0011
43
35
23
#
井号
0010 0100
44
36
24
$
美元符
0010 0101
45
37
25
%
百分号
0010 0110
46
38
26
&
和号
0010 0111
47
39
27
闭单引号
0010 1000
50
40
28
(
开括号
0010 1001
51
41
29
)
闭括号
0010 1010
52
42
2A
*
星号
0010 1011
53
43
2B
+
加号
0010 1100
54
44
2C
,
逗号
0010 1101
55
45
2D
减号/破折号
0010 1110
56
46
2E
.
句号
00101111
57
47
2F
/
斜杠
00110000
60
48
30
0
数字0
00110001
61
49
31
1
数字1
00110010
62
50
32
2
数字2
00110011
63
51
33
3
数字3
00110100
64
52
34
4
数字4
00110101
65
53
35
5
数字5
00110110
66
54
36
6
数字6
00110111
67
55
37
7
数字7
00111000
70
56
38
8
数字8
00111001
71
57
39
9
数字9
00111010
72
58
3A
:
冒号
00111011
73
59
3B
;
分号
00111100
74
60
3C
<
小于
00111101
75
61
3D
=
等号
00111110
76
62
3E
>
大于
00111111
77
63
3F
?
问号
01000000
100
64
40
@
电子邮件符号
01000001
101
65
41
A
大写字母A
01000010
102
66
42
B
大写字母B
01000011
103
67
43
C
大写字母C
01000100
104
68
44
D
大写字母D
01000101
105
69
45
E
大写字母E
01000110
106
70
46
F
大写字母F
01000111
107
71
47
G
大写字母G
01001000
110
72
48
H
大写字母H
01001001
111
73
49
I
大写字母I
01001010
112
74
4A
J
大写字母J
01001011
113
75
4B
K
大写字母K
01001100
114
76
4C
L
大写字母L
01001101
115
77
4D
M
大写字母M
01001110
116
78
4E
N
大写字母N
01001111
117
79
4F
O
大写字母O
01010000
120
80
50
P
大写字母P
01010001
121
81
51
Q
大写字母Q
01010010
122
82
52
R
大写字母R
01010011
123
83
53
S
大写字母S
01010100
124
84
54
T
大写字母T
01010101
125
85
55
U
大写字母U
01010110
126
86
56
V
大写字母V
01010111
127
87
57
W
大写字母W
01011000
130
88
58
X
大写字母X
01011001
131
89
59
Y
大写字母Y
01011010
132
90
5A
Z
大写字母Z
01011011
133
91
5B
[
开方括号
01011100
134
92
5C
\
反斜杠
01011101
135
93
5D
]
闭方括号
01011110
136
94
5E
^
脱字符
01011111
137
95
5F
_
下划线
01100000
140
96
60
`
开单引号
01100001
141
97
61
a
小写字母a
01100010
142
98
62
b
小写字母b
01100011
143
99
63
c
小写字母c
01100100
144
100
64
d
小写字母d
01100101
145
101
65
e
小写字母e
01100110
146
102
66
f
小写字母f
01100111
147
103
67
g
小写字母g
01101000
150
104
68
h
小写字母h
01101001
151
105
69
i
小写字母i
01101010
152
106
6A
j
小写字母j
01101011
153
107
6B
k
小写字母k
01101100
154
108
6C
l
小写字母l
01101101
155
109
6D
m
小写字母m
01101110
156
110
6E
n
小写字母n
01101111
157
111
6F
o
小写字母o
01110000
160
112
70
p
小写字母p
01110001
161
113
71
q
小写字母q
01110010
162
114
72
r
小写字母r
01110011
163
115
73
s
小写字母s
01110100
164
116
74
t
小写字母t
01110101
165
117
75
u
小写字母u
01110110
166
118
76
v
小写字母v
01110111
167
119
77
w
小写字母w
01111000
170
120
78
x
小写字母x
01111001
171
121
79
y
小写字母y
01111010
172
122
7A
z
小写字母z
01111011
173
123
7B
{
开花括号
01111100
174
124
7C
|
垂线
01111101
175
125
7D
}
闭花括号
01111110
176
126
7E
~
波浪号
01111111
177
127
7F
DEL (delete)
删除

 

2、ASCII字符集共计有128个字符(见上表),码点编号(即字符编号)从0到127(二进制为从0000 0000到0111 1111,十六进制为从0x00到0x7F),二进制最高位都是0。其中:

1)0~31:控制字符或通讯专用字符(不可显示不可打印字符),如0x07(BEL响铃)会让计算机发出哔的一声、0x00(NUL空,注意不是空格)通常用于指示字符串的结束、0x0D(CR回车)和0x0A(LF换行)用于指示打印机的打印针头退到行首(即回车)并移到下一行(即换行)等。

2)32~126:可显示可打印字符(其中32为可显示但不可打印的空格字符),48~57为0-9的阿拉伯数字,65~90为26个大写英文字母,97~122为26个小写英文字母,其余的是一些标点符号、运算符号等。

3)127:控制字符DEL。

3、这时候的字符编解码非常简单,比如若要将字符序列编码为二进制流写入存储设备,只需要将该字符序列里的各个字符在ASCII字符集中的字符编号(即码点编号),直接以一个二进制字节写入存储设备即可,字符编号就是字符编码,中间不需要经过特别的编码算法进行字符编号到字符编码的转换计算,更不存在所谓码元序列到字节序列的转换。

字符编码-教程(1)-概述与基本知识

编码的本质是:将人类世界的信息,以某种规则,变成二进制信息。解码的本质是:将二进制信息还原成人类世界的信息。

所谓的乱码:就是 解码和 编码的 规则 不配套罢了。

本系列教程主要是为了 仔细探讨计算机中的编码过程,方便程序开发时迅速解决乱码问题。

Ⅰ教程提纲


1、基本概念讲解,了解编码模型。

2、EBCDIC码与ASCII码的由来

3、EASCII及ISO 8859字符编码方案

4、字节序、本地序、网络序和多字节码元

5、简体汉字编码与半角和圆角

6、简体汉字的区位码、国标码、内码、外码及字型码

7、国际化-Unicode与ANSI

8、字符串长度与字符编码

Ⅱ 编码入门常识


一、位

1、即比特(Bit),亦称二进制位、比特位、位元、位,指二进制数中的一位,是计算机中信息表示的最小单位。

Bit是Binary digit(二进制数位)的缩写,由数学家John Wilder Tukey提出,习惯上以小写字母b表示,如8比特可表示为8b。

2、每个比特有0和1两个可能的值,除了代表数值本身之外,还可代表:

  • 数值的正、负;
  • 两种状态,如电灯的开、关,某根导线上电压的有、无,等等;
  • 一个抽象逻辑上的是、否。

二、字节

1、在计算机中,通常都会使用一连串的位(比特),称之为位串(bit string比特串)。很显然,计算机系统都不会让你使用任意长度的位串,而是使用某个特定长度的位串。

一些常见的位串长度形式具有约定好的名称,如,半字节(nibble,貌似用的不多)代表四个位的组合,字节(byte)代表8个位的组合;还有字(word)、双字(Double word,简写为Dword)、四字(Quad word,简写为Qword)、十字节(Ten byte,简写为Tbyte)。

2、字节(byte),又称为位元组,音译为“拜特”(但很少使用这个译名),是计算机中计量存储容量和传输容量的一种基本计量单位,是由连续的、固定数量的位(即比特)所组成的位串(即比特串),一般由8个位组成,即1 byte = 8 bit。习惯上用大写的B表示,如3字节可表示为3B。

现代个人计算机(PC)的存储器编址,一般是以字节为单位的,称之为按字节编址,因此字节一般也是存储器的最小存取单元以及处理器的最小寻址单位(也有按位寻址、按字寻址等等,但在个人计算机上应用不普遍,这里不讨论)。

3、字节作为存储器的最小存取单元以及处理器的最小寻址单位这一重要特点,跟字符编码的关系极为密切(比如,码元的单字节与多字节、字节序的大端序与小端序等,都与以字节为基础的基本数据类型密切相关,详见后文介绍)。

4、习惯上,按照下面的图来排列一个字节上的各个位的顺序,即按照从右到左的顺序,依次为最低位(第0位)到最高位(第7位):

5、注意,字节不一定非得是8位,以前也有过4位、6位或7位作为一个字节的标准,比如IBM 701(36位字长,18位为一字节)、IBM 702(7位字长,7位为一字节)、CDC 6600(60位字长,12位为一字节byte)等,只是现代计算机的事实标准就是用8位来代表一个字节(最终形成这一事实标准除了历史原因和商业原因之外,最重要的原因应该是由于二进制的特性:2的次方计算更方便快捷)。

正是因为这个原因,在很多较为严谨的技术规格文献中,为了避免产生歧义,更倾向于使用8位组(Octet)而不是字节(Byte)这个术语来强调8比特位串。

不过,由于大众基本上都将字节理解为8比特位的8位组,因此一般文章中如果未作特别说明,基本上都将8位组直接称之为字节。

三、字与字长

1、虽然字节是大多数现代计算机的最小存储单元和传输单元,但并不代表它是计算机可以最高效地处理的数据单位。

一般来说,计算机可以最高效地处理的数据大小,应该与其字的字长相同,这就涉及到了字及字长的概念。

字(Word)在计算机中,一串比特位(位串、比特串)是作为一个整体来处理或运算的,这串比特位称为一个计算机字,简称字。字通常分为若干个字节(每个字节一般是8位)。

字长(Word Length)即字的长度,是指计算机的每个字所包含的位数。字长决定了CPU一次操作所处理的实际比特位数量的多少。字长由CPU对外数据通路的数据总线宽度决定。

2、计算机处理数据的速率,显然和它一次能加工的位数以及进行运算的快慢有关。如果一台计算机的字长是另一台计算机的两倍,若两台计算机的速度相同,在相同的时间内,前者能做的工作一般是后者的两倍。因此,字长与计算机的功能和用途有很大的关系,是计算机的一个重要技术指标。

在目前来讲,桌面平台的处理器字长正处于从32位向64位过渡的时期,嵌入式设备基本稳定在32位,而在某些专业领域(如高端显卡),处理器字长早已经达到了64位乃至更多的128位。

四、字符集

1、字符集(Character Set、Charset),字面上的理解就是字符的集合,是一个自然语言文字系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括文字、数字、字母、音节、标点符号、图形符号等。

例如ASCII字符集,定义了128个字符;GB2312定义了7445个字符。而计算机系统中提到的字符集准确地来说,指的是已编号的字符的有序集合(但不一定是连续的)

2、常见字符集有ASCII字符集、ISO 8859系列字符集、GB系列字符集(GB2312、GBK、GB18030)、BIG5字符集、Unicode字符集等。

五、编码

 编码(Encode)是信息从一种形式或格式转换为另一种形式或格式的过程,比如用预先规定的方法将字符(文字、数字、符号等)、图像、声音或其它对象转换成规定的电脉冲信号或二进制数字。

六、解码

编码(Decode)为编码的逆过程。

七、字符编码

1、字符编码(Character Encoding)是把字符集中的字符按一定格式(形式、方式)编码为某指定集合中某一对象(比如由0和1两个数字所组成的位串模式、由0~9十个数字所组成的自然数序列、电脉冲等)的过程,亦即在字符集与指定集合两者之间建立一个对应关系(映射关系)的过程。这是信息处理的一项基础技术。

而在计算机科学中,通常以字符集来表达信息,以计算机为基础的信息处理系统则利用电子元件(硬件)的不同状态的组合来表示、存储和处理信息。

2、电子元件不同状态(一般是开和关或称为开和闭两种状态)的组合能代表数字系统中的数字(比如开和关代表二进制中的0和1),因此字符编码的过程也就可以理解为将字符转换映射为计算机可以接受的二进制数字的过程,其目的是为了便于字符在计算机中表示、存储、处理和传输(包括在网络中传输)。

常见的例子包括将拉丁字母表编码成摩斯电码和ASCII码。其中,ASCII将字母、数字和其它符号进行编号,并且在计算机中直接用7比特的二进制数字来表示这个编号。通常会额外地在最高位(即首位)再增加一个扩充的比特位“0”,以便于计算机系统刚好以1个字节(8比特位)的方式来进行处理、存储和传输。

 

Ⅲ 字符编码体系模型

1、字符编码模型(Character Encoding Model)是反映字符编码系统的结构特点和各构成部分相互关系的模型框架。

2、由于历史的原因,早期一般认为字符集字符编码是同义词,并不需要进行严格区分。因此在像ASCII这样的简单字符集为代表的传统字符编码模型中,这两个概念的含义几乎是等同的。

因为在传统字符编码模型中,基本上都是将字符集里的字符进行编号(字符编号转化为二进制数后一般不超过一个字节),然后该字符编号就是字符的编码

但是,由统一码(Unicode)和通用字符集(UCS)为代表的现代字符编码模型则没有直接采用ASCII这样的简单字符集的编码思路,而是采用了一个全新的编码思路。

3、这个全新的编码思路将字符集与字符编码的概念更为细致地分解为了以下几个方面:

1)有哪些字符;

2)这些字符的编号是什么;

3)这些编号如何编码成一系列逻辑层面有限大小的数字,即码元序列;

4)这些逻辑层面的码元序列如何转换为(映射为)物理层面的字节流(字节序列);

5)在某些特殊的传输环境中(比如Email),再进一步将字节序列进行适应性编码处理。

这几个方面作为一个整体,于是构成了现代字符编码模型

4、现代字符编码模型之所以要分解为这么几个方面,其核心思想是创建一个能够用不同方式来编码的通用字符集。注意这里的关键词:“不同方式”与“通用”。

这意味着,同一个字符集,可以通用于不同的编码方式;也就是说,可以采用不同的编码方式来对同一个字符集进行编码。字符集与编码方式之间的关系可以是一对多的关系。

更进一步而言,在传统字符编码模型中,字符编码方式与字符集是紧密结合在一起的;而在现代字符编码模型中,字符编码方式与字符集脱钩了。用软件工程的专业术语来说,就是将之前紧密耦合在一起的字符编码方式与字符集解耦了。

因此,为了正确地表示这个现代字符编码模型,需要采用更多比“字符集”和“字符编码”更为精确的概念术语来描述。

5、在Unicode Technical Report (UTR统一码技术报告) #17《UNICODE CHARACTER ENCODING MODEL》中,现代字符编码模型分为了5个层次,并引入了更多的概念术语来描述(下面所涉及到的一些全新的概念术语,这里只做简介,暂时不作解释,但后文会陆续进行详细解释):

1层 抽象字符表ACR(Abstract Character Repertoire抽象字符清单)明确字符的范围(即确定支持哪些字符)

2层 编号字符集CCS(Coded Character Set)用数字编号表示字符(即用数字给抽象字符表ACR中的字符进行编号)

3层 字符编码方式CEF(Character Encoding Form字符编码形式、字符编码格式、字符编码规则)将字符编号编码为逻辑上的码元序列(即逻辑字符编码)

4层 字符编码模式CES(Character Encoding Scheme)将逻辑上的码元序列映射为物理上的字节序列(即物理字符编码)

5层 传输编码语法TES(Transfer Encoding Syntax)将字节序列作进一步的适应性编码处理

大白话总结:现代字符编码五层模型

第一层:抽象字符表(ACR)

就是建立一个集合,决定哪些字符需要放入集合中。

第二层:编号字符集(CCS)

将字符集合进行编号,比如 Unicode编号。

第三层:字符编码方式(CEF)

将字符集合的编号,以某种编码方式进行编码,形成编号与编码的一对一映射。
比如UTF-8编码,UTF-16编码。

第四层:字符编码模式(CES)

经过 CEF 编码好的二进制字符流,需要依据情况判断是否需要  字节序 编码。
字节序问题:部分多字节的编码方案中,在读取和写入数据文档时,操作系统需要判断首字节先读取或写入还是尾字节先读取还是写入【这就是Little endian和Big endian问题】。

当然网络传输中,TCP/IP为任意整数数据项定义了统一的网络字节顺序(network byte order):大端字节顺序!例如IP地址:它放在包头中跨越网络被携带。在IP地址结构中存放的地址总是以(大端法)网络字节顺序存放的,即使主机字节顺序(host byte order)是小端法。

单字节编码不存在字节序问题,有些多字节编码考虑了相邻字节的规则,也叫单字节码元,也没有问题。只有多字节编码中,没有考虑相邻编码规则的,属于多字节码元的,才会有字节序问题:如UTF-16 ,UTF-32。

第五层:传输编码语法(TES)

在某些特殊的传输环境中(比如Email),再进一步将字节序列进行适应性编码处理。


本系列文章,参考如下:

字节序问题:

https://www.cnblogs.com/benbenalin/p/6882293.html

http://imweb.io/topic/57fe263b2a25000c315a3d8a

https://zhidao.baidu.com/question/554750118.html

https://baike.baidu.com/item/%E5%AD%97%E8%8A%82%E5%BA%8F/1457160

https://www.cnblogs.com/dragon2012/p/5020259.html

编码转换:

https://blog.csdn.net/garybrother/article/details/3988741

http://www.mytju.com/classcode/tools/encode_big5.asp

https://www.qqxiuzi.cn/bianma/Unicode-UTF.php

https://www.qqxiuzi.cn/zh/hanzi-big5-bianma.php

 

字符编码-原码反码与补码

java中byte转换int时为何与0xff进行与运算

在剖析该问题前请看如下代码

public static String bytes2HexString(byte[] b) {   
  String ret = "";   
  for (int i = 0; i < b.length; i++) {   
   String hex = Integer.toHexString(b[ i ] & 0xFF);   
   if (hex.length() == 1) {   
    hex = '0' + hex;   
   }   
   ret += hex.toUpperCase();   
  }   
  return ret;   
}

上面是将byte[]转化十六进制的字符串,注意这里b[ i ] & 0xFF将一个byte和 0xFF进行了与运算,然后使用Integer.toHexString取得了十六进制字符串,可以看出
b[ i ] & 0xFF运算后得出的仍然是个int,那么为何要和 0xFF进行与运算呢?直接 Integer.toHexString(b[ i ]);,将byte强转为int不行吗?答案是不行的.

其原因在于:
1.byte的大小为8bits而int的大小为32bits
2.java的二进制采用的是补码形式

在这里先温习下计算机基础理论

byte是一个字节保存的,有8个位,即8个0、1。
8位的第一个位是符号位,
也就是说0000 0001代表的是数字1
1000 0000代表的就是-1
所以正数最大位0111 1111,也就是数字127
负数最大为1111 1111,也就是数字-128

上面说的是二进制原码,但是在java中采用的是补码的形式,下面介绍下什么是补码

1、反码:
一个数如果是正,则它的反码与原码相同;
一个数如果是负,则符号位为1,其余各位是对原码取反;

2、补码:利用溢出,我们可以将减法变成加法
对于十进制数,从9得到5可用减法:
9-4=5    因为4+6=10,我们可以将6作为4的补数
改写为加法:
9+6=15(去掉高位1,也就是减10)得到5.

对于十六进制数,从c到5可用减法:
c-7=5    因为7+9=16 将9作为7的补数
改写为加法:
c+9=15(去掉高位1,也就是减16)得到5.

在计算机中,如果我们用1个字节表示一个数,一个字节有8位,超过8位就进1,在内存中情况为(100000000),进位1被丢弃。

⑴一个数为正,则它的原码、反码、补码相同
⑵一个数为负,刚符号位为1,其余各位是对原码取反,然后整个数加1

– 1的原码为                10000001
– 1的反码为                11111110
+ 1
– 1的补码为                11111111

0的原码为                 00000000
0的反码为                 11111111(正零和负零的反码相同)
+1
0的补码为               100000000(舍掉打头的1,正零和负零的补码相同)

Integer.toHexString的参数是int,如果不进行&0xff,那么当一个byte会转换成int时,由于int是32位,而byte只有8位这时会进行补位,
例如补码11111111的十进制数为-1转换为int时变为11111111111111111111111111111111好多1啊,呵呵!即0xffffffff但是这个数是不对的,这种补位就会造成误差。
和0xff相与后,高24比特就会被清0了,结果就对了。

—-
Java中的一个byte,其范围是-128~127的,而Integer.toHexString的参数本来是int,如果不进行&0xff,那么当一个byte会转换成int时,对于负数,会做位扩展,举例来说,一个byte的-1(即0xff),会被转换成int的-1(即0xffffffff),那么转化出的结果就不是我们想要的了。

而0xff默认是整形,所以,一个byte跟0xff相与会先将那个byte转化成整形运算,这样,结果中的高的24个比特就总会被清0,于是结果总是我们想要的。

来自:https://www.cnblogs.com/Free-Thinker/p/6529584.html

字符编码-http与web编码总结

大白话之http编码总结:

1、从本质上看url编码过程:【分成path路径部分和querystring查询串部分】

scheme://host[:port#]/path/…/[url-params] (路径) [?query-string][#anchor](查询字符串,#anchor 这个是文章锚点,用于定位文章的小标题)

浏览器的问题主要是路径(pathinfo)查询字符串(querystring)的编码问题。

2、 RFC 3986(2005年出版),建议所有新的URI必须对未保留字符不加以百分号编码;其它字符建议先转换为UTF-8字节序列, 然后对其字节值使用百分号编码。此前的URI不受此标准的影响。【按道理来说,编码问题应该解决了】

但是因为历史原因,部分浏览器的url ,还没有完全采用utf-8编码。说白了大家统一认为path路径是用utf-8编码的没有问题,但是querystring 查询串该怎么编码,意见不统一。

总结来说:Path路径中需要编码的内容都是用utf-8编码的,浏览器编码问题就是querystring查询串的编码问题,因为意见不统一。

3、补充:查询串 querystring 也包括:
表单的enctype属性为application/x-www-form-urlencoded 的post请求,因为请求体中的数据也是经过urlencoded。【虽然这是旧的url编码,需要注意空格会编码成 +】这时候请求体中的数据也相当于querystring ,只是请求体中不包含?符号。

4、将path路径的二进制流    和    querystring请求串的二进制流,拼接在一起就是完整的url二进制请求流。【当然放在请求体中的请求串不算url的一部分】

5、请求头中出现Content-Type字段 ,是为了通知服务器,我有请求体。
比如: Content-Type: application/x-www-form-urlencoded
响应头中出现Content-Type字段 ,是为了通知客户端,我有响应体。
比如: Content-Type: application/json;charset=UTF-8

6、所有http流都是ASCII编码的二进制流,除了表单用post提交且编码用 multipart/form-data 的情况,这时候请求头还是ASCII流,但是请求体中上传文件是二进制原文件流,请求体中的表单元素都是按表单环境中的编码方式   编码成二进制流,至于请求体中的其他的模板内容如——WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition:form-data;name=”name-file”;filename= 等等 还是用ASCII编码的二进制流


一、path路径编码详解

根据rfc3986,无论什么环境,什么浏览器,在path路径编码中,将需要的编码的字符,按utf-8编码。编码后以16进制展示,接着将16进制的展示符号,两两一组(相当于一个字节)为一个单元,在每个单元前添加百分号(%),最后将整个path进行 ASCII编码。

大白话:比如网站路径:www.baidu.com/春节
将春节:utf-8编码,E6 98 A5 E8 8A 82,然后在每个字节前添加百分号(%),即:%E6%98%A5%E8%8A%82【这算进行了一次编码,后面又会对每个字符再进行ASCII编码,变成二进制流】 

第一步网站地址 部分utf-8编码:
www.baidu.com/%E6%98%A5%E8%8A%82

第二步网站地址 全部ASCII编码后 真正的二进制流: 
77 77 77 2E 62 61 69 64 75 2E 63 6F 6D 2F 25 45 
36 25 39 38 25 41 35 25 45 38 25 38 41 25 38 32

二、querystring查询串编码详解

1、在浏览器地址栏输入的查询串

比如输入:  http://www.test.com/春节?name=春节&psw=123
所有浏览器的path路径都是用utf-8编码的,此时查询串的编码根据浏览器不同可以分为两类。

第一类:  将查询串两步编码成二进制流 ,比如 firefox,chrome,Safari,edge。
面对     ?name=春节&psw=123  。
做法是:浏览器根据自身默认的编码方式,对查询串进行编码并添加百分号。然后再继续进行第二步ASCII编码。

##### 都遵循标准url编码规范,都将查询串中需要编码的部分 采用浏览器默认的 utf-8 编码 并添加百分号
##### 编码前  ?name=春节&psw=123 
##### 编码后  ?name=%E6%98%A5%E8%8A%82&psw=123
##### ASCII码继续 编码成二进制流 
3F 6E 61 6D 65 3D 25 45 36 25 39 38 25 41 35 25 
45 38 25 38 41 25 38 32 26 70 73 77 3D 31 32 33

第二类:将查询串一步编码成二进制流,比如IE浏览器。
面对     ?name=春节&psw=123  
做法是:直接把这个  ?name=春节&psw=123  编码成二进制流,一步到位。编码方式与浏览器环境有关。中文IE 默认用 gb2312 编码方式将查询串编码。

### 中文IE 默认用 gb2312 编码查询串
###【主要是用gb2312编码 春节 二字,至于剩余的符号,因为gb2312是ASCII的一个拓展,所有用gb2312编码和ASCII编码都是一样的】
### 查询串 ?name=春节&psw=123 编码后的二进制流
3F 6E 61 6D 65 3D B4 BA BD DA 26 70 73 77 3D 31 32 33

再举一个例子:
chrome:https://wuu.wikipedia.org/wiki/%E6%98%A5%E8%8A%82?%E6%98%A5%E8%8A%82

#Request Headers

:authority: wuu.wikipedia.org
:method: GET
:path: /wiki/%E6%98%A5%E8%8A%82?%E6%98%A5%E8%8A%82
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7
cookie: WMF-Last-Access-Global=13-Aug-2018; GeoIP=JP:13:Tokyo:35.69:139.75:v4; WMF-Last-Access=13-Aug-2018
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36

我们知道,”春”和”节”的utf-8编码分别是”E6 98 A5″和”E8 8A 82″,因此,”%E6%98%A5%E8%8A%82″就是按照顺序,在每个字节前加上%而得到的。

所以 chrome浏览器,url中的 路径path 和 查询串 query string  都是UTF-8编码。其实Firefox, Safari ,Edge 都是 同样的。

##### utf-8 编码后 %E6%98%A5%E8%8A%82
##### 又经过第二次 ASCII 编码后
25 45 36 25 39 38 25 41 35 25 45 38 25 38 41 25 38 32

至于,IE 浏览器 无论 IE 8 还是 IE 11 ,路径 path是UTF-8 编码,【查询串queryString 则按照 浏览器默认 编码来进行。比如 中文的IE 用的 就是 GB2312编码。 而且是一步到位,直接编码成二进制流,没有百分号编码的过程。】


IE浏览器:www.baidu.com/春节?春节

#Request Headers

GET /%E6%98%A5%E8%8A%82?���� HTTP/1.1
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/x-ms-xbap, application/x-ms-application, */*
Accept-Language: zh-cn
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: www.baidu.com
Cookie: BAIDUID=DFF7C9EB333036D329E7EB55757711B7:FG=1; BIDUPSID=D05CF78322A0985BC8FD928169BC4BF1; PSTM=1533825584; H_PS_PSSID=; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BD_UPN=1123314151; H_PS_645EC=9c51wK0QsNr9o%2BoxCNqWR7faZJlxa6F7GdNch0OwzQznYCdO%2B5jhAlHofJh5PLalplC3%2BoaOqoNeFg

我们知道,”春”和”节”的GB2312编码(我的操作系统”Windows XP”中文版的默认编码)分别是”B4 BA”和”BD DA”。因此,IE实际上就是将查询字符串,以GB2312编码的格式发送出去。至于,路径pathinfo 还是 用utf-8  的形式,比如春节两字的编码– %E6%98%A5%E8%8A%82【但是IE的查询串没有用%编码,直接GB2312编码成二进制流,与其他浏览器先百分号编码,再编码成二进制流不同】

=======================================================

参考其他文章,说以前Firefox的 pathInfo和queryString都是URL encode按照GB2312编码的。对于中文IE,如果在高级选项中选中总以UTF-8发送(默认方式),则PathInfo是URL Encode是按照UTF-8编码,QueryString是按照GBK编码。 很显然,不同的浏览器以及同一浏览器的不同设置,会影响最终URL中PathInfo的编码。

按照RFC3986 的规定,需要编码的字符,都应该用utf-8编码成字节流,然后百分号编码。所以这是浏览器的历史遗留问题。

根据现在的浏览器情况:浏览器地址栏直接输入:

Chrome,Safari,Edge,Firefox,pathInfo和query string 都是utf-8编码
IE pathInfo 用utf-8 编码,query string 用 gb2312 编码


2、点击超链接生成的查询串

比如网页中有如下超链接:
<a href="gbk-response/春节?name=春节&psw=123">go</a>

所有浏览器的path路径都是用utf-8编码的,此时查询串的编码根据浏览器不同可以分为两类。

第一类:  将查询串两步编码成二进制流 ,比如 firefox,chrome,Safari,edge。
面对     ?name=春节&psw=123  。
做法是:
1、浏览器首先根据网页中设置的编码方式对查询串进行编码并添加百分号。
  <meta http-equiv="content-type" content="text/html;charset=utf-8">
2、如果网页中找不到指定的编码方法,浏览器就会采用
自身默认的编码方式,对查询串进行编码并添加百分号(中文环境下firefox,chrome,Safari,edge,默认都是utf-8 编码),然后再继续对查询串进行第二步ASCII编码。

第二类:将查询串一步编码成二进制流,比如IE浏览器。
面对     ?name=春节&psw=123  
做法是:
1、浏览器首先根据网页中设置的编码方式对查询串一步到位编码成二进制流。
  <meta http-equiv="content-type" content="text/html;charset=utf-8">
2、如果网页中找不到指定的编码方法,浏览器就会采用
自身默认的编码方式(中文环境下 IE 默认采用gb2312编码)一步到位编码成二进制流。


3、表单提交生成的查询串

如果请求串,由表单提交生成且表单的enctype属性为 application/x-www-form-urlencoded【默认属性】,无论是get还是post方法,都会进行百分号编码【当然这种旧url编码需要注意 空格会编码成 +】

form元素有个enctype属性,可以指定数据编码方式,有如下三种:

1. application/x-www-form-urlencoded: 表单数据编码为键值对,&分隔
2. multipart/form-data: 表单数据编码为一条消息,每个控件对应消息的一部分
3. text/plain: 表单数据以纯文本形式进行编码

详细说明:

form的enctype的编码方式,常用有两种:
application/x-www-form-urlencoded和multipart/form-data
其中 application/x-www-form-urlencoded为默认编码方式。

在form的action为get时,浏览器用x-www-form-urlencoded的编码方式,将表单数据编码为
(name1=value1&name2=value2...),然后把这个字符串append到url后面,用?分隔,跳转
到这个新的url
当form的action为post时,浏览器将form数据封装到http body中,然后发送到server。
在没有type=file时候,用默认的 application/x-www-form-urlencoded 就行。
在有 type=file 时候,要用multipart/form-data编码方式。浏览器会把表单以控件为单位分割,
并且为每个部分加上Content-Dispositon(form-data或file)、Content-Type(默认text/plain)、
name(控件name)等信息,并加上分割符(boundary)。

假设浏览器所在环境,采用UTF-8编码将表单信息编码成querystring【编码选择方式后文会讲到】
1、如果浏览器用get方式请求   http://www.test.com/春节?name=春节&psw=123
则url显示结果为:http://www.test.com/%E6%98%A5%E8%8A%82?name=%B4%BA%BD%DA&pwd=123
2、如果浏览器是post请求方式:则url显示结果为http://www.test.com/%E6%98%A5%E8%8A%82
且请求体中有:name=%B4%BA%BD%DA&pwd=123

不管是放在url中还是请求体中,接下来仍需要将请求串ASCII编码。

### 已经过一次编码的请求串
?name=%E6%98%A5%E8%8A%82&psw=123
### ASCII码继续 编码成二进制流
3F 6E 61 6D 65 3D 25 45 36 25 39 38 25 41 35
25 45 38 25 38 41 25 38 32 26 70 73 77 3D 31
### 注意放在请求体中的请求串(严格来讲已不叫请求串了)
### 因为请求体中的请求串没有问号(?)所以请求体的二进制流要去掉第一个3F

4、表单提交生成的查询串的编码选择

(1)如果网页中 的head标签中 没有指定 网页编码类型

比如缺少类似下面这个的

  <meta http-equiv="content-type" content="text/html;charset=utf-8">

那么浏览器会使用默认的编码方式进行解码,这样会出现乱码的可能。比如mac下的chrome浏览器,我这边测试出来默认编码是用gb2312的。如果网页没直接指明编码方式,那么如果是gb2312编码的网页那会显示正确。如果是utf-8编码的网页,网页就会解析错误,产生乱码。因为浏览器默认是用GB2312解码网页的。

测试了一下Safari,发现浏览器默认编码即不是utf-8,也不是GB2312。
测试了一下Firefox,浏览器默认编码是GB2312。
测试了一下IE,浏览器默认编码是GB2312。

注:浏览器默认编码可能和系统默认编码有关。【如中文系统和英文系统】

(2)如果表单中,指定了accept-charset

网页里的form编码其实不完全取决于网页编码,form标记中有一个accept-charset属性,在非ie浏览器种,如果将其赋值(比如accept-charset=”UTF-8″),则表单会按照这个值表示的编码方式,将需要编码的字符编码成字节流,然后采用  application/x-www-form-urlencoded   非标准URL编码。

<form action="form_action.asp" accept-charset="utf-8">
  <p>First name: <input type="text" name="fname" /></p>
  <p>Last name: <input type="text" name="lname" /></p>
  <input type="submit" value="Submit" />
</form>

定义和用法

accept-charset 属性规定服务器处理表单数据所接受的字符集。此属性的默认值是 “unknown”,表示表单的字符集与包含表单的文档的字符集相同。

提示:请避免使用该属性。应该在服务器端验证文件上传。

浏览器支持

除了 Internet Explorer,accept-charset 属性得到几乎所有浏览器的支持。

注释:accept-charset 属性无法在 Internet Explorer 中正确地工作。如果 accept-charset 属性设置为 “ISO-8859-1″,IE 将发送以 “Windows-1252” 编码的数据。

我在chrome浏览器中测试过,gb2312编码的网页,表单用指定用utf-8编码。

结果我发现,请求头中 Content-Type: application/x-www-form-urlencoded   也不存在 charset=xxx 的字段。

POST http://192.168.2.233/http_encode/gbk-response/%E6%98%A5%E8%8A%82 HTTP/1.1
Host: 192.168.2.233
Connection: keep-alive
Content-Length: 32
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.2.233
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://192.168.1.133/~cool/http_encode/gbk-post.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8

name=%E6%98%A5+%E8%8A%82&pwd=123

 

在ie下,我的兼容解决办法是:

form1.onsubmit=function(){
document.charset=this.getAttribute('accept-charset');
}

三、表单知识补充

1、四种常见的 POST 提交数据方式

(1)application/x-www-form-urlencoded
(2)multipart/form-data
(3)application/json
(4)text/xml(xml-rpc)

2、表单提交方式GET与POST的区别

当用GET提交时,会将表单信息变成query string 查询字符串。根据 网页中 head 标签 里面设定的编码方案  将   query string 中 需要编码的字符  编码成字节流 。然后再用非标准的url编码。【大白话,GET提交表单时,表单信息变成了URL里面的query string,即在http协议的请求行中】

因为 query string  在表单 中 ,采用的 是  application/x-www-form-urlencoded   非标准URL编码。 这和标准的URL编码有区别。比如编码空格时标准url编码成”%20″,非标准url编码成”+”。

========================================================

当用POST提交表单时,会将表单信息变成FORM DATA,存放在http协议中的 请求体中。FORM DATA 和query string 一样,也是将 需要编码的部分,根据网页head中设定的编码方式,编码成字节流。然后采用  application/x-www-form-urlencoded   非标准URL编码。

同时,会在请求头中 加入:

Content-Type: application/x-www-form-urlencoded

注意:无论是GET还是POST方式,将表单信息编码成字节流时,空格是不需要编码的,所以后面采用  application/x-www-form-urlencoded   非标准URL编码时,空格 一定会被编码成 “+”符号。

3、表单中的enctype属性

表单常用属性:

action:url 地址,服务器接收表单数据的地址
method:提交服务器的http方法,一般为post和get
name:参数名属性

######################################
enctype: 表单数据提交时使用的编码类型,默认使用"aplication/x-www-form-urlencoded",
如果是使用POST请求,则请求头中的content-type指定值就是该值。因为包含了请求体。
如果是使用GET请求,通过URL的query string传递参数,没有请求体,所以不需要content-type
######################################

如果表单中有上传文件,编码类型需要使用"multipart/form-data",类型,才能完成传递文件数据。
<form action="/upload" enctype="multipart/form-data" method="post">
    Username: <input type="text" name="username">
    Password: <input type="password" name="password">
    File: <input type="file" name="file">
    <input type="submit">
</form>

4、表单用post提交,并且编码用 multipart/form-data 的情况

这种情况下,除了上传的文件直接采用二进制流没有再编码,其他的表单信息编码方式和x-www-form-urlencoded一样。举例如下:
表单信息:【name-desc 填写  春节  二字,上传的文件为   春节.txt】

<form action="testfileupload" method="post" enctype="multipart/form-data" accept-charset="gb2312">
File:<input type="file" name="name-file"/>
Desc:<input type="text" name="name-desc"/>
<input type="submit" name="name-submit" value="submit" />
</form>

待上传文件:春节.txt
文件内容:

good春节OK
hello

文件二进制代码:
【备注在Windows的记事本软件中如果用utf-8编码保存时,会自动在保存信息中添加EF BB BF 字节序,而mac系统下不会,这里实验采用mac系统】

676f 6f64 e698 a5e8 8a82 4f4b 0d0a 6865
6c6c 6f

提交请求时,fiddler2抓包结果:

POST http://192.168.1.131:8080/springmvc-2/testfileupload HTTP/1.1
Host: 192.168.1.131:8080
Connection: keep-alive
Content-Length: 410
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.1.131:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryo2ilS97ZdtneTvV5
Referer: http://192.168.1.131:8080/springmvc-2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=A15ECBF73D297496D7785129A231A321

------WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition: form-data; name="name-file"; filename="    .txt"
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition: form-data; name="name-desc"

    
------WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundaryo2ilS97ZdtneTvV5--

http请求特别解析:

##### 下面的  filename="    .txt"   其实是   filename="春节.txt"
##### 之所有 出现空白是表单信息中 春节 2个字 二进制信息是 B4 BA BD DA ,采用了GBK编码
##### 而 fiddler2 显示信息 用的是 utf-8 编码 所有显示不出来
Content-Disposition: form-data; name="name-file"; filename="    .txt"

##### 下面第三行默认是空白,而第四行 也是 空白 但是 有二进制信息 是 B4 BA BD DA
------WebKitFormBoundaryo2ilS97ZdtneTvV5
Content-Disposition: form-data; name="name-desc"

如果把表单 删除gb2312编码,也就是用 网页内定 的utf-8 编码:

<form action="testfileupload" method="post" enctype="multipart/form-data" >
File:<input type="file" name="name-file"/>
Desc:<input type="text" name="name-desc"/>
<input type="submit" name="name-submit" value="submit" />
</form>

抓包结果:

POST http://192.168.1.131:8080/springmvc-2/testfileupload HTTP/1.1
Host: 192.168.1.131:8080
Connection: keep-alive
Content-Length: 414
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.1.131:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary4v5NL53bCqC2d9fH
Referer: http://192.168.1.131:8080/springmvc-2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=A15ECBF73D297496D7785129A231A321

------WebKitFormBoundary4v5NL53bCqC2d9fH
Content-Disposition: form-data; name="name-file"; filename="春节.txt"
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundary4v5NL53bCqC2d9fH
Content-Disposition: form-data; name="name-desc"

春节
------WebKitFormBoundary4v5NL53bCqC2d9fH
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundary4v5NL53bCqC2d9fH--

从这里我们能够看到,所有 春节 两字 编码信息 都是 utf-8 编码,都能在fiddler2 抓包中,正确显示。

从结果比较来看, multipart/form-data 的请求体中,上传文件的表单单元,不需要再编码,直接传输文件二进制流,因为春节.txt是utf-8编码,而fiddler2默认是用utf-8解码,所有解码正确。至于其他的表单单元,根据表单环境提供的编码方式编码,然后传递。上面的例子,表单第一次编码是gb2312,而fiddler2是utf-8解码,导致抓包时,信息解码错误。表单第二次编码是utf-8,和fiddler2的解码一致,所以信息抓包解码正确。

======================================================

特别说明:如果改成了iso-8859-1的编码表单

<form action="testRequestBody" method="post" enctype="multipart/form-data" accept-charset="iso-8859-1">
File:<input type="file" name="name-file"/>
Desc:<input type="text" name="name-desc"/>
<input type="submit" name="name-submit" value="submit" />
</form>

上传文件名还是春节.txt ,表单输入的name-desc 也还是 春节

这个时候fiddler2抓包二进制流,然后用utf-8解码结果:

POST http://192.168.1.131:8080/springmvc-2/testRequestBody HTTP/1.1
Host: 192.168.1.131:8080
Connection: keep-alive
Content-Length: 420
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.1.131:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary3FCh7yh4z2wpPZo9
Referer: http://192.168.1.131:8080/springmvc-2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=07D6AF477CEE3B7CDB6A1847E2ECB3A4

------WebKitFormBoundary3FCh7yh4z2wpPZo9
Content-Disposition: form-data; name="name-file"; filename="??.txt"
Content-Type: text/plain

good春节OK
hello
------WebKitFormBoundary3FCh7yh4z2wpPZo9
Content-Disposition: form-data; name="name-desc"

&#26149;&#33410;
------WebKitFormBoundary3FCh7yh4z2wpPZo9
Content-Disposition: form-data; name="name-submit"

submit
------WebKitFormBoundary3FCh7yh4z2wpPZo9--

我们看到:请求体中
文件名为: filename=”??.txt”    查看二进制发现春节两字 编码成  3F 3F
而 表单中 name-desc 值为:  &#26149;&#33410;    【这个26149 33410 分别是春节的Unicode 十进制 值】【&#26149;&#33410; 这两个都可以用ASCII解码成二进制流比如 &#代表26 23】
从这里我们能够看出来:表单用 multipart/form-data 传递信息时,当遇到表单信息无法编码时,表单中的不同类型,应对结果可能不同。比如 表单中的文件名应对错误编码的结果与 表单中 name-desc 应对错误编码的结果不同。但是,如果表单能被正确编码,那么两者编码结果值相同(因为两者原码相同)。

后来,我们又将表单编码设置成立utf-16,但是抓包结果来看,表单信息还是被编码成了utf-8,所以感觉没有效果。

当我们将表单 用big5 编码时,上传文件名 改为:春節   ,表单填写name-desc时 也改为 春節 ,抓包结果看到这两处 春節 都被编码成了   AC 4B B8 60  二进制流。至于文件中的信息春节,因为文件用utf-8编码过的二进制流,直接放入请求体中,不需要二次编码,所以不受表单编码的影响。

我们知道的是表单信息提交时,会按照表单设置的编码进行发送。但是对于 上传文件,用的是原始二进制流,不用编码。现在有一个疑问,就是http请求中,默认格式字符信息,用的是ASCII编码  还是 用表单设置的编码,目前我看到的表单编码都是兼容ASCII码的(比如UTF-8,GBK,big5 兼容ASCII),所以无法实质区分。曾尝试用utf-16设置表单编码,但是抓包后发现,表单信息居然采用了utf-8 编码。所以区分不了。


四、url编码与javascript

url编码-JavaScript-函数

   escape 、 encodeURI 、encodeURIComponent

首先,无论网页的编码方式是什么,都可以正确解析,毕竟编码和解码是对应统一的。JavaScript引擎根据 已经编码好的网页二进制流 和 网页的编码方式 , 将网页二进制信息流正确转码成JavaScript引擎内部能处理的二进制流【内部编码要么是ucs-2 或者是 utf -16,待探究】

所以,JavaScript 中的  函数参数 也会 编码成 内部编码。【JavaScript内部编码可能是 ucs-2,utf-16,总之:两个字节内,编码的结果都是一样的,以后再探讨到底是哪种编码 】

javascript:escape("\u6625\u8282");  ==>  反斜杠 \ 代表Unicode转义字符。以Unicode字符序的方式接收
javascript:escape("春节");  ==>  春节 二字,代表默认的字符 接收
#这里的函数参数  春节  或者 Unicode排序值  都将内部都编码成了 UCS-2 或者 UTF-16  
#这里的函数返回值 就是 将内部编码后的数据 进行运算,并将运算结果转码成 Unicode排序值,然后输出

编码问题归纳:记录网页信息的网页编码 和 JavaScript引擎处理网页信息的内部编码。

下面列出了这三个函数的安全字符(即函数不会对这些字符进行编码)

escape(69个):*/@+-._0-9a-zA-Z
encodeURI(82个):!#$&'()*+,/:;=?@-._~0-9a-zA-Z
encodeURIComponent(71个):!'()*-._~0-9a-zA-Z
encodeURI('https://www.baidu.com/ a b c')
// "https://www.baidu.com/%20a%20b%20c"
encodeURIComponent('https://www.baidu.com/ a b c')
// "https%3A%2F%2Fwww.baidu.com%2F%20a%20b%20c"
 
//而 escape 会编码成下面这样,eocode 了冒号却没 encode 斜杠,十分怪异,故废弃之
escape('https://www.baidu.com/ a b c')
// "https%3A//www.baidu.com/%20a%20b%20c"

实际上,escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode排序值。比如”春节”的返回结果是%u6625%u8282,也就是说在Unicode字符集中,”春”是第6625个(十六进制)字符,”节”是第8282个(十六进制)字符。

春的Unicode排序值为:6625(十六进制)   ; 26149(十进制)   

春的utf-8编码值为:E6 98 A5(十六进制) ;URL编码采用的是utf-8 编码!!!

Javascript:escape("春节");
"%u6625%u8282"

Javascript:escape("Hello World");
"Hello%20World"

###  反斜杠 \ 代表Unicode转义字符 ,以Unicode字符序的方式接收。

javascript:escape("\u6625\u8282");
"%u6625%u8282"

javascript:unescape("%u6625%u8282");
"春节"

javascript:unescape("\u6625\u8282");
"春节"

 

escape()的具体规则是,除了ASCII字母、数字、标点符号”@ * _ + – . /”以外,对其他所有字符进行编码。在\u0000到\u00ff之间的符号被转成%xx的形式,其余符号被转成%uxxxx的形式。对应的解码函数是unescape()。

escape()不对”+”编码。但是我们知道,网页在提交表单的时候,如果有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。所以,使用的时候要小心。

escape函数已经被W3C废弃了。因为 escape函数 返回的是Unicode 排序值,不是 utf-8 编码值。不符合RFC3986 标准。但是在ECMA-262标准中仍然保留着escape的这种编码语法。


encodeURI()是Javascript中真正用来对URL编码的函数。

它着眼于对整个URL进行编码,因此除了常见的符号以外,对其他一些在网址中有特殊含义的符号”; / ? : @ & = + $ , #”,也不进行编码。编码后,它输出符号的utf-8形式,并且在每个字节前加上%。

encodeURI("http://www.cnblogs.com/season-huang/some other thing");
//"http://www.cnblogs.com/season-huang/some%20other%20thing";

编码后变为上述结果,可以看到空格被编码成了%20,而斜杠 / ,冒号 : 并没有被编码。需要注意的是,它不对单引号’编码。


encodeURIComponent()。与encodeURI()的区别是,它用于对URL的组成部分进行个别编码,而不用于对整个URL进行编码。

因此,”; / ? : @ & = + $ , #”,这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。

使用场景:当我们的 URL 长这样子,请求参数中带了另一个 URL :

var URL = "http://www.a.com?foo=http://www.b.com?t=123&s=456";
//直接对它进行 encodeURI 显然是不行的。
//因为 encodeURI 不会对冒号 : 及斜杠 / 进行转义,
//那么就会出现上述所说的服务器接受到之后解析会有歧义。

encodeURI(URL);
// "http://www.a.com?foo=http://www.b.com?t=123&b=456"
//这个时候,就该用到 encodeURIComponent() 。
//它的作用是对 URL 中的参数进行编码,记住是对参数,而不是对整个 URL 进行编码。

//正确的用法:encodeURIComponent() 着眼于对单个的参数进行编码:
var param = "http://www.b.com?t=123&s=456"; // 要被编码的参数
URL = "http://www.a.com?foo="+encodeURIComponent(param);
//"http://www.a.com?foo=http%3A%2F%2Fwww.b.com%3Ft%3D123%26s%3D456"

url编码-JavaScript-AJAX

GBK编码的gbk-get.html

<!DOCTYPE html>
<html>
<head>
    <title>gb2312网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=gb2312">
    <script type="text/javascript"	src="jquery.js"></script>
</head>
<body>
    <center>
      <a href="gbk-response/春节?name=春节&psw=123">go</a>
        <form action="gbk-http-get-response/春节" method="get">
            <p>用户名:<input type="text" id="name"  name="name"/></p>
            <p>密码:<input type="password" id="pwd" name="pwd"/></p>
            <input type="submit" value="登录">
            <input type="button" value="ajax-get" id="ajax-get-btn"/>
            <input type="button" value="ajax-post" id="ajax-post-btn"/>

        </form>
    </center>

<script>

    $("#ajax-get-btn").click(function(){
    			//发送ajax请求,弹出部门信息,显示在下拉列表中
          $.ajax({
            url:"gbk-ajax-get-response/春节",
            type:"get",
            data:"name="+$("#name").val(),
            success:function(result){

            }

          })
    		});


        $("#ajax-post-btn").click(function(){

              //获取DOM对象的 value 属性
              var name = $("#name")[0].value;

          		//发送ajax请求,弹出部门信息,显示在下拉列表中
              $.ajax({
                url:"gbk-ajax-post-response/春节",
                type:"post",
                //下面这个 ${"#name"}.value 写错了,难怪一直报错。 应该是 $("#name").value
                //data:"name="+${"#name"}.value,
                data:"name="+name,
                success:function(result){

                }

              })
        		});

            // $("#ajax-get-btn").click(function(){
            //
            //     var name = ${"#name"}.value;
            //
            //       //发送ajax请求,弹出部门信息,显示在下拉列表中
            //       $.ajax({
            //         url:"control",
            //         type:"get",
            //       //  下面的写法是错误的,正确的是 1、获取DOM属性  data:"name="+$("#name")[0].value,  或者 2、获取JQuery属性   data:"name="+$("#name").val(),
            //       //  data:"name="+${"#name"}.value,
            //         data:"name="+name,
            //         success:function(result){
            //
            //         }
            //
            //       })
            //     });

</script>

</body>
</html>

utf-8编码的文件大致相同:

测试结果:

fiddler2 用 utf-8 解码显示 http请求信息

IE浏览器,gbk网页,用get请求

GET http://192.168.1.131/~sky/http_encode/gbk-ajax-get-response/%B4%BA%BD%DA?name=     HTTP/1.1
x-requested-with: XMLHttpRequest
Accept-Language: zh-cn
Referer: http://192.168.1.131/~sky/http_encode/gbk-get.html
Accept: */*
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Host: 192.168.1.131
Connection: Keep-Alive

/%B4%BA%BD%DA?name=
上面用utf-8显示的文字,用16进制表示如下:
2F 25 42 34 25 42 41 25 42 44 25 44 41 3F 6E 61 6D 65 3D B4 BA BD DA

IE浏览器,gbk网页,用post请求
POST http://192.168.1.131/~sky/http_encode/gbk-ajax-post-response/%B4%BA%BD%DA HTTP/1.1
x-requested-with: XMLHttpRequest
Accept-Language: zh-cn
Referer: http://192.168.1.131/~sky/http_encode/gbk-get.html
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Host: 192.168.1.131
Content-Length: 11
Connection: Keep-Alive
Pragma: no-cache

name=春节

fiddler2 用utf-8 解码http信息:
请求体: name=春节  的二进制码为 6e 61 6d 65 3d e6 98 a5 e8 8a 82


IE浏览器 utf-8 网页 ,用get请求

GET http://192.168.1.131/~sky/http_encode/utf8-ajax-get-response/%B4%BA%BD%DA?name=     HTTP/1.1
x-requested-with: XMLHttpRequest
Accept-Language: zh-cn
Referer: http://192.168.1.131/~sky/http_encode/utf-8-get.html
Accept: */*
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Host: 192.168.1.131
Connection: Keep-Alive

/%B4%BA%BD%DA?name=
上面用utf-8显示的文字,用16进制表示如下:
2F 25 42 34 25 42 41 25 42 44 25 44 41 3F 6E 61 6D 65 3D B4 BA BD DA


IE浏览器 utf-8 网页 ,用post请求

POST http://192.168.1.131/~sky/http_encode/utf8-ajax-post-response/%B4%BA%BD%DA HTTP/1.1
x-requested-with: XMLHttpRequest
Accept-Language: zh-cn
Referer: http://192.168.1.131/~sky/http_encode/utf-8-get.html
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)
Host: 192.168.1.131
Content-Length: 11
Connection: Keep-Alive
Pragma: no-cache

name=春节

fiddler2 用utf-8 解码http信息:
请求体: name=春节  的二进制码为 6e 61 6d 65 3d e6 98 a5 e8 8a 82
=================================================================

chrome gbk网页 get请求

GET /~sky/http_encode/gbk-ajax-get-response/%E6%98%A5%E8%8A%82?name=%B4%BA%BD%DA HTTP/1.1
Host: localhost
Connection: keep-alive
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Referer: http://localhost/~sky/http_encode/gbk-get.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7

chrome gbk网页 post请求

POST /~sky/http_encode/gbk-ajax-post-response/%E6%98%A5%E8%8A%82 HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 11
Accept: */*
Origin: http://localhost
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost/~sky/http_encode/gbk-get.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7

name=春节

抓包发现:请求体的二进制代码为:
6e 61 6d 65 3d e6 98 a5 e8 8a 82



chrome utf-8 get请求

GET /~sky/http_encode/utf8-ajax-get-response/%E6%98%A5%E8%8A%82?name=%E6%98%A5%E8%8A%82 HTTP/1.1
Host: localhost
Connection: keep-alive
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Referer: http://localhost/~sky/http_encode/utf-8-get.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7

chrome utf-8 post请求

POST /~sky/http_encode/utf8-ajax-post-response/%E6%98%A5%E8%8A%82 HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 11
Accept: */*
Origin: http://localhost
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost/~sky/http_encode/utf-8-get.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7

name=春节
抓包发现:请求体的二进制代码为:
6e 61 6d 65 3d e6 98 a5 e8 8a 82

也就是说,在Ajax调用中,path路径调用不同,IE是%B4%BA%BD%DA,chrome是%E6%98%A5%E8%8A%82querystring 又和网页的编码有关系,IE 是 一步到位的编码。chrome是两步到位的编码,中间经历了百分号编码。


五、url编码与服务器响应

Apache服务器返回的   Response Headers :

HTTP/1.1 200 OK
Date: Wed, 15 Aug 2018 07:49:34 GMT
Server: Apache/2.4.28 (Unix) PHP/5.5.38
Last-Modified: Wed, 15 Aug 2018 03:41:28 GMT
ETag: "1c2-xxxxxxxxxxxx"  【一串字符串】
Accept-Ranges: bytes
Content-Length: 450
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html【我看到百度Apache服务器,这里是 text/html;charset=utf-8】

百度Apache 服务器 Request Headers

HTTP/1.1 200 OK
Bdpagetype: 3
Bdqid: XXXXXXXXXXXXXXXX
Cache-Control: private
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Cxy_all: news+XXXXXXXXXXXXXXXXXXXXXXXXX
Cxy_ex: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Date: Wed, 15 Aug 2018 07:43:23 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Server: Apache
Tracecode: XXXXXXXXXXXXXXXXXXXXXXXXX
Vary: Accept-Encoding
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1

Nginx 服务器 Request Headers:

HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Wed, 15 Aug 2018 07:56:25 GMT
Content-Type: text/html
【上面是欢迎页的响应,我个人网站和Nginx官网服务器的响应都是text/html;charset=utf-8】
Content-Length: 612
Last-Modified: Tue, 17 Apr 2018 15:48:00 GMT
Connection: keep-alive
ETag: "XXXXXXXXXX-XXX"  【一串字符串】
Accept-Ranges: bytes

nginx官网服务器:Response Headers

content-encoding: gzip
content-type: text/html; charset=UTF-8
date: Wed, 15 Aug 2018 08:22:48 GMT
link: <https://www.nginx.com/wp-json/>; rel="https://api.w.org/"
link: <https://www.nginx.com/>; rel=shortlink
link: <https://www.nginx.com/wp-json>; rel="https://github.com/WP-API/WP-API"
server: nginx
status: 200
vary: Accept-Encoding, User-Agent
x-cache-config: 0 0
x-cache-status: HIT
x-cache-status: HIT
x-pingback: https://www.nginx.com/xmlrpc.php
x-user-agent: standard

Tomcat 服务器解析 http 请求过程

1、开发人员必须清楚的servlet规范:

(1) HttpServletRequest.setCharacterEncoding()方法 仅仅只适用于设置post提交的request body的编码而不是设置get方法提交的queryString的编码。该方法告诉应用服务器应该采用什么编码解析post传过来的内容。很多文章并没有说明这一点。
(2) HttpServletRequest.getPathInfo()返回的结果是由Servlet服务器解码(decode)过的。
(3) HttpServletRequest.getRequestURI()返回的字符串没有被Servlet服务器decoded过。
(4) POST提交的数据是作为request body的一部分。
(5) 网页的Http头中ContentType(“text/html; charset=GBK”)的作用:
(a) 告诉浏览器网页中数据是什么编码;
(b) 表单提交时,通常浏览器会根据ContentType指定的charset对表单中的数据编码,然后发送给服务器的。
这里需要注意的是:这里所说的ContentType是指http头的ContentType,而不是在网页中meta中的ContentType。

2、Java中的uri 和 url 【和IETF设置的标准不同】

浏览器请求:http://localhost:8080/springmvc-1/春节.jsp?p=春节

写了一个 春节.jsp 文件

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page language="java" import="java.util.*" %>
    
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

测试完成 uri 与 url
<br>


<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

out.println("basePath:"+basePath);
out.println("<br/>");
out.println("getContextPath:"+request.getContextPath());
out.println("<br/>");
out.println("getServletPath:"+request.getServletPath());
out.println("<br/>");
out.println("getRequestURI:"+request.getRequestURI());
out.println("<br/>");
out.println("getRequestURL:"+request.getRequestURL());
out.println("<br/>");
out.println("getRealPath:"+request.getRealPath("/"));
out.println("<br/>");
out.println("getServletContext().getRealPath:"+getServletContext().getRealPath("/"));
out.println("<br/>");
out.println("getQueryString:"+request.getQueryString());

%>
	
</body>
</html>

结果显示:chrome,Safari,Firefox,Edge结果

测试完成 uri 与 url 
basePath:http://localhost:8080/springmvc-1/ 
getContextPath:/springmvc-1 
getServletPath:/春节.jsp 
getRequestURI:/springmvc-1/%E6%98%A5%E8%8A%82.jsp 
getRequestURL:http://localhost:8080/springmvc-1/%E6%98%A5%E8%8A%82.jsp 
getRealPath:/Users/hello/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc-1/ 
getServletContext().getRealPath:/Users/hello/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc-1/ 
getQueryString:p=%E6%98%A5%E8%8A%82

IE浏览器显示结果【浏览器地址栏 编码,query string  用了GB2312 导致乱码】

测试完成 uri 与 url 
basePath:http://192.168.1.133:8080/springmvc-1/ 
getContextPath:/springmvc-1 
getServletPath:/春节.jsp 
getRequestURI:/springmvc-1/%E6%98%A5%E8%8A%82.jsp 
getRequestURL:http://192.168.1.133:8080/springmvc-1/%E6%98%A5%E8%8A%82.jsp 
getRealPath:/Users/hello/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc-1/ 
getServletContext().getRealPath:/Users/hello/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc-1/ 
getQueryString:p=´º½Ú

Tomcat 服务器 response 报文:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Content-Length: 849
Date: Thu, 16 Aug 2018 09:50:30 GMT

java中 request.getServletPath() 和 request.getPathInfo() 的区别 {映射问题造成}

在 Web 中,我们通常需要获取 URL 相对于 Webapp 的路径,主要是下面的几个方法:

request.getServletPath()
request.getPathInfo()
request.getContextPath()
request.getRequestURI()

其中 request.getRequestURI() 的返回值包含了 request.getContextPath(),所以是相对于网站的根目录的。

下面我们分析 request.getServletPath() 和 request.getPathInfo()

1. 如果我们的 servlet-mapping 如下配置:

<servlet-mapping>

  <servlet-name>jetbrick-template</servlet-name>

  <url-pattern>*.jetx</url-pattern>

</servlet-mapping>

那么访问: /context/templates/index.jetx

request.getServletPath() == "/templates/index.jetx"

request.getPathInfo() == <null>

2. 如果我们的 servlet-mapping 如下配置:

<servlet-mapping>

  <servlet-name>jetbrick-template</servlet-name>

  <url-pattern>/*</url-pattern>

</servlet-mapping>

那么访问: /context/templates/index.jetx

request.getServletPath() == ""

request.getPathInfo() == "/templates/index.jetx"

3. 如果我们的 servlet-mapping 如下配置:

<servlet-mapping>

  <servlet-name>jetbrick-template</servlet-name>

  <url-pattern>/template/*</url-pattern>

</servlet-mapping>

那么访问: /context/templates/index.jetx

request.getServletPath() == "/templates"
request.getPathInfo() == "/index.jetx"
3、tomcat 解析http 流的过程

解析请求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,这个方法把传过来的 URL 的 byte[] 设置到 org.apache.coyote.Request 的相应的属性中。这里的 URL 仍然是 byte 格式,转成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:

protected void convertURI(MessageBytes uri, Request request) 
throws Exception { 
       ByteChunk bc = uri.getByteChunk(); 
       int length = bc.getLength(); 
       CharChunk cc = uri.getCharChunk(); 
       cc.allocate(length, -1); 
       String enc = connector.getURIEncoding(); 
       if (enc != null) { 
           B2CConverter 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.getBuffer().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【java标准的uri,上节已提到过】 部分进行解码的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/> 中定义的,如果没有定义,那么将以默认编码 ISO-8859-1 解析。所以如果有中文 URL 时最好把 URIEncoding 设置成 UTF-8 编码。

QueryString 的解码字符集要么是 Header 中 ContentType 中定义的 Charset 要么就是默认的 ISO-8859-1,要使用 ContentType 中定义的编码就要设置 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 设置为 true。这个配置项的名字有点让人产生混淆,它并不是对整个 URI 都采用 BodyEncoding 进行解码而仅仅是对 QueryString 使用 BodyEncoding 解码,这一点还要特别注意。

关于post表单提交的数据,存放在请求体中,至于如何解码,需要以后探讨。


六、请求头编码与网页内置编码优先级

设置网页编码的方法有:

1.利用php header()函数声明,这个header()函数的作用是把括号里面的信息发到http标头。

header(“Content-type: text/html; charset=xxx”); 

2.利用HTML <meta >进行声明,HTML <meta >这个标签的作用是声明客户端的浏览器用什么字符集编码显示该页面

<META http-equiv=”content-type” content=”text/html; charset=xxx”>

3.服务器在response 网页时,也可以 设置 服务器默认响应 编码。就相当于给每个文件都 加了一行header(“content-type:text/html; charset=xxx”)。如果网页里有header(“content-type:text/html; charset=xxx”),就把默认的字符集改为你设置的字符集,所以这个函数永远有用。

所以,网页编码的优先级是:

header(“content-type:text/html; charset=xxx”) 
AddDefaultCharset xxx 
<META http-equiv=”content-type” content=”text/html; charset=xxx”> 

如果你是web程序员,给你的每个页面都加个header(“content-type:text/html; charset=xxx”),保证它在任何服务器都能正确显示,可移植性强。 

1,2点 在服务器和浏览器上都可以使用,第3点只能在服务器上使用。

七、其它需要编码的地方

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

xml 文件可以通过设置头来制定编码格式

<?xml version="1.0" encoding="UTF-8"?>

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”。


PHP程序在查询数据库之前,首先执行 mysql_query(“SET NAMES xxxx”);其中 xxxx 是你网页的编码(charset=xxxx),如果网页中 charset=utf8,则 xxxx=utf8,如果网页中 charset=gb2312,则xxxx=gb2312,几乎所有WEB程序,都有一段连接数据库的公共代码,放在一个文件里,在这文件里,加入 mysql_query(“set names”)就可以了。

SET NAMES 显示客户端发送的 SQL 语句中使用什么字符集。因此,SET NAMES ‘utf-8’语句告诉服务器“将来从这个客户端传来的信息采用字符集utf-8”。它还为服务器发送回客户端的结果指定了字符集。(例如,如果你使用一 个SELECT语句,它表示列值使用了什么字符集。)


八、乱码出现的常见原因

在了解了 Java Web 中可能需要编码的地方后,下面看一下,当我们碰到一些乱码时,应该怎么处理这些问题?出现乱码问题唯一的原因都是在 char 到 byte 或 byte 到 char 转换中编码和解码的字符集不一致导致的,由于往往一次操作涉及到多次编解码,所以出现乱码时很难查找到底是哪个环节出现了问题,下面就几种常见的现象进行分析。

1、中文变成了看不懂的字符

例如,字符串“淘!我喜欢!”变成了“Ì Ô £ ¡Î Ò Ï²»¶ £ ¡”编码过程如下图所示

字符串在解码时所用的字符集与编码字符集不一致导致汉字变成了看不懂的乱码,而且是一个汉字字符变成两个乱码字符。

2、一个汉字变成一个问号

例如,字符串“淘!我喜欢!”变成了“??????”编码过程如下图所示

将中文和中文符号经过不支持中文的 ISO-8859-1 编码后,所有字符变成了“?”,这是因为用 ISO-8859-1 进行编解码时遇到不在码值范围内的字符时统一用 3f 表示,这也就是通常所说的“黑洞”,所有 ISO-8859-1 不认识的字符都变成了“?”。

3、一个汉字变成两个问号

例如,字符串“淘!我喜欢!”变成了“????????????”编码过程如下图所示

这种情况比较复杂,中文经过多次编码,但是其中有一次编码或者解码不对仍然会出现中文字符变成“?”现象,出现这种情况要仔细查看中间的编码环节,找出出现编码错误的地方。

4、网页中head标签设置的编码和保存网页的编码不同

我用  gb2312 编码 保存了一个网页【大白话,里面的中文都是gb2312编码】。然后我在该网页中,<META http-equiv=”content-type” content=”text/html; charset=xxx”>设置了utf-8编码。  于是出现乱码了。

5、一种不正常的正确编码

还有一种情况是在我们通过 request.getParameter 获取参数值时,当我们直接调用

String value = request.getParameter(name);

会出现乱码,但是如果用下面的方式

String value = String(request.getParameter(name).getBytes("
ISO-8859-1"), "GBK");

解析时取得的 value 会是正确的汉字字符,这种情况是怎么造成的呢?

看下如所示:

这种情况是这样的,ISO-8859-1 字符集的编码范围是 0000-00FF,正好和一个字节的编码范围相对应。这种特性保证了使用 ISO-8859-1 进行编码和解码可以保持编码数值“不变”。虽然中文字符在经过网络传输时,被错误地“拆”成了两个欧洲字符,但由于输出时也是用 ISO-8859-1,结果被“拆”开的中文字的两半又被合并在一起,从而又刚好组成了一个正确的汉字。虽然最终能取得正确的汉字,但是还是不建议用这种不正常的方式取得参数值,因为这中间增加了一次额外的编码与解码,这种情况出现乱码时因为 Tomcat 的配置文件中 useBodyEncodingForURI 配置项没有设置为”true”,从而造成第一次解析式用 ISO-8859-1 来解析才造成乱码的。


九、http编码优先级及编码别名的宽容性

1、编码优先级问题

根据 HTML4.01 规范中的描述,服务端应该提供给用户端文档的字符编码(character encoding)信息,最直接的方式为通过 HTTP 协议([RFC2616], 3.4 及 14.17) “Content-Type” 头字段的 “charset” 将文档的字符编码告诉用户端。例如以下 HTTP 头声明了字符编码为 ISO-8859-1:

Content-Type: text/html; charset=ISO-8859-1

处于某种情况无法访问服务器时,HTML 文档可以包含有关文档的字符编码的明确信息,META 元素可以用来为用户端提供这些信息。例如指定当前文档的字符编码为 ISO-8859-1,文档中应包含如下 META 声明:

<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

当 HTTP 协议与 META 元素均没有提供有关一个文档的字符编码信息时,HTML 还为一些元素提供了 charset 属性。结合这些机制,作者可以在很大程度上提高当用户获取资源时用户端识别字符编码的机会。

针对如何确定一个文档的字符编码,用户代码必须遵守下面的优先级顺序(优先级由高至低):

(1)HTTP “Content-Type” 字段中的 “charset” 参数。
(2)META 声明中 “http-equiv” 为 “Content-Type” 对应的值中的 “charset” 的值。
(3)元素的 charset 属性。

关于 字符编码 的详细信息,请参考 HTML4.01 规范 5.2 Character encodings 以及 W3C Internationalization 关于 Character encodings 中的内容。

2、编码别名宽容性问题

补充知识:做个测试,我们就能明白各浏览器对页面的默认字符编码不尽相同。
当页面没有设置任何字符编码信息或者浏览器无法识别 HTTP 头字段以及 META 元素中所声明的字符编码信息时,所有浏览器均会以各自在当前语言版本下的默认字符编码显示页面。

编码别名宽容性举例:

<?php
  header("Content-Type: text/html; charset=maccyrillic");
?>
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=b.i.g+5"/>
</head>
<body style="font:24px Tahoma;">
字符編碼 --- 
<script>
  document.write((document.charset || document.characterSet).toUpperCase());
</script>
</body>
</html>

上面的动态页面自身的编码为 BIG5,HTTP “Content-Type” 头字段设置了字符编码为 maccyrillic,页面中的 META 元素设置了字符编码为 b.i.g+5。

各浏览器中运行效果如下:

IE6 IE7 IE8 Firefox Chrome Safari Opera
才絪絏 — GB2312 ¶r≤≈љsљX — X-MAC-CYRILLIC 字符編碼 — BIG5

(1)     在 IE6 IE7 IE8 Firefox 中,浏览器无法识别 maccyrillic 这种字符编码别名,所以寻找更低优先级的 META 元素声明的字符编码,但发现也无法识别 b.i.g+5 这种字符编码别名,继而采用了当前语言版本的默认编码 GB2312,与页面自身的字符编码 BIG5 不相符,导致页面中的文字显示异常。
(2)     在 Chrome Safari 中,浏览器将 maccyrillic 识别为合法的 X-MAC-CYRILLIC,则不再理会更低优先级的编码设置,页面采用 X-MAC-CYRILLIC 作为字符的编码,与页面自身的 BIG5 编码不符,导致页面中的文字显示异常。
(3)     在 Opera 中,浏览器无法识别 maccyrillic 这种字符编码别名,所以寻找更低优先级的 META 元素声明的字符编码,将 b.i.g+5 这种字符编码别名识别为 BIG5,刚好与页面自身的 BIG5 字符编码相符,所以页面中的文字显示正常。

出现上述现象的原因主要有三点:

(1)      各浏览器的字符编码别名表不尽相同,对同一种编码下的各种别名支持的宽泛程度不一样。像 maccyrillic 这种别名在 Chrome Safari 可以识别为通用的 X-MAC-CYRILLIC1,但其他浏览器则会将其视作错误的字符编码信息处理。
(2)     各浏览器在匹配页面的字符编码与别名表中的字符编码时,匹配的方式不同。Chrome Safari Opera 会将编码类型的字符串做一个转换,过滤了除英文大小写字符、数字字符之外的字符(isASCIIAlphanumeric)。如 ISO8859_5 会以转换后的 ISO88595 与别名表中的 ISO-8859-5 转换后的 ISO88595 做比较,b.i.g+5 也会转换为 big5 与别名表中的做比较,所以浏览器可以正确识别这些设置的字符编码为浏览器内部的别名。
(3)     各浏览器的默认字符编码不同。
注:各浏览器均可以识别 X-MAC-CYRILLIC 这种通用的字符编码别名。


附录:http 抓包工具 和  测试网页代码

1、http抓包工具fiddler2

2、测试网页代码下载

GBK编码的文件:
<!DOCTYPE html>
<html>
<head>
    <title>gb2312网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=gb2312">
</head>
<body>
    <center>
        <form action="gbk-response/春节" method="get">
            <p>用户名:<input type="text" name="name"/></p>
            <p>密码:<input type="password" name="pwd"/></p>
            <input type="submit" value="登录">

        </form>
    </center>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>gb2312网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=gb2312">
</head>
<body>
    <center>
        <form action="gbk-response/春节"  method="post">
            <p>用户名:<input type="text" name="name"/></p>
            <p>密码:<input type="password" name="pwd"/></p>
            <input type="submit" value="登录">

        </form>
    </center>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>gb2312网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=gb2312">
</head>
<body>
    <center>
       
        gb2312 响应页
        
    </center>
</body>
</html>
UTF-8编码的文件:
<!DOCTYPE html>
<html>
<head>
    <title>utf-8网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
    <center>
        <form action="utf-8-response/春节" method="get">
            <p>用户名:<input type="text" name="name"/></p>
            <p>密码:<input type="password" name="pwd"/></p>
            <input type="submit" value="登录">

        </form>
    </center>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>utf-8网页测试post提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
    <center>
        <form action="utf-8-response/春节" method="post">
            <p>用户名:<input type="text" name="name"/></p>
            <p>密码:<input type="password" name="pwd"/></p>
            <input type="submit" value="登录">

        </form>
    </center>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>utf-8网页测试get提交数据方法</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
    <center>
        utf-8 响应页
    </center>
</body>
</html>

查看 网页的请求和响应,可以用 fiddler2 或者 浏览器开发者工具 –> network –>网页–>headers


参考:

http://www.w3help.org/zh-cn/causes/HR9001
https://blog.csdn.net/xiaokuikey/article/details/50517015
http://aub.iteye.com/blog/763117
https://www.cnblogs.com/JemBai/archive/2010/11/10/1873764.html
http://www.ruanyifeng.com/blog/2010/02/url_encoding.html
http://kaozjlin.iteye.com/blog/1038802
https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html