中文编码(java c++)

中文编码


Document Information:

Owner:Li.zhang
Date:2008.02.05
Version:0.01

Version History:

VersionDate AuthorSummary of ChangesDetails of Changes
0.012008-02-05Li.zhang草稿 
     

 

 

  • 1 The History of Character Coding

  1. ASCII(American Standard Code for Information Interchange)

早期计算机单字节编码。

0x0-0x20 (包含0x7F):控制字符,如0x0 NUL,0xA LF(line feed), 0x7F del

0x21-0x7e : 通用英文字符,数字,运算符,标点符号等。

0x80-0xff : 扩展字符。

1
  • 测试:

  • //Test.c

  • int main( )

  • {

  • printf(“%c %c”, 127, 129);

  • return 0;

  • }

  • 结果:

  • 您的浏览器可能不支持显示此图像。

  • 1



  1. ANSI(American National Standards Institute)
  • 由于计算机的普及,不同的国家地区由于语言的需要,指定了不同的适应本国和地区的编码标准如GB2312,JIS等。ANSI是扩展ASCII编码,由两个字节来表示ASCII没有涵盖的本地文字编码。由于ASCII编码是0x00-0x7F,ANSI编码利用0x80-0xFF做为扩展编码的标示,详细解释参见后文。

GB2312又称为GB2312-80字符集,全称为《信息交换用汉字编码字符集·基本集》,由原中国国家标准总局发布,1981年5月1日实施,是中国 国家标准的简体中文字符集。

GB2312收录简化汉字及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。其中包括6763个汉字,其中一级汉字3755个,二级汉字3008个;包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符.

GB2312是中国大陆普及采用的ANSI编码,现在已被GBK代替,GBK兼容GB2312,并提供GB2312中没有囊括的汉字编码。GB18030又 囊括了GBK.

GB2312 (GB2312-1980) 的编码:

* 单字节:第一字节 0-----0x7F (0-127)
* 双字节:第一字节 0xB0--0xF7 (176--247) 第二节 0xA1--0xFE(161--254)

GBK (GB13000/GB12345) 的编码:
* 单字节:第一字节 0-----0x7F (0-127)
* 双字节:第一字节 0x81--0xFE (129--254) ,第二节 0x40--0xFE(64--254)

2.3 GB18030 的编码:
* 单字节:第一字节 0--0x7F (0-127)
* 双字节:第一字节 0x81--0xFE (129--254) ,第二节 0x40--0xFE(64--254)
* 四字节:第一字节 0x81--0xFE (129--254) ,第二节 0x30--0x39(48--57)
第三字节 0x81--0xFE (129--254) ,第四节 0x30--0x39(48--57)

2

Hello,world: 其内存标示:

  • //Test.c

int main()

{

char* s = "Hello,world";

int I = 0;

while( s[i] != 0)

{

printf("%-4c ", s[i++]);

}

printf("/n");

i=0;

while( s[i] != 0)

{

printf("%#x ", s[i++]);

}

}

结果:

您的浏览器可能不支持显示此图像。

2

Test中程序从源码中读取Hello,world,并将其按照ASCII编码存储于内存。


3

”Hello,中文,其内存标示:

//Test.c

int main()

{

char* s = "Hello,中文";

char temp[3];

unsigned short *p;

int i = 0;

int j = 0;

int k = 0;

for(i = 0; i < 6; i++)

{

printf("%-4c ",s[i]);

}

printf("/n");

for(i = 0; i < 6; i++)

{

printf("%#x ",s[i]);

}

printf("/n");

for(i = 6; i < 10; i += 2)

{

temp[0] = s[i];

temp[1] = s[i+1];

temp[2] = 0;

j = s[i];

j = j&0x00ff;

k = s[i+1];

k = k&0x00ff;

printf("%s(%#x %#x) ",temp, j, k);

}

printf("/n");

for(i = 6; i < 10; i += 2)

{

p = &s[i];

printf("%-#13x ", *p);

}

printf("/n");

}

  • 结果:

  • 您的浏览器可能不支持显示此图像。

  • 3

首先分析一下结果:“Hello,”是按照ASCII编码,如H,ASCII编码为0x48.但中文却是一个字符占两个字节,“中”在GBKGB2312中对应的编码为0xd6d0,其在程序结果对应的内存值为0xd6 0xd0.

GB2312用双字节标示一个汉字,在windows内存存储和文件存储中,第一个字节存放高字节,第二个字节存放低字节(big endian),如“中”,他在内存中存放两字节,第一字节存放0xd6,第二个字节存放0xd0。在GB2312中原始兼容的ASCII编码仍然为1字节,但对于汉字为两个字节。

如”Hello,中文”,其编码为:

H e l l o , 中 文

0x 48 65 6c 6c 6f 2c d6d0 cec4

文字编辑器是如何存储这些文字的呢?

  • 打开记事本(选择开始菜单中运行,输入cmd,进入命令行,运行notepadtest,c命令),将test.c 复制到记事本中,保存(ctrl+s)。用二进制编辑器(如:UltraEdit)打开,”Hello,中文”在源代码文件中的格式如下:

您的浏览器可能不支持显示此图像。

  • 图 4

  • 在test.c的源码中,汉字”中文”编码为”D6D0CEC4”,但其他通用的英文字符符合ASCII编码,如int -> 696e74.

  • 记事本是如何存储这些源码呢?

  • 用记事本打开test.c.打开记事本菜单中“另存为“选项如下:

您的浏览器可能不支持显示此图像。

5

可以看到编码选项为ANSI,而在中文WINDOWS XP操作系统中ANSI编码为GBK或者GB2312(现在基本为GBKwindows95/98GBK为基本汉字编码)。当默认保存test.c时候,采取的就是ANSI默认编码GBK。所以其存储格式如图4所示。

如何识别GBK(GB2312)编码的呢?

仅以GBK为例,GBK编码表可以参照http://users.ir-lab.org/~taozi/GBK1.txt

GBK用双字节编码,两字节中前面字节为第一字节或高字节,后面为第二字节或低字节,高位字节值在0X81-0XFE之间,低位字节在0X40-0XFE之间(除去7F,7FGBK编码中有特殊用处)GB2312,GBK都是双字节字符集(DBCS)。DBCS中内码格式始终为big-endian(高位在前).

  • 由于ASCII基本编码值在0x00-0x7f之间,GB编码字节高位都是1,所以windows在解析DBCS字符流时,当遇到一个字节的高位为1时,就将这个字节和下一个字节作为一个双字节编码来解析

例如: 英文+汉字 --- ”Hello,中文”,

您的浏览器可能不支持显示此图像。

6,文本编辑器显示

您的浏览器可能不支持显示此图像。

7 ,二进制显示

  • Notepad(记事本)采用默认的ANSI编码,对于”H”,其ASCII编码为0x48,由于其值在0x00-0x7f之间,所以将其存储为0x48,同理”ello,”,由于中文采用GBK编码0xd6d0 oxcec4,由于” 0xd6d0,由于其第一个字节为d6 > 0x7f,所以这个字节提示这个编码不是ASCII基本编码,为扩展编码,由于GBK采用双字节存储扩展编码,所以0xd6提示这个character是一个双字节编码,必须如后一个字节连同一起解释,如是这个character就联合0xd6 0xd0,组合成0xd6d0,0xd6d0对于GBK编码表为”:

您的浏览器可能不支持显示此图像。

同理。


  1. Unicode
  • Unicode不与ANSI编码如GBK,GB2312兼容,只与ASCII兼容.

  • Unicode编码方式与UCS ( Universal Character Set)概念相对应,目前实际应用的Unicode版本对应于UCS-2,使用16bit的编码空间,及两个字节存储一个Character,可以标示65536Character

  • 如果一个仅包含ASCII CharUnicode文件,如果每个字节都用双字节存储,则char的第一字节始终为0,造成空间浪费。鉴于这种情况,可以使用UTF-8编码,UTF-8(UTF: Unicode Translation Format)是变长编码,UTF-8使用可变长度字节来储存 Unicode字符,例如ASCII字母继续使用1字节储存,重音文字、希腊字母或西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节.ASCII Char1-3个字节编码,利用每个字节的高位0/1来辨别。而UTF-16俱采用2字节标示一个char,java默认采用UTF-16编码。

  • 标准UnicodeUTF-16.Unicode字符在网络中传输时,由于UTF-8采用字节编码,所以没有字节序的问题,UTF-16以两个字节为一个编码单元,所以字节序很重要,如0X4E4F“乏”,0X4F4E “低”,如果程序探测到一个字节流0x4e4f,究竟这个字是“低”还是“乏”呢?所以Unicode提供了一种标定字节顺序的方法 BOM(Byte Order Mark),由于UCS编码规范中规定一个"ZERO WIDTH NO-BREAK SPACE"的字符(0xFEFF),由于0Xfffe在UCS中是不存在的字符,所以在传输字节流的时候可以先传输” ZERO WIDTH NO-BREAK SPACE”,如果收到0xfeff则这个字节流为big-endian,如果为0xfffe则为little-endian. UTF -8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF。所以如果收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

实验:

打开开始菜单 –>所有程序->附件->系统工具->字符映射表.

选择一个字体宋体,并选择高级查看,选择Unicode。选择汉字“中”,如图,可见Unicode值为0X4E2D,查询Unicode编码表如图可见为0x4e2d.

您的浏览器可能不支持显示此图像。

您的浏览器可能不支持显示此图像。

在字符映射表中选择”windows: 中文(简体)“,选择汉字“中”:由图可见汉字“中”在windows:中文(简体)(GBK)中为0XD6D0,Unicode中为0x4e2d.

您的浏览器可能不支持显示此图像。

“U+4E2D” Unicode编码,D6D0 GBK 编码



实验:

Notepad++ 新建一个文本文件:

输入”abc中文變”:

您的浏览器可能不支持显示此图像。

查看格式选项:

您的浏览器可能不支持显示此图像。

可见其默认是以ANSI编码。二进制打开,可见其为GB编码:

GBK编码: ->0xd6d0 , ->0xcec4, -> 0xd783

您的浏览器可能不支持显示此图像。

若在格式中选择”UTF-8 BOM格式编码查看:

您的浏览器可能不支持显示此图像。

此时会出现乱码D6D0CEC4,而这正是“中文”的GB编码,由于D6D0UTF-8中编码不存在。但”,其编码为0Xd783,0xd783UTF-8中编码对应于”:”, 您的浏览器可能不支持显示此图像。

您的浏览器可能不支持显示此图像。

所以其显示如图所示。如果在”UTF-8 BOM格式编码下输入中文”,

您的浏览器可能不支持显示此图像。

其二进制为:

您的浏览器可能不支持显示此图像。

中文UTF-8下编码: -> 0XE4B8AD ->E69687,所以而进制如图所示。并在UTF-8格式下能够查看,并正确显示“中文”。

若在格式中选择”UTF-8 格式编码查看并存储:

您的浏览器可能不支持显示此图像。

其二进制格式为:

您的浏览器可能不支持显示此图像。

其相对于UTF-8 BOM格式编码,在其开始处多了“0XEFBBBF”,就是上文所说的BOM,这是由于这种编码采用BOM标示为UTF-8格式。

也可以用windows记事本来建立UTF-8文件:

cmd->noepad->

输入“abc-中文”,选择“另存为”编码“UTF-8”:

您的浏览器可能不支持显示此图像。

再用二进制打开:

您的浏览器可能不支持显示此图像。

开始EFBBBF如上文所述为”ZERO WIDTH NO-BREAK SPACE”,标示文件采用UTF-8编码,”abc”对应ASCII编码”0x616263”,而”中文”对应”0xe4b8ade69687”,在UTF-8中每个汉字是一个3字节编码。

若在格式中选择UCS-2 Big Endian格式编码查看并存储:

您的浏览器可能不支持显示此图像。

其二进制:

您的浏览器可能不支持显示此图像。

“0Xfeff”如上文所说标示UTF-16 big-endian编码,a-> 0x6100,b->0x6200, c->0x6300

-> 0x2d4e,->0x8765

若在格式中选择UCS-2 little Endian格式编码查看并存储:

您的浏览器可能不支持显示此图像。

其二进制:

您的浏览器可能不支持显示此图像。

其编码:“0xfffe”提示这是little-endian UTF-16编码,其后0x0061->a , 0x4e2d->中。


当在文本编辑器中如notepad

写入“中文”,然后存储为Unicode编码,是如何实现转换的呢?

您的浏览器可能不支持显示此图像。

在非 Unicode环境中,由于不同国家和地区采用的字符集不一致,很肯呢个无法正常显示文字,微软公司采用了code page 转换表的技术暂时解决这个问题,及通过转换表将非Unicodechar编码转换为同以字对应的windows系统内部使用的Unicode编码,由于字window2000以来,windows内码使用unicode.可以在控制面板“语言与区域设置”中高级选项选择代码也转换表。选择不同的语言从而选择不同的代码转换表集合。如选择中文,其中在代码也转换表中囊括GBK(code page 936),GB2312(code page 20936)等。

您的浏览器可能不支持显示此图像。

Windows就是用代码页来适应各种语言的。指定了哪种代码页,windows和程序就用哪中编码来解释遇见的的字节流。在c/c++中可以通过setlocal这个函数来实现指定代码页。如setlocale(LC_ALL, ".936");就是用code page 936来解释程序中的字节流,code page 936对应GBK.

  • 2 Unicode Java

JVM中字符都是UnicodeUTF-16,所以String中数据都是Unicode编码的,由于Unicode统一编码,所以String无差别,但由String转化成的byte[]数组确实带编码的。一个GBK编码的byte[]转换成String,其实就是GBK编码转化Unicode编码。一个String转换成一个GBK编码的byte[],就是从Unicode编码转化GBK编码。

那么java程序开发中,java是如何编码解码的呢?
  1. 编辑源代码,如notepad中书写源码。此时源码保存时采用了操作系统默认的ANSI编码格式,在中文Windows XP中几乎为GBK.如打开notepad,编辑源码:
  • 您的浏览器可能不支持显示此图像。

  • 其二进制:

您的浏览器可能不支持显示此图像。

  • 可见其存储采用GB编码(GBK | GB2312),所以java程序在被编译前采用系统默认的file.encoding编码。.可以通过System.getProperty("file.encoding")得到系统默认的file.encoding.

  1. 编译源码。调用javac编译文件,生成class文件。在编译的时候如果没有用-encoding参数指定java源程序的编码格式,则javac采用操作系统默认的编码格式来解读源码,并将源码转化成java默认的UnicodeUTF-8)格式,然后编译成Unicodeclass文件。Class二进制如下:

您的浏览器可能不支持显示此图像。

可见“中文“采用UTF-8格式编码 “中” -> 0xe4b8ad “”->e69687,已从源码中的d6d0cec4 –转换成UTF-8格式。如果在非GBK默认file.encoding中编译GB K源码呢。仅以Cp1252为例(Cp1252 ISO 8859-1,拉丁字母 1 号,编码0x00-0xff, 最简单的编码规则,每一个字节直接作为一个 UNICODE 字符。比如,[0xD6, 0xD0] 这两个字节,通过 iso-8859-1 转化为字符串时,将直接得到 [0x00D6, 0x00D0] 两个 UNICODE 字符,即 "ÖÐ"。

您的浏览器可能不支持显示此图像。

如果源码是GBK的,在Cp1252下编译运行:

您的浏览器可能不支持显示此图像。

生成的class文件:

您的浏览器可能不支持显示此图像。

为什么此时的“中文“编码变为0x c3 96 c3 90 c3 8e c3 84 呢?

由于此时系统默认的file.encodingCp1252,Cp1252编码转化成Unicode编码时,仅是添加一个0字节,中文0xd6d0 ce c4- >会变成Unicode编码 0x00d6 0x00d0 0x00ce 0x 00c4.在将这个UnicodeUTF-16)编码转化成UTF-8编码。

:

UTF-16 转化 UTF-8 规则:

您的浏览器可能不支持显示此图像。

所以 0x 00d6

二进制-> 1101-0110

UTF-8: 1100-0011 1001-0110

UTF-8 0X-> c3 96

同理 0x00d0 0x00ce 0xc4

二进制-> 1101-0000 1100-1110 1100-0100

UTF-8: 1100-0011 1001-0000 1100-0011 1000-1110 1100-0011 1000-0100

UTF-8 0X-> c3 90 c3 8e c3 84

所以class结果完全正确,他按照cp1252读取源码并转化成Unicode-,并最后生成class文件中的UTF-8.

  1. 运行class文件,(java Test);
  • JVM读取class文件,并将相应的UTF-8编码重新解析为Unicode编码。当把这些字符输出的时候,JVM又按照默认的file.encoding转化成Unicode编码,并传递给操作系统并显示。

假设某操作系统默认编码为GBK,源码以GBK编码,并含有“中的字符,程序运行并打印

源码 javac class Java console

0xd6d0 0x4e2d 0xe4b8ad 0x4e2d 0xd6d0

因为系统默认编码为GBK,所以javac编译源码时能正确的将0xd6d0转化成相应的Unicode 0x4e2d ,并正确的转化成UTF-8 0XE4B8AD. 运行java程序,javaUTF-8正确的解析为0x4e2d,打印时java有正确的将unicode解析为GBK,并正确打印。

假设某操作系统默认编码为Cp1252,源码以GBK编码,并含有“中的字符,程序运行并打印

源码 javac class Java console

d6d0 00d6 00d0 c396 c390 00d6 00d0 d6d0

因为系统默认编码为GBK,所以javac编译源码时将0xd6d0转化成相应的Unicode 0x00d6 0x00d0 ,并转化成class: UTF -8 c396 c390,最后运行java,java load class解析成Unicode 0x00d6 0x00d0,打印时Unicode又按照Cp1252转化成d6d0.


实验:

//Test.java

public class Test {

public static void main(String[] args)

{

System.out.println(System.getProperty("file.encoding"));

String str = "中文";

for(int i = 0; i < str.length(); i++)

{

System.out.printf("%d: %x /n",i,str.codePointAt(i));

}

System.out.println("*************");

byte[] b = str.getBytes();

for(int i = 0; i < b.length; i++)

{

System.out.printf("%d: %x /n",i,b[i]);

}

}

}

结果:

您的浏览器可能不支持显示此图像。

可见在内存中是按照Unicode(UTF-16)存储的,中->0x4e2d.但调用str.getBytes(),java会将Unicode解析成系统默认的file.encoding GBK编码,所以见到编码 0xd6d0.当然可以调用str.getBytes(charset)得到任意编码形式的“中文”byte[],str.getBytes(“Cp1252”);

public class Test {

public static void main(String[] args)

{

try

{

System.out.println(System.getProperty("file.encoding"));

String str = "中文";

for(int i = 0; i < str.length(); i++)

{

System.out.printf("%d: %x /n",i,str.codePointAt(i));

}

System.out.println("*************");

byte[] b = str.getBytes("Cp1252");

for(int i = 0; i < b.length; i++)

{

System.out.printf("%d: %x /n",i,b[i]);

}

}catch(Exception e)

{

e.printStackTrace();

}

}

}

但其结果:

您的浏览器可能不支持显示此图像。

得到的bytes3f,3f3f对应字符“?,这是由于4e2d,是汉字编码,java认为从Unicode转化成CP1252,CP1252中没有正确的对应字符,所以反馈为”?”.

也可以设置getBytesCharset ->BIG5,(台湾等地编码)

您的浏览器可能不支持显示此图像。

解析为big5中字符,big5a4a4也对应“中”而a4a5->对应“丰”

您的浏览器可能不支持显示此图像。



Java中编码注意的地方主要在数据库,文件,网络,JVM之间交流数据时产生问题。但解决这个问题最关键一点:

如果java得到是一个字节流,他会用默认的file.encoding来解析这个字节流,并将这个字节流转化成Unicode。当JAVA解析一个Unicodebytes,他也用默认的file.encoding解析。记住这一点就没有问题了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值