字符集编码知识集锦

  注:本文收集自网络,作者不一。

Ascii

ASCII是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646

标准ASCII 码也叫基础ASCII码,使用位二进制数来表示所有的大写和小写字母,数字9、标点符号, 以及在美式英语中使用的特殊控制字符,共128字符。在标准ASCII中,其最高位(b7)用作奇偶校验位。为了表示更多的欧洲常用字符对ASCII进行了扩展,ASCII扩展字符集使用8位(bits)表示一个字符,共256字符。ASCII扩展字符集比ASCII字符集扩充出来的符号包括表格符号、计算符号、希腊字母和特殊的拉丁符号

Anis

ANSI码(American National Standards Institute),中文:美国国家标准学会的标准码

对于欧美国家的语言字符,ASCII尚能应付自如,可是随着计算机的发展和普及,伴随着中文、日文、韩文等语言的需求,256个字符远远不能表示所有的常用字符了。这时就需要对原本的ASCII进行改进以表示更多的字符,最简单最实际的做法就是扩展字节。将128作为分水岭,小于128的字符还是使用正常的一个字节的ASCII进行表示,保证了英文的兼容。把大于128的字符作为一个引导字节,来决定后边字符的编码的长度和内容。通过这种变长的灵活编码方式,使得这种编码支持了几乎常用的所有语言的字符集,例如我们常用的GB2312、GBK、GB18030等等。由于ASCII起初是ANSI的标准字符集,因此这种变长编码方式称为ANSI的多字节字符集MBCS,也称为为ANSI字符集。

不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。 

GB2312

GB2312又称为GB2312-80字符集,全称为《信息交换用汉字编码字符集·基本集》,由原中国国家标准总局发布,198151日实施。

GB2312是中国国家标准的简体中文字符集。它所收录的汉字已经覆盖99.75%的使用频率, 基本满足了汉字的计算机处理需要。在中国大陆和新加坡获广泛使用。GB2312收录简化汉字及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。其中包括6763个汉字,其中一级汉字3755个,二级汉字3008个;包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。

GB2312中对所收汉字进行了分区处理,共分为每区含有94个汉字/符号。这种表示方式也称为区位码。

各区包含的字符如下:01-09区为特殊符号;16-55区为一级汉字,按拼音排序;56-87区为二级汉字,按部首/笔画排序;10-15区及88-94区则未有编码。

GB2312是双字节编码,即一个GB2312编码的字符占2个字节。习惯上称第一字节为高字节” ,而称第二字节为低字节高位字节使用了0xA1-0xF7(01-87区的区号加上0xA0)低位字节使用0xA1-0xFE(01-94加上0xA0)GB2312高字节 = “区号”+160 GB2312低字节 = "位号" + 160

GB2312字符集的第一个汉字字为例,它的区号16,位号01,则区位码是1601,在大多数计算机程序中,高字节和低字节分别加0xA0得到程序的汉字GB2312编码为0xB0A1。计算公式是:0xB0=0xA0+16, 0xA1=0xA0+1

所以如果我们求汉字区位码的话,我们可以通过求出该字的GB2312编码,然后将该编码的高字节与低字节分别减去160(0XA0)

BIG5

BIG5又称大五码或五大码,1984年由台湾财团法人信息工业策进会和五间软件公司宏碁 (Acer)、神通 (MiTAC)、佳佳、零壹 (Zero One)、大众 (FIC)创立,故称大五码。

Big5码使用了双字节储存方法,一个字符占2个字节。第一个字节称为高位字节,第二个字节称为低位字节。高位字节的编码范围0xA1-0xF9,低位字节的编码范围0x40-0x7E0xA1-0xFE

Unicode 

Unicode统一码、万国码、单一码)是一种在计算机上使用的字符编码。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

Unicode国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。

最初的unicode编码是固定长度的,16位,也就是2两个字节代表一个字符,这样一共可以表示65536个字符。显然,这样要表示各种语言中所有的字符是远远不够的。Unicode4.0规范考虑到了这种情况,定义了一组附加字符编码,附加字符编码采用216位来表示,这样最多可以定义1048576个附加字符,目前unicode4.0只定义了45960个附加字符。

Unicode的编码和实现

大概来说,Unicode编码系统可分为编码方式和实现方式两个层次。

编码方式

统一码的编码方式与ISO 10646通用字符集(UCS概念相对应。目前实际应用的统一码版本对应于UCS-2,使用16的编码空间。也就是每个字符占用2字节。这样理论上一共最多可以表示216(即65536)个字符。基本满足各种语言的使用。实际上当前版本的统一码并未完全使用这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。

上述16位统一码字符构成基本多文种平面。最新(但未实际广泛使用)的统一码版本定义了16辅助平面,两者合起来至少需要占据21位的编码空间,比3字节略少。但事实上辅助平面字符仍然占用4字节编码空间,与UCS-4保持一致。未来版本会扩充到ISO 10646-1实现级别3,即涵盖UCS-4的所有字符。UCS-4是一个更大的尚未填充完全的31位字符集,加上恒为0的首位,共需占据32位,即4字节。理论上最多能表示231个字符,完全可以涵盖一切语言所用的符号。

基本多文种平面的字符的编码为U+hhhh,其中每个h代表一个十六进制数字,与UCS-2编码完全相同。而其对应的4字节UCS-4编码后两个字节一致,前两个字节则所有位均为0

实现方式

Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF

例如,如果一个仅包含基本7ASCII字符的Unicode文件,如果每个字符都使用2字节的原Unicode编码传输,其第一字节的8位始终为0。这就造成了比较大的浪费。对于这种情况,可以使用UTF-8编码,这是一种变长编码,它将基本7ASCII字符仍用7位编码表示,占用一个字节(首位补0)。而遇到与其他Unicode字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为01进行识别。这样对以7ASCII字符为主的西文文档就大大节省了编码长度(具体方案参见UTF-8)。类似的,对未来会出现的需要4个字节的辅助平面字符和其他UCS-4扩充字符,2字节编码的UTF-16也需要通过一定的算法进行转换。

再如,如果直接使用与Unicode编码一致(仅限于BMP字符)的UTF-16编码,由于每个字符占用了两个字节,在麦金塔电脑 (Mac)机和个人电脑上,对字节顺序的理解是不一致的。这时同一字节流可能会被解释为不同内容,如某字符为十六进制编码4E59,按两个字节拆分为4E59,在Mac上读取时是从低字节开始,那么在Mac OS会认为此4E59编码为594E,找到的字符为,而在Windows上从高字节开始读取,则编码为U+4E59的字符为。就是说在Windows下以UTF-16编码保存一个字符,在Mac OS环境下打开会显示成。此类情况说明UTF-16的编码顺序若不加以人为定义就可能发生混淆,于是在UTF-16编码实现方式中使用了大端序Big-Endian, 简写为UTF-16 BE)、小端序Little-Endian,简写为UTF-16 LE)的概念,以及可附加的字节顺序记号解决方案,目前在PC机上的Windows系统和Linux系统对于UTF-16编码默认使用UTF-16 LE。(具体方案参见UTF-16

此外Unicode的实现方式还包括UTF-7PunycodeCESU-8SCSUUTF-32GB18030等,这些实现方式有些仅在一定的国家和地区使用,有些则属于未来的规划方式。目前通用的实现方式是UTF-16小端序(LE)、UTF-16大端序(BE)和UTF-8。在微软公司Windows XP附带的记事本Notepad)中,另存为对话框可以选择的四种编码方式除去非Unicode编码的ANSI(对于英文系统即ASCII编码,中文系统则为GB2312Big5编码) 外,其余三种为“Unicode”(对应UTF-16 LE)、“Unicode big endian”(对应UTF-16 BE)和“UTF-8”

目前辅助平面的工作主要集中在第二和第三平面的中日韩统一表意文字中,因此包括GBKGB18030Big5简体中文繁体中文日文韩文以及越南喃字的各种编码与Unicode的协调性被重点关注。考虑到Unicode最终要涵盖所有的字符。从某种意义而言,这些编码方式也可视作Unicode的出现于其之前的既成事实的实现方式,如同ASCII及其扩展Latin-1一样,后两者的字符在16Unicode编码空间中的编码第一字节各位全为0,第二字节编码与原编码完全一致。但上述东亚语言编码与Unicode编码的对应关系要复杂得多。

UTF-8

UTF-88-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件网页及其他存储或传送文字的应用中,优先采用的编码。

UTF-8使用一至四个字节为每个字符编码:

128US-ASCII字符只需一个字节编码(Unicode范围由U+0000U+007F)。

带有附加符号拉丁文希腊文西里尔字母亚美尼亚语希伯来文阿拉伯文叙利亚文它拿字母则需要二个字节编码(Unicode范围由U+0080U+07FF)。

其他基本多文种平面BMP)中的字符(这包含了大部分常用字)使用三个字节编码。

其他极少使用的Unicode 辅助平面的字符使用四字节编码。

UTF-8以字节为编码单元,没有字节序的问题。它使用可变长度字节来储存 Unicode字符,ASCII字母继续使用1字节储存,重音文字、希腊字母或西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节,辅助平面字符则使用4字节。(UTF-8编码的最大长度是4个字节)

 Unicode编码(16进制)   ║ UTF-8 字节流(二进制)
    000000 - 00007F   ║     0xxxxxxx
    000080 - 0007FF   ║     110xxxxx 10xxxxxx
    000800 - 00FFFF   ║     1110xxxx 10xxxxxx 10xxxxxx
   010000 - 10FFFF   ║     11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-16

UTF-16Unicode字符编码五层次模型的第三层:字符编码表(Character Encoding Form,也称为 "storage format")的一种实现方式。即把Unicode字符集的抽象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。Unicode字符的码位,需要1个或者216位长的码元来表示,因此这是一个变长表示。

UTF"Unicode/UCS Transformation Format"的首字母缩写,即把Unicode字符转换为某种格式之意。UTF-16正式定义于ISO/IEC 10646-1的附录C,而RFC2781也定义了相似的做法。

Uft-16UCS-2的关系

UTF-16可看成是UCS-2父集。在没有辅助平面字符surrogate code points)前,UTF-16UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。现在若有软件声称自己支持UCS-2编码,那其实是暗指它不能支持在UTF-16中超过2bytes的字集。对于小于0x10000UCS码,UTF-16编码就等于UCS码。

Microsoft Windows操作系统内核对Unicode的支持 

Windows操作系统内核中的字符表示为UTF-16小尾序,可以正确处理、显示以4字节存储的字符。但是Windows API实际上仅能正确处理UCS-2字符,即仅以2字节存储的,码位小于U+FFFFUnicode字符。其根源是Microsoft C++语言把wchar_t数据类型定义为16比特的unsigned short,这就与一个wchar_t型变量对应一个宽字符,可以存储一个Unicode字符的规定相矛盾。相反,Linux平台的GCC编译器规定一个wchar_t4字节长度,可以存储一个UTF-32字符,宁可浪费了很大的存储空间。下例运行于Windows平台的C++程序可说明此点:

// 此源文件在Windows平台上必须保存为Unicode格式(即UTF-16小尾)

// 因为包含的汉字“�”,不能在Windows默认的代码页936(即gbk)中表示。

// 该汉字在UTF-16小尾序中用4个字节表示,

// Windows操作系统能正确显示这样的在UTF-16需用4字节表示的字符

// 但是Windows API不能正确处理这样的在UTF-16需用4字节表示的字符,把它判定为2UCS-2字符

 

#include <windows.h>

int main()

{

        const wchar_t lwc[]=L"";

 

        MessageBoxW(NULL, lwc, lwc, MB_OK);

 

        int i = wcslen(lwc);

        printf("%d\n", i);

        int j = lstrlenW(lwc);

        printf("%d\n", j);

 

        return 0;

}

windows字符集编码

在Windows编程时经常会遇到编码转换的问题,一直以来让刚接触的人摸不着头脑。其实只要弄清Win32程序使用的字符编码方式就清楚了,图1展示了一个Win32控制台项目的属性中的字符集选项。这里有两个不同的字符集:一个是Unicode字符集,另一个就是多字节字符集MBCS(Multi-Byte Character System),即熟知的ANSI字符集。

伴随着图形界面计算机的出现,字符集就应运而生了。要显示字符信息,就需要将之转换为二进制信息表示——编码。可悲的是计算机是美国人发明的,而英语就是26个英文字母和一些常用标点符号的组合,这些字符称为ASCLL字符集。它是使用1个字节的长度进行编码,也就是能表示256个不同的字符,实际上真正用到的可见字符不到128个。

对于欧美国家的语言字符,ASCLL尚能应付自如,可是随着计算机的发展和普及,伴随着中文、日文、韩文等语言的需求,256个字符远远不能表示所有的常用字符了。这时就需要对原本的ASCLL进行改进以表示更多的字符,最简单最实际的做法就是扩展字节。将128作为分水岭,小于128的字符还是使用正常的一个字节的ASCLL进行表示,保证了英文的兼容。把大于128的字符作为一个引导字节,来决定后边字符的编码的长度和内容。通过这种变长的灵活编码方式,使得这种编码支持了几乎常用的所有语言的字符集,例如我们常用的GB2312、GBK、GB18030等等。由于ASCII 起初是ANSI的标准字符集,因此这种变长编码方式称为ANSI的多字节字符集MBCS,也称为为ANSI字符集。

windows关于默认字符集的设定是在

控制面板-->地区及语言选项—>Advanced-->选择非unicode的默认字符集(更改此选项,需要重启电脑)

windows 98由于是继承自16位操作系统的32位操作系统,而16位操作系统只使用ANSI编码,故windows98只支持ANSI编码,windows系统调用函数的参数都必须是ANSI编码; 

windows 2000是一个不同于windows 98的全新操作(即它是重新开发的),它的系统调用函数的参数只支持Unicode,但是它同时也通过一种间接转换来支持ANSI参数的系统调用,即当参数是ANSI时,操作系统会将ANSI转化为Unicode,然后将转换后的参数传给被调用的这个系统函数,同理,若这个系统函数返回值是ANSI,操作系统也会将Unicode转换为ANSI,然后传递这个返回值;因此说windows 2000是既支持ANSI也支持Unicode,但是使用ANSI会降低系统的性能;

C语言的字符编码

c中,char代表字节数据类型;在java中,char代表字符数据类型(内存可能占一个或倆个字节),byte代表字节数据类型;

 C中,字符串char *代表的是ansi编码,ansi不是一种确定的编码,那些所有使用 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在繁体中文系统下,ANSI 编码代表 BIG5 编码,在日文操作系统下,ANSI 编码代表 JIS 编码;

JAVA字符集编码

Javaclass文件采用utf8的编码方式,JVM运行时采用utf16
Java的字符串是unicode编码的。(注:这里说的UNICODE应该是UCS-22字节,支持65536个字符。UTF-16UCS-2的一个超集,在没有辅助平面字符surrogate code points)前,UTF-16UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。现在若有软件声称自己支持UCS-2编码,那其实是暗指它不能支持在UTF-16中超过2bytes的字集。对于小于0x10000UCS码,UTF-16编码就等于UCS码。
总之,Java采用了unicode字符集,使之易于国际化。

下面来看个问题:

// strutf-8编码的字符串吗?  

String str = new String(webPara.getBytes("GBK"), "UTF-8"));

简单地说StringJVM里是unicode的,任何byte[]String以及Stringbyte[]都涉及到字符集编码转换。

byte[] ---> String就是将按某一个编码后的字节数组转换为unicode的字符串,

String ---> byte[]正好相反,它是将unicode的字符串编码为唯一特定字符集编码后的字节数组。

public String(byte[] bytes)  

public String(byte[] bytes, String charsetName)  

这两个String的方法就是完成将bytes“解码unicodeString

前者使用的是jvm默认的字符集编码,而后者是用户指定某一个charsetName,可以是UTF-8,GBK之类的。

两者都是完成specCharsetunicode的过程,而不是说改变编码格式为charsetName指定的字符集编码。

public byte[] getBytes()  

public byte[] getBytes(String charsetName)  

这两个方法就是完成从unicode到指定字符集编码的转码过程。

以浏览器为例,httpparameterservlet里后,

应用服务器已经自动完成了new String(parameterBytes, browserSpecCharset)这个过程。

也就是,自动用页面设置的charset“解码”parameterBytes字节数组为unicode的字符串。

因此,类似最上面的代码

String str = new String(webPara.getBytes("GBK"), "UTF-8"));  

得到的str是用utf-8“解码”经过gbk编码后的字节数组而得到unicode码的字符串。

举个例子 

中国”.getBytes("GBK")  

中国”.getBytes("UTF-8")  

上述字节数组按十六进制打印出来

//GBK编码为   

// D6 D0 B9 FA  

 

//UTF-8编码为   

// E4 B8 AD E5 9B BD  

这个也说明了getBytes方法完成了unicodegbk/utf-8的转换。

综上所述

String str = new String(aStr.getBytes("GBK"), "UTF-8"));  

是无法完成字符集编码转换地,任何一个Java String都是unicode

对用a编码的字节数组,用b去解码,大部分情况都是乱码。


Java支持哪些字符集:


   即Java能识别哪些字符集并对它进行正确地处理?
   查看Charset 类,最新的JDK支持160种字符集。可以通过static方法availableCharsets拿到所有Java支持的字符集。

assertEquals(160, Charset.availableCharsets().size());

Set<String> charsetNames = Charset.availableCharsets().keySet();

assertTrue(charsetNames.contains("utf-8"));
assertTrue(charsetNames.contains("utf-16"));
assertTrue(charsetNames.contains("gb2312"));

assertTrue(Charset.isSupported("utf-8"));


需要在哪些时候注意编码问题?
1. 从外部资源读取数据:
这跟外部资源采取的编码方式有关,我们需要使用外部资源采用的字符集来读取外部数据:

InputStream is = new FileInputStream("res/input2.data");
InputStreamReader streamReader = new InputStreamReader(is, "GB18030");


这里可以看到,我们采用了GB18030编码读取外部数据,通过查看streamReaderencoding可以印证:
 

assertEquals("GB18030", streamReader.getEncoding());

正是由于上面我们为外部资源指定了正确的编码,当它转成char数组时才能正确地进行解码(GB18030 -> unicode):


Java代码
char[] chars = new char[is.available()];
streamReader.read(chars, 0, is.available());

但我们经常写的代码就像下面这样:
 

InputStream is = new FileInputStream("res/input2.data");
InputStreamReader streamReader = new InputStreamReader(is);


这时候InputStreamReader采用什么编码方式读取外部资源呢?Unicode?不是,这时候采用的编码方式是JVM的默认字符集,这个默认字符集在虚拟机启动时决定,通常根据语言环境和底层操作系统的 charset 来确定。可以通过以下方式得到JVM的默认字符集:

Charset.defaultCharset();


为什么要这样?因为我们从外部资源读取数据,而外部资源的编码方式通常跟操作系统所使用的字符集一样,所以采用这种默认方式是可以理解的。
好吧,那么我通过我的IDE Ideas创建了一个文件,并以JVM默认的编码方式从这个文件读取数据,但读出来的数据竟然是乱码。为何?呵呵,其实是因为通过Ideas创建的文件是以utf-8编码的。要得到一个JVM默认编码的文件,通过手工创建一个txt文件试试吧。

2. 字符串和字节数组的相互转换
我们通常通过以下代码把字符串转换成字节数组:

"string".getBytes();

但你是否注意过这个转换采用的编码呢?其实上面这句代码跟下面这句是等价的:

string".getBytes(Charset.defaultCharset());

也就是说它根据JVM的默认编码(而不是你可能以为的unicode)把字符串转换成一个字节数组。
反之,如何从字节数组创建一个字符串呢? 

new String("string".getBytes());


同样,这个方法使用平台的默认字符集解码字节的指定数组(这里的解码指从一种字符集到unicode)。
字符串编码迷思:

new String(input.getBytes("ISO-8859-1"), "GB18030")


上面这段代码代表什么?有人会说: input字符串从ISO-8859-1编码方式转换成GB18030编码方式。如果这种说法正确,那么又如何解释我们刚提到的java字符串都采用unicode编码呢?
这种说法不仅是欠妥的,而且是大错特错的,让我们一一来分析,其实事实是这样的:我们本应该用GB18030的编码来读取数据并解码成字符串,但结果却采用了ISO-8859-1的编码,导致生成一个错误的字符串。要恢复,就要先把字符串恢复成原始字节数组,然后通过正确的编码GB18030再次解码成字符串(即把以GB18030编码的数据转成unicode的字符串)。注意,字符串永远都是unicode编码的。
但编码转换并不是负负得正那么简单,这里我们之所以可以正确地转换回来,是因为 ISO8859-1 是单字节编码,所以每个字节被按照原样 转换为String ,也就是说,虽然这是一个错误的转换,但编码没有改变,所以我们仍然有机会把编码转换回来!

ANSIBUG

很多细心的人会发现,当新建文本文档只输入“联通”2字保存再打开时将是乱码

txt文档中一切字符都在 C0AA第一个字节)≤DF 80BB第二个字节)≤BF 这个范围时,notepad都无法确认文档地格式,没有自动依照UTF-8格式来"Display"。 而"联通"就是C1 AA CD A8,刚好在上面地范围内,所以不能正常显现。

记事本默认是以ANSI编码保存文本文档的,而正是这种编码存在的bug招致了上述怪现象。假如保存时选择UnicodeUnicode(big endian)UTF-8编码就正常了。此外,假如以ANSI编码保存含有某些特别符号的文本文档,再次打开后符号也会变成英文问号。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值