- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章ANSI,Unicode,BMP,UTF等编码概念实例讲解由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
1、前言 。
其实从开始写java代码以来,我遇到过无数次乱码与转码问题,比如从文本文件读入到string出现乱码,servlet中获取http请求参数出现乱码,jdbc查询到的数据乱码等等,这些问题很常见,遇到的时候随手搜一下都可以顺利解决,所以没有深入的去了解.
直到前两天同学与我谈起一个java源文件的编码问题(这问题在最后一个实例分析),从这个问题入手拉扯出了一连串的问题,然后我们一边查资料一边讨论,直到深夜,终于在一篇博客中找到了关键性线索,解决了所有的疑惑,以前没有理解的语句都能解释清楚了。因此我决定用这篇随笔,记录我对一些编码问题的理解以及实验的结果.
下面有些概念是我自己结合实际的理解,如果有误,请一定不吝指正.
2、概念总结 。
早期,互联网还没有发展起来,计算机仅用于处理一些本地的资料,所以很多国家和地区针对本土的语言设计了编码方案,这种与区域相关的编码统称为ansi编码(因为都是对ansi-ascii码的扩展)。但是他们没有事先商量好怎么相互兼容,而是自己搞自己的,这样就埋下了编码冲突的祸根,比如大陆使用的gb2312编码与台湾使用的big5编码就有冲突,同样的两个字节,在两种编码方案里表示的是不同的字符,随着互联网的兴起,一个文档里经常会包含多种语言,计算机在显示的时候就遇到麻烦了,因为它不知道这两个字节到底属于哪种编码.
这样的问题在世界上普遍存在,因此重新定义一个通用的字符集,为世界上所有字符进行统一编号的呼声不断高涨.
由此unicode码应运而生,它为世界上所有字符进行了统一编号,由于它可以唯一标识一个字符,所以字体也只需要针对unicode码进行设计就行了。但unicode标准定义的是一个字符集,而没有规定编码方案,也就是说它仅仅定义了一个个抽象的数字与其对应的字符,而没有规定具体怎么存储一串unicode数字,真正规定怎么存储的是utf-8、utf-16、utf-32等方案,所以带有utf开头的编码,都是可以直接通过计算和unicode数值(codepoint,代码点)进行转换的。顾名思义,utf-8就是8位长度为基本单位编码,它是变长编码,用1~6个字节来编码一个字符(因为受unicode范围的约束,所以实际最大只有4字节);utf-16是16位为基本单位编码,也是变长编码,要么2个字节要么4个字节;utf-32则是定长的,固定4字节存储一个unicode数.
其实我以前一直对unicode有点误解,在我的印象中unicode码最大只能到0xffff,也就是最多只能表示2^16个字符,在仔细看了维基百科之后才明白,早期的ucs-2编码方案确实是这样,ucs-2固定使用两个字节来编码一个字符,因此它只能编码bmp(基本多语言平面,即0x0000-0xffff,包含了世界上最常用的字符)范围内的字符。为了要编码unicode大于0xffff的字符,人们对ucs-2编码进行了拓展,创造了utf-16编码,它是变长的,在bmp范围内,utf-16与ucs-2完全一致,而bmp之外utf-16则使用4个字节来存储.
为了方便下面的描述,先交代一下代码单元(codeunit)的概念,某种编码的基本组成单位就叫代码单元,比如utf-8的代码单元为1个字节,utf-16的代码单元为2个字节,不好解释,但是很好理解.
为了兼容各种语言以及更好的跨平台,javastring保存的就是字符的unicode码。它以前使用的是ucs-2编码方案来存储unicode,后来发现bmp范围内的字符不够用了,但是出于内存消耗和兼容性的考虑,并没有升到ucs-4(即utf-32,固定4字节编码),而是采用了上面所说的utf-16,char类型可看作其代码单元。这个做法导致了一些麻烦,如果所有字符都在bmp范围内还没事,若有bmp外的字符,就不再是一个代码单元对应一个字符了,length方法返回的是代码单元的个数,而不是字符的个数,charat方法返回的自然也是一个代码单元而不是一个字符,遍历起来也变得麻烦,虽然提供了一些新的操作方法,总归还是不方便,而且还不能随机访问.
此外,我发现java在编译的时候还不会处理大于0xffff的unicode字面量,所以如果你敲不出某个非bmp字符来,但是你知道它的unicode码,得用一个比较笨的方法来让string存储它:手动计算出该字符的utf-16编码(四字节),把前两个字节和后两个字节各作为一个unicode数,然后赋值给string,示例代码如下所示.
1
2
3
4
5
6
7
8
9
10
|
public
static
void
main(string[] args) {
//string str = ""; //我们想赋值这样一个字符,假设我输入法打不出来
//但我知道它的unicode是0x1d11e
//string str = "\u1d11e"; //这样写不会识别
//于是通过计算得到其utf-16编码 d834 dd1e
string str =
"\ud834\udd1e"
;
//然后这么写
system.out.println(str);
//成功输出了""
}
|
windows系统自带的记事本可以另存为unicode编码,实际上指的是utf-16编码。上面说了,主要使用的字符编码都在bmp范围内,而在bmp范围内,每个字符的utf-16编码值与对应的unicode数值是相等的,这大概就是微软把它称为unicode的原因吧。举个例子,我在记事本中输入了”好a“两个字符,然后另存为unicode big endian(高位优先)编码,用winhex打开文件,内容如下图,文件开头两个字节被称为byte order mark(字节顺序标记),(fe ff)标识字节序为高位优先,然后(59 7d)正是”好“的unicode码,(00 61)正是”a“的unicode码.
有了unicode码,也还不能立即解决问题,因为首先世界上已经存在了大量的非unicode标准的编码数据,我们不可能丢弃它们,其次unicode的编码往往比ansi编码更占空间,所以从节约资源的角度来说,ansi编码还是有存在的必要的。所以需要建立一个转换机制,使得ansi编码可以转换到unicode进行统一处理,也可以把unicode转换到ansi编码以适应平台的要求.
转换方法说起来比较容易,对于utf系列或者是iso-8859-1这种被兼容的编码,可以通过计算和unicode数值直接进行转换(实际可能也是查表),而对于系统遗留下来的ansi编码,则只能通过查表的方式进行,微软把这种映射表称为codepage(代码页),并按编码进行分类编号,比如我们常见的cp936就是gbk的代码页,cp65001就是utf-8的代码页。下图是微软官网查到的gbk->unicode映射表(目测不全),同理还应有反向的unicode->gbk映射表.
有了代码页,就可以很方便的进行各种编码转换了,比如从gbk转换到utf-8,只需要先按照gbk的编码规则对数据按字符划分,用每个字符的编码数据去查gbk代码页,得到其unicode数值,再用该unicode去查utf-8的代码页(或直接计算),就可以得到对应的utf-8编码。反过来同理。注意:utf-8是unicode的标准实现,它的代码页中包含了所有的unicode取值,所以任意编码转换到utf-8,再转换回去都不会有任何丢失。至此,我们可以得出一个结论就是,要完成编码转换工作,最重要的是第一步要成功的转换到unicode,所以正确选择字符集(代码页)是关键.
理解了转码丢失问题的本质后,我才突然明白jsp的框架为什么要以iso-8859-1去解码http请求参数,导致我们获取中文参数的时候不得不写这样的语句:
stringparam=newstring(s.getbytes("iso-8859-1"),"utf-8"),
因为jsp框架接收到的是参数编码的二进制字节流,它不知道这究竟是什么编码(或者不关心),也就不知道该查哪个代码页去转换到unicode。然后它就选择了一种绝对不会产生丢失的方案,它假设这是iso-8859-1编码的数据,然后查iso-8859-1的代码页,得到unicode序列,因为iso-8859-1是按字节编码的,而且不同于ascii的是,它对0~255空间的每一位都进行了编码,所以任意一个字节都能在它的代码页中找到对应的unicode,若再从unicode转回原始字节流的话也就不会有任何丢失。它这样做,对于不考虑其他语言的欧美程序员来说,可以直接用jsp框架解码好的string,而要兼容其他语言的话也只需要转回原始字节流,再以实际的代码页去解码一下就好.
我对unicode以及字符编码的相关概念阐述完毕,接下来用java实例来感受一下.
3、实例分析 。
1.转换到unicode——string构造方法 。
string的构造方法就是把各种编码数据转换到unicode序列(以utf-16编码存储),下面这段测试代码,用来展示javastring构造方法的应用,实例中都不涉及非bmp字符,所以就不用codepointat那些方法了.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public
class
test {
public
static
void
main(string[] args)
throws
ioexception {
//"你好"的gbk编码数据
byte
[] gbkdata = {(
byte
)
0xc4
, (
byte
)
0xe3
, (
byte
)
0xba
, (
byte
)
0xc3
}
;
//"你好"的big5编码数据
byte
[] big5data = {(
byte
)
0xa7
, (
byte
)
0x41
, (
byte
)
0xa6
, (
byte
)
0x6e
}
;
//构造string,解码为unicode
string strfromgbk =
new
string(gbkdata,
"gbk"
);
string strfrombig5 =
new
string(big5data,
"big5"
);
//分别输出unicode序列
showunicode(strfromgbk);
showunicode(strfrombig5);
}
public
static
void
showunicode(string str) {
for
(
int
i =
0
; i < str.length(); i++) {
system.out.printf(
"\\u%x"
, (
int
)str.charat(i));
}
system.out.println();
}
}
|
运行结果如下图 。
可以发现,由于string掌握了unicode码,要转换到其它编码soeasy! 。
3.以unicode为桥梁,实现编码互转 。
有了上面两部分的基础,要实现编码互转就很简单了,只需要把他们联合使用就可以了。先newstring把原编码数据转换为unicode序列,再调用getbytes转到指定的编码就ok.
比如一个很简单的gbk到big5的转换代码如下 。
1
2
3
4
5
6
7
8
9
10
11
|
public
static
void
main(string[] args)
throws
unsupportedencodingexception {
//假设这是以字节流方式从文件中读取到的数据(gbk编码)
byte
[] gbkdata = {(
byte
)
0xc4
, (
byte
)
0xe3
, (
byte
)
0xba
, (
byte
)
0xc3
}
;
//转换到unicode
string tmp =
new
string(gbkdata,
"gbk"
);
//从unicode转换到big5编码
byte
[] big5data = tmp.getbytes(
"big5"
);
//后续操作……
}
|
4.编码丢失问题 。
上面已经解释了,jsp框架采用iso-8859-1字符集来解码的原因。先用一个例子来模拟这个还原过程,代码如下 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public
class
test {
public
static
void
main(string[] args)
throws
unsupportedencodingexception {
//jsp框架收到6个字节的数据
byte
[] data = {(
byte
)
0xe4
, (
byte
)
0xbd
, (
byte
)
0xa0
, (
byte
)
0xe5
, (
byte
)
0xa5
, (
byte
)
0xbd
}
;
//打印原始数据
showbytes(data);
//jsp框架假设它是iso-8859-1的编码,生成一个string对象
string tmp =
new
string(data,
"iso-8859-1"
);
//**************jsp框架部分结束********************
//开发者拿到后打印它发现是6个欧洲字符,而不是预期的"你好"
system.out.println(
" iso解码的结果:"
+ tmp);
//因此首先要得到原始的6个字节的数据(反查iso-8859-1的代码页)
byte
[] utfdata = tmp.getbytes(
"iso-8859-1"
);
//打印还原的数据
showbytes(utfdata);
//开发者知道它是utf-8编码的,因此用utf-8的代码页,重新构造string对象
string result =
new
string(utfdata,
"utf-8"
);
//再打印,正确了!
system.out.println(
" utf-8解码的结果:"
+ result);
}
public
static
void
showbytes(
byte
[] data) {
for
(
byte
b : data)
system.out.printf(
"0x%x "
, b);
system.out.println();
}
}
|
运行结果如下,第一次输出是不正确的,因为解码规则不对,也查错了代码页,得到的是错误的unicode。然后发现通过错误的unicode反查iso-8859-1代码页还能完美的还原数据.
这不是重点,重点如果把“中”换成“中国”,编译就会成功,运行结果如下图。另外进一步可发现,中文字符个数为奇数时编译失败,偶数时通过。这是为什么呢?下面详细分析一下.
因为javastring内部使用的是unicode,所以在编译的时候,编译器就会对我们的字符串字面量进行转码,从源文件的编码转换到unicode(维基百科说用的是与utf-8稍微有点不同的编码)。编译的时候我们没有指定encoding参数,所以编译器会默认以gbk方式去解码,对utf-8和gbk有点了解的应该会知道,一般一个中文字符使用utf-8编码需要3个字节,而gbk只需要2个字节,这就能解释为什么字符数的奇偶性会影响结果,因为如果2个字符,utf-8编码占6个字节,以gbk方式来解码恰好能解码为3个字符,而如果是1个字符,就会多出一个无法映射的字节,就是图中问号的地方.
再具体一点的话,源文件中“中国”二字的utf-8编码是e4b8ade59bbd,编译器以gbk方式解码,3个字节对分别查cp936得到3个unicode值,分别是6d93e15e6d57,对应结果图中的三个奇怪字符。如下图所示,编译后这3个unicode在.class文件中实际以类utf-8编码存储,运行的时候,jvm中存储的就是unicode,然而最终输出时,还是会编码之后传递给终端,这次约定的编码就是系统区域设置的编码,所以如果终端编码设置改了,还是会乱码。我们这里的e15e在unicode标准中并没有定义相应的字符,所以在不同平台不同字体下显示会有所不同.
可以想象,如果反过来,源文件以gbk编码存储,然后骗编译器说是utf-8,那基本上是无论输入多少个中文字符都无法编译通过了,因为utf-8的编码很有规律性,随意组合的字节是不会符合utf-8编码规则的.
当然,要使编译器能正确的把编码转换到unicode,最直接的方法还是老老实实告诉编译器源文件的编码是什么.
4、总结 。
经过这次收集整理和实验,了解了很多与编码相关的概念,也熟悉了编码转换的具体过程,这些思想可以推广到各种编程语言去,实现原理都类似,所以我想以后再遇到这类问题,应该不会再不知所以然了.
以上就是本文关于ansi,unicode,bmp,utf等编码概念实例讲解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持! 。
原文链接:http://www.cnblogs.com/coderjun/p/5117590.html 。
最后此篇关于ANSI,Unicode,BMP,UTF等编码概念实例讲解的文章就讲到这里了,如果你想了解更多关于ANSI,Unicode,BMP,UTF等编码概念实例讲解的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
UTF-8、UTF-16 和 UTF-32 之间有何区别? 据我所知,它们都将存储 Unicode,并且每个都使用不同数量的字节来表示字符。选择其中之一是否有优势? 最佳答案 当 ASCII 字符代表
好的。我知道这看起来像典型的“他为什么不直接用谷歌搜索或去 www.unicode.org 查一下?”问题,但对于这样一个简单的问题,在检查了两个来源后,我仍然无法回答。 我很确定这三种编码系统都支持
是否存在可以用 UTF-16 编码但不能用 UTF-8 编码的字符 最佳答案 没有。 UTF-* 是可以对全范围 Unicode 字符进行编码的编码。 编码之间的差异在于每个字符使用多少字节。 关于u
是否存在可以用 UTF-16 编码但不能用 UTF-8 编码的字符 最佳答案 没有。 UTF-* 是可以对全范围 Unicode 字符进行编码的编码。 编码之间的差异在于每个字符使用多少字节。 关于u
UTF-16 是一种双字节字符编码。交换两个字节的地址将产生 UTF-16BE 和 UTF-16LE。 但我发现在 Ubuntu gedit 文本编辑器中存在名称 UTF-16 编码,以及 UTF-1
我想将 UTF-16 字符串转换为 UTF-8。我通过 Unicode 发现了 ICU 库。我在转换时遇到问题,因为默认设置是 UTF-16。我试过使用转换器: UErrorCode myError
UTF-16 需要 2 个字节,UTF-8 需要 1 个字节。 而USB是面向8bit的,UTF-8更自然。 UTF-8 向后兼容 ASCII,而 UTF-16 则不然。 UTF-16 需要 2 个字
我对将 unicode 字符转换为十六进制值有点困惑。 我正在使用这个网站获取字符的十六进制值。 ( https://www.branah.com/unicode-converter ) 如果我输入“
我已经用UTF-8编码创建了一个文件,但是我不了解其在磁盘上占用的大小的规则。这是我的完整研究: 首先,我创建了一个带有印地语字母“'”的文件,Windows 7上的文件大小为 8个字节。 现在带有两
如何将WideString(或其他长字符串)转换为UTF-8中的字节数组? 最佳答案 这样的功能将满足您的需求: function UTF8Bytes(const s: UTF8String): TB
我有一个奇怪的验证程序,用于验证utf-8字符串是否是有效的主机名(PHP中的Zend Framework主机名valdiator)。它允许IDN(国际化域名)。它将比较每个子域与由其十六进制字节表示
在 utf16 和 utf32 中,一个字节的零是否意味着空?就像在 utf8 中一样,还是我们需要 2 个和 4 个字节的零来相应地在 utf16 和 utf32 中创建 null? 最佳答案 在
这是基于我的观察,对于 mysql,默认字符集 utf8 有点误导,它不支持完整的 Unicode,因为它无法存储四字节 UTF-8 编码的字符。它实际上是 utf8mb4 字符集,它是完整的 Uni
我只有处理 ASCII(单字节字符)的经验,并且阅读了很多关于人们如何以不同方式处理 Unicode 的帖子,这些帖子提出了他们自己的一系列问题。 此时我对 Unicode 的了解非常有限,我读到过U
我明白 std::codecvt在 C++11 中执行 UTF-16 和 UTF-8 之间的转换,并且 std::codecvt执行 UTF-32 和 UTF-8 之间的转换。是否可以在 UTF-8
我正在编写一个 HTTP 服务器并使用 trivial-utf-8:write-utf-8-bytes 来响应请求。我听说Babel就像trivial-utf-8但效率更高,所以我想试一试。搜索了一段
我正在设计一个新的 CMS,但想要设计它来满足我 future 的所有需求,比如多语言内容,所以我认为 Unicode (UTF-8) 是最好的解决方案 但是通过一些搜索我得到了这篇文章 http:/
例如,假设我在字符串中有以下 xml: 如果我尝试将其插入到带有 Xml 列的 SQL Server 2005 数据库表中,我将收到以下错误(我使用的是 EF 4.1,但我认为这无关紧要): XM
我正在使用 Python CSV 库读取两个 CSV 文件。 一种使用 UTF-8-BOM 编码,另一种使用 UTF-8 编码。在我的实践中,我发现使用“utf-8-sig”作为编码类型可以读取这两个
假设我的数据库设置如下以使用 utf-8(mysql 中的完整 4mb 版本) mysql_query("SET CHARACTER SET utf8mb4"); mysql_query("SET N
我是一名优秀的程序员,十分优秀!