xml基础-xml结构介绍

XML由3个部分构成,它们分别是:文档类型定义(Document Type Definition,DTD),即XML的布局语言;可扩展的样式语言(Extensible Style Language,XSL),即XML的样式表语言;以及可扩展链接语言(Extensible Link Language,XLL)。


重新编辑勘误一下:在普遍应用XSD文件规范XML格式之前,流传过一段使用DTD文件规范XML格式的时间。因为DTD文件的语法比较复杂,不如XSD简单明了,所以渐渐的DTD文件退出了网络平台。

那xsl文件是做什么的呢?xsl文件更像是一种编程语言,文件中包含了很多逻辑运算符号,用来渲染XML承载的数据的表现方式,因为xsl文件会操作XML文件中的数据,然后生成相应的HTML标签。

以前xml与xsl结合现实web page很常见,CSDN在2004年之前就是这么用的。这种想法是很先进的,就是服务器端分别提供数据与控制展现方式的xsl文件,这样如果想使用不同的主题,就换一套xsl就可以了。但是后来不用了,xsl也退出了舞台,因为效率低,需要在浏览器端进行转换,非常慢。

XLL:Microsoft Excel内插器(加载项)文件。Excel加载项文件与DLL文件类似,只是它们是专门为Microsoft Excel构建的。
XLL 是 XML 链接语言,它提供了 XML 中的链接,类似 HTML 中的链接,然而功能更强。用 XLL,链接可以是多向的,而且链接可存在于对象级,不是仅在页面级。Internet Explorer 5 不支持 XLL。参看MSDN【个人感觉XLL文件,要么是链接文件,要么是加载项文件,目前好像没有看到过XLL文件了。】


(1)DTD
DTD规定了文档的逻辑结构。它可定义文档的语法,而文档的语法反过来也能够让XML语法分析程序确认页面标记使用的合法性。DTD定义了页面的元素、元素的属性及元素和属性间的关系。元素与元素间用起始标记和结束标记来定界,对于空元素,用一个空元素标记来分隔。每一个元素都有一个用名字标识的类型,也称为它的通用标识符,并且它还可以有一个属性说明集。每个属性说明都有一个名字和一个值。理想定义应该面向描述与应用程序相关的数据结构,而不是如何显示数据。就是说,应该把一个元素定义为一个标题行,之后让样式表和脚本定义显示标题行。
DTD不具强制性。对于简单的应用程序来说,开发商不需建立自己的DTD,可以使用预先定义的公共DTD或不使用。即使某个文档已经有DTD,只要文档组织是良好的,语法分析程序也不必对照DTD来检验文档的合法性。服务器可能己执行了检查,所以检验的时间和带宽将得以大幅度节省。
(2)XSL
XSL是用来规定XML文档样式的语言。XSL能使Web浏览器改变原有文档的表示法,例如改变数据的显示顺序,不必再与服务器进行交互通信。通过样式表的变换,同一文档可以显示得更大,或经过折叠只显示外面的一层,或者变为打印格式。
XSL凭借其本身的可扩展性,能够控制无穷无尽的标记,而且控制每个标记的方式也是无穷尽的,这也给Web提供了高级的布局特性。如文本的旋转、多列和独立区域。同时支持国际书写格式,可在一页上混合使用从左至右、从右至左及从上至下的书写格式。就如同XML介于HTML和SGML之间一样,XSL标准是介于CSS和SGMI的文档样式语义和规范语言之间的。
(3)XLL
XLL支持Web上已有的简单链接,而且将进一步扩展链接,包括终结死链接的间接链接及可从服务器中只查询某个元素的相关部分链接等。
超文本标记语言(HTML)仅仅执行历来与超文本系统概念相关的极少功能,仅支持最简单的链接形式,即指向硬编码位置的单向链接,这与XML相比有着很大的差别。在为XML所设想的真正超文本系统中,所有典型的超文本链接机制全部将得到支持,包括:与位置无关命名,双向链接,可在文档外规定和管理的链接,元超链接(如环路、多个窗口),集合链接(多来源),Transc1usion(链接目标文档是链接源文档的一部分),链接属性(链接类型)。
所有这些可通过XLL来实现。由于XML以SGML作为基础,因此,XLL基本上属于Hytime(超媒体/基于时间的结构语言,ISO10744)的一个子集,另外它还遵循文本编码所倡议规定的链接概念。
XML能方便有效地表示结构化数据,这就使得XML可以作为描述和传输数据的手段。使用XML进行数据交换已经成为计算机软件领域的标准技术模式。通过XML实现数据的标准化、结构化,解决了在不同平台、不同系统之间的数据结构/模式的差异,使得数据层在XML技术的支持下统一起来。
Web Service全部的规范,技术都是以XML为底层核心和构架基础的,对Web Service而言,SOAP、WSDL和UDDI,都是使用XML作为信息描述和交换的标准手段。XML技术的产生促使了Web Service技术的产生与发展。

xml基础-xml简介

可扩展标记语言,标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。
在电子计算机中,标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种的信息比如文章等。它可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。 它非常适合万维网传输,提供统一的方法来描述和交换独立于应用程序或供应商的结构化数据。是Internet环境中跨平台的、依赖于内容的技术,也是当今处理分布式结构信息的有效工具。早在1998年,W3C就发布了XML1.0规范,使用它来简化Internet的文档信息传输。

1998年2月,W3C正式批准了可扩展标记语言的标准定义,可扩展标记语言可以对文档和数据进行结构化处理,从而能够在部门、客户和供应商之间进行交换,实现动态内容生成,企业集成和应用开发。可扩展标记语言可以使我们能够更准确的搜索,更方便的传送软件组件,更好的描述一些事物。例如电子商务交易等。
它被设计用来传输和存储数据; [1]
超文本标记语言被设计用来显示数据。
它们都是标准通用标记语言的子集。

一、什么是可扩展标记语言?
可扩展标记语言是一种很像超文本标记语言的标记语言。
它的设计宗旨是传输数据,而不是显示数据。
它的标签没有被预定义。您需要自行定义标签。
它被设计为具有自我描述性。
它是W3C的推荐标准。

二、可扩展标记语言和超文本标记语言之间的差异
它不是超文本标记语言的替代。
它是对超文本标记语言的补充。
它和超文本标记语言为不同的目的而设计:
它被设计用来传输和存储数据,其焦点是数据的内容。
超文本标记语言被设计用来显示数据,其焦点是数据的外观。
超文本标记语言旨在显示信息,而它旨在传输信息。
对它最好的描述是:它是独立于软件和硬件的信息传输工具。

三、可扩展标记语言是W3C的推荐标准
XML 于 1998 年 2 月 10 日成为 W3C 的推荐标准。

四、可扩展标记语言无所不在
超文本标记语言。
XML 是各种应用程序之间进行数据传输的最常用的工具。


可以在可扩展标记语言文件的内容包括几乎所有的万国码Unicode字符(以下例子使用本条例,以 标准通用标记语言常用来定义针对HTML的文档类型定义(DTD),同时它也常用于编写XML的DTD。标准通用标记语言的问题就在于它允许出现一些奇怪的语法,这让创建HTML的解析器成为一个大难题:
某些起始标签可以选择性出现结束标签或者隐含了结束标签。
某些起始标签要求必须出现结束标签,例如HTML中<script>“脚本”标签。
标签可以以任何顺序嵌套。即使结束标签不按照起始标签的逆序出现也是允许的,例如,This is asamplestring是正确的。
某些特性要求必须包含值,例如<图片 源=”百度百科.jpg”>中的源特性。
某些特性不要求一定有值,例如中的“不换行”(外语:nowrap)特性。
定义特性的两边有没有加上双引号都是可以的,所以都是允许的。

<百度百科  词条="可扩展标记语言">
和
<百度百科  词条=可扩展标记语言>

这些问题使建立一个标准通用标记语言的解析器变成了一项艰巨的任务,判断何时应用以上规则的困难导致了标准通用标记语言语言的定义一直停滞不前,以这些问题作为出发点,XML逐渐步入我们的视野。
XML去掉了之前令许多开发人员头疼的标准通用标记语言的随意语法。在XML中,采用了如下的语法:
一、任何的起始标签都必须有一个结束标签。
二、可以采用另一种简化语法,可以在一个标签中同时表示起始和结束标签。这种语法是在大于符号之前紧跟一个斜线(/),例如<百度百科词条/>。XML解析器会将其翻译成<百度百科词条></百度百科词条>。
三、标签必须按合适的顺序进行嵌套,所以结束标签必须按镜像顺序匹配起始标签,例如这是一串百度百科中的样例字符串。这好比是将起始和结束标签看作是数学中的左右括号:在没有关闭所有的内部括号之前,是不能关闭外面的括号的。
四、所有的特性都必须有值。
五、所有的特性都必须在值的周围加上双引号。
这些规则使得开发一个XML解析器要简便得多,而且也除去了解析标准通用标记语言中花在判断何时何地应用那些奇怪语法规则上的工作。仅仅在XML出现后的前六年就衍生出多种不同的语言,包括MathML、SVG、RDF、RSS、SOAP、XSLT、XSL-FO,而同时也将HTML改进为XHTML。


可扩展标记语言是一种元标记语言,即定义了用于定义其他特定领域有关语义的、结构化的标记语言,这些标记语言将文档分成许多部件并对这些部件加以标识。XML 文档定义方式有:文档类型定义(DTD)和XML Schema。DTD定义了文档的整体结构以及文档的语法,应用广泛并有丰富工具支持。XML Schema用于定义管理信息等更强大、更丰富的特征。XML能够更精确地声明内容,方便跨越多种平台的更有意义的搜索结果。它提供了一种描述结构数据的格式,简化了网络中数据交换和表示,使得代码、数据和表示分离,并作为数据交换的标准格式,因此它常被称为智能数据文档。
XML技术已经广泛应用于e-Learning应用系统的开发,大多数的商用e-Learning平台都支持XML标准。一些主要的网络设备制造商,如CISCO、JUNIPER等,生产的网络设备也已提供了对XML的支持,以利于今后基于XML的网络管理。
XML在e-Learning管理中的应用
一、兼容现有协议
XML文档格式的管理信息可以很容易地通过HTTP 协议传输,由于HTTP是建立在TCP之上的,故管理数据能够可靠传输。XML还支持访问XML文档的标准API,如DOM,SAX,XSLT,Xpath等。
二、统一的管理数据存取格式
XML能够以灵活有效的方式定义管理信息的结构。以XML格式存储的数据不仅有良好的内在结构,而且由于它是W3C提出的国际标准,因而受到广大软件提供商的支持,易于进行数据交流和开发。现有网络管理标准如TMN、SNMP等的管理信息库规范决定了网管数据符合层次结构和面向对象原则,这使得以XML格式存储网管数据也非常自然,易于实现。
三、不同应用系统间数据的共享和交互
只要定义一套描述各项管理数据和管理功能的XML语言,用Schema对这套语言进行规定,并且共享这些数据的系统的XML文档遵从这些Schema,那么管理数据和管理功能就可以在多个应用系统之间共享和交互。
四、底层传输的数据更具可读性
网络中传输的底层数据因协议不同而编码规则不同,虽然最终传输时都是二进制位流,但是不同的应用协议需要提供不同的转换机制。这种情况导致管理站在对采用不同协议发送管理信息的被管对象之间进行管理时很难实现兼容。如果协议在数据表示时都采用XML格式进行描述,这样网络之间传递的都是简单的字符流,可以通过相同的XML解析器进行解析,然后根据不同的XML标记,对数据的不同部分进行区分处理,使底层数据更具可读性。

字符编码-教程(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

服务器开发-概述

想做一个简单的服务器,用来更好的了解web交互模式。


http1.0 简单服务器 :三个文件

package com.httpserver;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

import javax.swing.JTextArea;
import javax.swing.JTextField;


public class HttpResponse extends Thread {
	Socket socket;
	JTextArea ta;
	PrintStream pout;
	boolean isHttp1 = false;
	String path = null;
	HttpResponse(Socket socket, JTextArea ta,JTextField path)
	{
		this.socket = socket;
		this.ta = ta;
		this.path = path.getText();
	}
	
	public void run()
	{
		try {
		
			BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			pout = new PrintStream(socket.getOutputStream());
			String requestLine = br.readLine();
			//String requestCmds = ""+requestLine;
			if(requestLine==null){
				error(400,"Empty Request");
			}
			else{
				ta.append("Http请求,来自:["+socket.getInetAddress()+":"+socket.getPort()+"]   "+requestLine+"\n");
			}
			if(requestLine.toLowerCase().indexOf("http/1.")!=-1){
				isHttp1 = true;
			}
			String[] request = requestLine.split(" ");
			if(request.length<2)
			{
				error(400,"Bad Request");
			}
			String str1 = request[0];
			if(str1.equals("GET")){
				serveFile(request[1]);
			}
			else{
				error(400, "Bad Request");
				ta.append("Bad Request");
			}
			
			socket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
			return;
		}
		
	}
	
	
	private void error(int erorcd, String erortx) {
		erortx = "<html><h1>" + erortx + "</h1></html>";
		if (isHttp1) {
			pout.println("HTTP/1.0 " + erorcd + " " + erortx);
			pout.println("Content-type: text/html");
			pout.println("Content-length: " + erortx.length() + "\n");
		}
		pout.println(erortx);
	}
	
	private void serveFile(String requestPath)  {
		if (requestPath.equals("/")){
			/**
			 * 取首页文件,首页文件可以为index.html或index.htm
			 */
			requestPath = "/index.html";
			if(path==null){
				path=new File("").getAbsolutePath();
			}
			if(!new File(path+requestPath).exists()){
				requestPath="/index.htm";
			}			
		}				
		try {
			sendFileData(requestPath);
			ta.append("文件传输成功 !       "+requestPath+"\n");
		} catch (Exception e) {
			error(404, "");
			ta.append("请求文件不存在\n");
		}		
	}
	
	private void sendFileData(String requestPath) throws IOException,FileNotFoundException {
		InputStream inputstream = new FileInputStream(path+requestPath);
		if (inputstream == null)
			throw new FileNotFoundException(requestPath);		
		if (isHttp1) {
			pout.println("HTTP/1.0 200 Document follows");
			pout.println("Content-length: " + inputstream.available());
			
			if (requestPath.endsWith(".gif"))
				pout.println("Content-type: image/gif");
			else if (requestPath.endsWith(".jpg"))
				pout.println("Content-type: image/jpeg");
			else if (requestPath.endsWith(".png") || requestPath.endsWith(".htm"))
				pout.println("Content-Type: text/png");
			
			else if (requestPath.endsWith(".css") || requestPath.endsWith(".htm"))
				pout.println("Content-Type: text/css");
			else if (requestPath.endsWith(".js") || requestPath.endsWith(".htm"))
				pout.println("Content-Type: text/javascript");

			else if (requestPath.endsWith(".html") || requestPath.endsWith(".htm"))
				pout.println("Content-Type: text/html");

			else
				pout.println("Content-Type: application/octet-stream");
			
			pout.println("Connection: Keep-Alive");//println 表示写完自带换行
			/*****下面这个换行回车,必须要有 这个是报头 格式******/
			pout.println();
			/***** 但是这个 只需要一个换行回车就行了 **/
			//pout.println();

           
			
		}
		/*缓冲区设为8K*/
		byte[] is = new byte[8*1024];
		/*实际测试 缓冲区 太小了,请求一张 大于 8K 的图片就 无法传输了,好像不是这个问题*/
		//byte[] is = new byte[300*1024];

		
		int length=0;
		while((length=inputstream.read(is))!=-1){
			pout.write(is, 0, length);
		}
		inputstream.close();
		pout.flush();
	}

}
package com.httpserver;

import java.net.*;

import javax.swing.*;

public final class ServerThread extends Thread {
	int port;
	JTextArea display;
	JTextField status;
	JTextField direc;
	//JTextField ipAdd;
	ServerSocket listener = null;
	ServerThread (int port, JTextArea display, JTextField status,JTextField direc,JTextField ipAdd) throws Exception
	{
		this.port = port;
		this.display = display;
		this.status = status;
		this.direc = direc;
		byte[] ip = new byte[4];
		String ipp = ipAdd.getText();
		//System.out.println(ipp);
		String[] ip1 = ipp.split("\\.");
		for(int i = 0;i<ip1.length;i++){
			try{
				ip[i] = (byte) Integer.parseInt(ip1[i]);
			}
			catch(Exception e){
				ip[i] = -1;
			}
			//System.out.println(ip1[i]);
		}
		//System.out.println(ip[0]+" "+ip[1]+" "+ip[2]+" "+ip[3]);
		//this.ipAdd = ipAdd;
		try{
			listener = new ServerSocket(port,20, InetAddress.getByAddress(ip));
		}
		catch(Exception e){
			
			listener = new ServerSocket(port,20);
			display.append("无效的IP地址,已使用默认地址:"+InetAddress.getLocalHost().getHostAddress()+"\n");
			ipAdd.setText(InetAddress.getLocalHost().getHostAddress());
		}
		display.append("服务器已开启,端口:"+port+"\n");
		status.setText("服务器已开启。 "+ipAdd.getText()+":"+port+"  "+direc.getText());
	}
	
	public void run()
	{
		
		
		while(true)
		{
			try{
				Socket socket = listener.accept();
				/*for(String s = new BufferedReader(new InputStreamReader(socket.getInputStream())).readLine();s!="\n";
						s = new BufferedReader(new InputStreamReader(socket.getInputStream())).readLine()){
					display.append(new BufferedReader(new InputStreamReader(socket.getInputStream())).readLine());
				}
				*/
				/*BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				for(String s = br.readLine();s!=null;s = br.readLine()){
					display.append(s+"\n");
				}*/
				new HttpResponse(socket, display, direc).start();
			}
			catch(Exception e){
				display.append(e+"\n");
			}
		}
	}
	
}
package com.httpserver;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.plaf.synth.SynthStyle;

public class Surface implements ActionListener {

	JFrame jf = new JFrame("Web Server");
	JLabel iP = new JLabel("IP: ");
	JLabel port = new JLabel("Port: ");
	JTextField ipAdd = new JTextField(15);
	JTextField portNum = new JTextField(4);
	JButton start = new JButton("Start");
	JButton over = new JButton("Stop");
	JLabel direct = new JLabel("Main Directory: ");
	JTextField directory = new JTextField(24);
	JButton br = new JButton("...");
	// JLabel diary = new JLabel("Diary:");
	JTextArea ta = new JTextArea(10, 40);
	// JTextField outDirct = new JTextField(15);
	// JTextField outIp = new JTextField(8);
	JTextField outStatus = new JTextField(40);
	// JLabel status = new JLabel("Status:");
	ServerThread listener = null;
	boolean hasStarted = false;
	String ipp;

	public void init() {
		JPanel jp1 = new JPanel();
		jp1.add(iP);
		jp1.add(ipAdd);
		jp1.add(port);
		jp1.add(portNum);
		jp1.add(start);
		jp1.add(over);
		JPanel jp2 = new JPanel();
		jp2.add(direct);
		jp2.add(directory);
		jp2.add(br);
		// JPanel jp3 = new JPanel();
		// jp3.add(diary);
		// JScrollPane jsp = new JScrollPane();
		// jsp.add(ta);
		Border bb = BorderFactory.createEtchedBorder();
		Border tb1 = BorderFactory.createTitledBorder(bb, "Console");
		Border tb2 = BorderFactory.createTitledBorder(bb, "Diary");
		JScrollPane jsp = new JScrollPane(ta);
		JPanel jp4 = new JPanel();
		jp4.add(jsp);
		jp4.setBorder(tb2);
		ta.setLineWrap(true);
		ta.setEditable(false);
		start.addActionListener(this);
		over.addActionListener(this);
		br.addActionListener(this);
		Box jb1 = Box.createVerticalBox();
		jb1.add(jp1);
		jb1.add(jp2);
		jb1.setBorder(tb1);
		directory.setText("/Users/hello/Sites/testwebsites");
		// Box jb2 = Box.createVerticalBox();
		// jb2.add(jp3);
		// jb2.add(jp4);
		Box jb = Box.createVerticalBox();
		// jb.setBorder(bb);
		outStatus.setBackground(jf.getBackground());
		outStatus.setBorder(null);
		outStatus.setEditable(false);
		JPanel jp5 = new JPanel();
		jp5.setBorder(bb);
		// jp5.add(status);
		jp5.add(outStatus);
		outStatus.setText("服务器已停止");
		try {
			ipAdd.setText(InetAddress.getLocalHost().getHostAddress());
		} catch (Exception e) {
			ipAdd.setText("0.0.0.0");
		}
		jb.add(jb1);
		jb.add(jp4);
		jb.add(jp5);
		// jb.add(jp3);
		// jb.add(jp4);
		jf.add(jb);
		// jf.add(jp1);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setResizable(false);
		jf.pack();
		jf.setVisible(true);

	}

	public void startServer() {
		start.setEnabled(false);
		over.setEnabled(true);
		int port = 8080;
		try {
			port = Integer.parseInt(portNum.getText());
		} catch (Exception e) {
			ta.append("端口号异常,已使用8080端口。\n");
			portNum.setText("8080");
		}
		if (hasStarted && port == listener.port) {
			listener.resume();
			ta.append("服务器已开启,端口:" + listener.port + "\n");
			outStatus.setText("服务器已开启。" + ipp + ":" + port + "  " + directory.getText());
			// ipAdd.setText(InetAddress.getLocalHost().getHostAddress());
			return;
		}

		try {
			listener = new ServerThread(port, ta, outStatus, directory, ipAdd);
			ipp = ipAdd.getText();
		} catch (Exception e) {
			ta.append("端口已被其他程序占用,请重试。\n");
			listener = null;
			hasStarted = false;
		}
		if (listener == null) {
			start.setEnabled(true);
		} else {
			hasStarted = true;
			listener.start();
		}
	}

	public void exitServer() {
		ta.append("服务器关闭。\n");
		if (listener != null) {
			listener.stop();
		}
		System.exit(0);
	}

	public void stopServer() {
		start.setEnabled(true);
		over.setEnabled(false);
		listener.suspend();
		ta.append("服务器已停止。\n");
		outStatus.setText("服务器已停止。");
	}

	public void selectPath() {
		String str = "";
		String loc = "";
		JFileChooser chooser = new JFileChooser();
		chooser.setCurrentDirectory(new java.io.File("."));
		chooser.setDialogTitle("主目录");
		chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		chooser.setAcceptAllFileFilterUsed(false);
		if (chooser.showOpenDialog(jf) == JFileChooser.APPROVE_OPTION) {
			// str += chooser.getCurrentDirectory();
			str += chooser.getSelectedFile();
			loc = str.replaceAll("\\\\", "/"); //// Windows路径到JAVA路径的转换
		}
		directory.setText(loc);
	}

	public void actionPerformed(ActionEvent event) {
		String command = event.getActionCommand();
		if (command.equals("Start")) {
			startServer();
		}
		if (command.equals("Stop")) {
			stopServer();
		}
		if (command.equals("Exit")) {
			exitServer();
		}
		if (command.equals("...")) {
			selectPath();
		}
	}

	public static void main(String[] args) {
		Surface sf = new Surface();
		sf.init();
		// sf.outStatus.setText("服务器已开启:220.111.332.444:8000 sdafsagassasa");

		 String a = new String("源码下载站");
		 System.out.println("16制"+strTo16(a));

		char c = '8';
		System.out.printf("The value of char %c  %X,is %d.%n", c, (int)c,(int)c);
        //16进制 转成10进制。  java 内部编码是 utf-16 
		
		String str = String.valueOf(c);
		byte[] bys;
		try {
			bys = a.getBytes("UTF-8");
			for (int i = 0; i < bys.length; i++) {
				System.out.printf("%X ", bys[i]);
			}
            
			//因为是Unicode编码,所以编码前有字节序。
			//(byte & 0xFF) 这是一个字节相与,然后 << 8  代表数据 左移 8位 ,相当于 两字节大小 与后面的 或运算
			// 相当于 地位的字节 复制了 新添加的 字节
			//int unicode = (bys[2] & 0xFF) << 8 | (bys[3]& 0xFF);
			//System.out.printf("The unicode value of %c is %d.%n", c, unicode);

		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public static String strTo16(String s) {
		String str = "";

		System.out.println("字符串长度"+s.length());
		for (int i = 0; i < s.length(); i++) {
			int ch = (int) s.charAt(i);
			String s4 = Integer.toHexString(ch);
			str = str + s4;
		}
		return str;
	}

}

参考自:GitHub

另,Tomcat源码剖析电子书:

代码和UML图:https://github.com/Aresyi/HowTomcatWorks 
排版更好的百度电子书:https://yuedu.baidu.com/ebook/ac92f0d35122aaea998fcc22bcd126fff7055d60

网络编程资料:

自定义 服务器 css 设置成了 二进制类型 导致加载 不出来 样式,报文头和体 有且只有空一行 才能 被 浏览器 正确 解析。空两行 浏览器 加载不了 图片

http://localhost/~cool/beike/index.html

http://www.ietf.org/rfc/rfc1945.txt

 

http编码

https://blog.csdn.net/hongxingxiaonan/article/details/49963643

tcp连接详解:

https://blog.csdn.net/liuxinmingcode/article/details/50376035

tcp包长度

https://blog.csdn.net/lishanmin11/article/details/77045745

http 长度

https://blog.csdn.net/zerooffdate/article/details/78962818

http详解

https://segmentfault.com/a/1190000006689767#articleHeader8

https://blog.csdn.net/u012813201/article/details/70211255

websocket

https://www.jianshu.com/p/f666da1b1835

http://www.cnblogs.com/hustskyking/p/websocket-with-node.html

https://www.cnblogs.com/oshyn/p/3574497.html