2006-10-31
字符串编码(charset,encoding/decoding)问题原理
字符串编码(charset, encoding/decoding)问题原理
编码问题很重要,关于编码问题的文章也很多。我本来没有兴趣重复这个主题。
一个朋友问我有没有比较好的编码介绍文章。我记得以前看过几篇很不错的,但是当时搜索到的时候,就比较费劲,一时想不起来。于是,我就说,我攒一篇好了。
---------------------------------------
编码无处不在。Database, file, editor, IDE, compiler, browser。
代码(比如java, jsp, asp, php, python, ruby etc)里面的字符串比较麻烦,涉及到editor, compiler, interpreter等等。
所以,我的做法是,从来不在代码里面直接写字符串资源,尤其是双字节编码的字符串资源。
都是把字符串资源分离到一个单独的资源文件里面。这样,只需要照管这个文件的编码就够了。
需要注意的一点是,文件里面、数据库里面、网络传输需要的数据,都是byte[]。
以下的讨论,不涉及代码里面的字符串编码问题。只讨论系统运行起来之后,各部分之间的编码问题。
先说Java。
JVM里面的任何字符串资源都是Unicode,就是说,任何String类型的数据都是Unicode编码。没有例外。既然只有一种编码,那么,我们可以这么说,JVM里面的String是不带编码的。String相当于 char[]。
JVM里面的 byte[] 数据是带编码的。比如,Big5,GBK,GB2312,UTF-8之类的。
一个GBK编码的byte[] 转换成 String,其实就是从GBK编码向Unicode编码转换。
一个String转换成一个Big5编码的byte[],其实就是从Unicode编码向Big5编码转换。
所以,Unicode是所有编码转换的中间介质。所有的编码都有一个转换器可以转换到Unicode,而Unicode也可以转换到其他所有的编码。这样构成了一个总线结构。
比如,如果总共有10种编码,那么只需要 10 + 10 = 20个转换器就够了。如果要是两两直接转换,那么,需要的转换器数量是一个组合数字,需要90个转换器。
一个系统的不同部分,都有自己的编码。比如,数据库,文件,JVM,浏览器这4个部分。
在这些部分之间数据交换的地方,就会出现编码问题。比如,数据库和JVM之间,文件和JVM之间,浏览器和JVM之间。这些问题的原理都是相通的。
编码问题最容易处理的地方是文件和JVM之间。文件IO API带有encoding 参数,请自行查阅。
最不容易出现编码问题的地方是数据库和JVM之间。这应该是数据库JDBC连接的基本功能。本文不专门进行讨论。
最容易出问题的地方是浏览器和服务器JVM之间(其实,代码里面的字符串更容易出问题,不过,我已经事先声明,本文不讨论代码中的字符串编码)。下面主要讨论这块浏览器和服务器JVM之间的编码问题。
我们把浏览器编码叫做 Browser_Charset,把JVM编码叫做JVM_Charset(通常等于服务器系统编码)。
当浏览器的数据过来的时候,是一个带有Browser_Charset的byte[]。
如果用户处理程序需要一个String类型的数据,那么JVM会好心好意地把这个byte[]转换成String。使用的转换器是 JVM_Charset -> Unicode。
注意,如果这个时候,Browser_Charset 和 JVM_Charset并不相等。那么,这个自动转换是错误的。
为了弥补这个错误。我们需要做两步工作。
(1) Unicode -> JVM_Charset,把这个String 转换回到原来的 byte[]。
(2) Browser_Charset -> Unicode,把这个还原的byte[]转换成 String。
这个效果,和直接从HTTP Request取得byte[],然后执行 (2) Browser_Charset -> Unicode 的效果是一样的。
如果在Request里面设置了CharacterEncoding,那么POST Data参数就不需要自己手工转换了,web server的自动转换就是正确的。URL的参数编码还涉及到URL编码,需要考虑的问题多一些,没有这么简单。
JVM把数据发到浏览器的时候。也需要考虑编码问题。可以在Response里面设置。另外,HTML Meta Header里面也可以设置编码,提醒Browser选择正确编码。
有些语言的VM或者解释器的字符串编码可能不同。比如,Ruby。不过,编码转换原理都是一样的。
That is all.
编码问题很重要,关于编码问题的文章也很多。我本来没有兴趣重复这个主题。
一个朋友问我有没有比较好的编码介绍文章。我记得以前看过几篇很不错的,但是当时搜索到的时候,就比较费劲,一时想不起来。于是,我就说,我攒一篇好了。
---------------------------------------
编码无处不在。Database, file, editor, IDE, compiler, browser。
代码(比如java, jsp, asp, php, python, ruby etc)里面的字符串比较麻烦,涉及到editor, compiler, interpreter等等。
所以,我的做法是,从来不在代码里面直接写字符串资源,尤其是双字节编码的字符串资源。
都是把字符串资源分离到一个单独的资源文件里面。这样,只需要照管这个文件的编码就够了。
需要注意的一点是,文件里面、数据库里面、网络传输需要的数据,都是byte[]。
以下的讨论,不涉及代码里面的字符串编码问题。只讨论系统运行起来之后,各部分之间的编码问题。
先说Java。
JVM里面的任何字符串资源都是Unicode,就是说,任何String类型的数据都是Unicode编码。没有例外。既然只有一种编码,那么,我们可以这么说,JVM里面的String是不带编码的。String相当于 char[]。
JVM里面的 byte[] 数据是带编码的。比如,Big5,GBK,GB2312,UTF-8之类的。
一个GBK编码的byte[] 转换成 String,其实就是从GBK编码向Unicode编码转换。
一个String转换成一个Big5编码的byte[],其实就是从Unicode编码向Big5编码转换。
所以,Unicode是所有编码转换的中间介质。所有的编码都有一个转换器可以转换到Unicode,而Unicode也可以转换到其他所有的编码。这样构成了一个总线结构。
比如,如果总共有10种编码,那么只需要 10 + 10 = 20个转换器就够了。如果要是两两直接转换,那么,需要的转换器数量是一个组合数字,需要90个转换器。
一个系统的不同部分,都有自己的编码。比如,数据库,文件,JVM,浏览器这4个部分。
在这些部分之间数据交换的地方,就会出现编码问题。比如,数据库和JVM之间,文件和JVM之间,浏览器和JVM之间。这些问题的原理都是相通的。
编码问题最容易处理的地方是文件和JVM之间。文件IO API带有encoding 参数,请自行查阅。
最不容易出现编码问题的地方是数据库和JVM之间。这应该是数据库JDBC连接的基本功能。本文不专门进行讨论。
最容易出问题的地方是浏览器和服务器JVM之间(其实,代码里面的字符串更容易出问题,不过,我已经事先声明,本文不讨论代码中的字符串编码)。下面主要讨论这块浏览器和服务器JVM之间的编码问题。
我们把浏览器编码叫做 Browser_Charset,把JVM编码叫做JVM_Charset(通常等于服务器系统编码)。
当浏览器的数据过来的时候,是一个带有Browser_Charset的byte[]。
如果用户处理程序需要一个String类型的数据,那么JVM会好心好意地把这个byte[]转换成String。使用的转换器是 JVM_Charset -> Unicode。
注意,如果这个时候,Browser_Charset 和 JVM_Charset并不相等。那么,这个自动转换是错误的。
为了弥补这个错误。我们需要做两步工作。
(1) Unicode -> JVM_Charset,把这个String 转换回到原来的 byte[]。
(2) Browser_Charset -> Unicode,把这个还原的byte[]转换成 String。
这个效果,和直接从HTTP Request取得byte[],然后执行 (2) Browser_Charset -> Unicode 的效果是一样的。
如果在Request里面设置了CharacterEncoding,那么POST Data参数就不需要自己手工转换了,web server的自动转换就是正确的。URL的参数编码还涉及到URL编码,需要考虑的问题多一些,没有这么简单。
JVM把数据发到浏览器的时候。也需要考虑编码问题。可以在Response里面设置。另外,HTML Meta Header里面也可以设置编码,提醒Browser选择正确编码。
有些语言的VM或者解释器的字符串编码可能不同。比如,Ruby。不过,编码转换原理都是一样的。
That is all.
评论
fxsc
2006-11-03
我一般是统一使用UTF-8编码开发,(包括文件,网页,服务器端程序等,除数据库外,因为数据库基本用现成的),UTF-8对所有语言都适用,尤其适合国际化开发,IE支持也很好,因为不管什么语言版本的Windows都肯定支持UTF-8,不用考虑转码问题推荐使用。不过有时UTF-8的传输效率低于GB2312,因为UTF-8对中文字要用3个字节表示。
另外听说UTF-16也就是Unicode的下一代版本是UTF-32,也就是用4字节表示一个符号,可能将真正覆盖全球的所有语种。
另外听说UTF-16也就是Unicode的下一代版本是UTF-32,也就是用4字节表示一个符号,可能将真正覆盖全球的所有语种。
zelsa
2006-11-01
ddd 写道
传说gb2312是gbk的一个子集。
HZ-GB2312(很多地方称HZ编码)和GB2312完全是两个不一样的编码,差别非常大。
huangyiiiiii
2006-11-01
我的理解是这样的:
计算机中所有数据都是以2进制形式存储。而编码是对二进制数据的一种解释方式。
而 unicode 通过定义一种代码点的东西,把每一个字符都映射到一个代码点。unicode 这个词语本身通常也代表一种实现方式,那就是两个字节表示一个字符,utf-8 是 unicode 的另一种实现:ascii字符还是1个字节,而其他国家字符使用三个字节。
另外楼上说的繁简转换问题就不是转换编码能搞定的了,一般通过查表来实现。
计算机中所有数据都是以2进制形式存储。而编码是对二进制数据的一种解释方式。
而 unicode 通过定义一种代码点的东西,把每一个字符都映射到一个代码点。unicode 这个词语本身通常也代表一种实现方式,那就是两个字节表示一个字符,utf-8 是 unicode 的另一种实现:ascii字符还是1个字节,而其他国家字符使用三个字节。
另外楼上说的繁简转换问题就不是转换编码能搞定的了,一般通过查表来实现。
bigpanda
2006-11-01
要想把这些问题彻底搞清楚,手上一定要有个编码表。
Unicode比较好找, 这里都有: http://www.unicode.org/charts/
ISO-8859-1也好找, 好多书上后面都有, 如Java Internationalization, GB_2312就比较难找, 我后来在一本Developing International Software for Windows 95 and Windows NT 里找到了.
GBK和GB_2312的关系我现在还不清楚.
繁体字的编码比较好笑, BIG5还不是官方标准,而是当年某个公司自己定的标准, 他们的产品在市场上占有率太高了,就成了事实标准了。
要是有谁有链接告诉一声。
Unicode比较好找, 这里都有: http://www.unicode.org/charts/
ISO-8859-1也好找, 好多书上后面都有, 如Java Internationalization, GB_2312就比较难找, 我后来在一本Developing International Software for Windows 95 and Windows NT 里找到了.
GBK和GB_2312的关系我现在还不清楚.
繁体字的编码比较好笑, BIG5还不是官方标准,而是当年某个公司自己定的标准, 他们的产品在市场上占有率太高了,就成了事实标准了。
要是有谁有链接告诉一声。
fish
2006-11-01
引用
如果总共有10种编码,那么只需要 10 + 10 = 20个转换器就够了。如果要是两两直接转换,那么,需要的转换器数量是一个组合数字,需要90个转换器。
java的转换器好像还不够,比如说gbk的“中国”--〉unicode--〉big5的话,“中”能认出来(unicode中只有一个),但是“国”的话,转不到big5的“國”(unicode中两字编码不同)
ddd
2006-11-01
buaawhl 写道
HZ-GB2312 ————> GBK
从道理上说,应该是
HZ-GB2312 byte[] ——> String ——> GBK byte[]
GBK中的一些字可能在utf16中找不到对应的代码点。
ddd
2006-11-01
传说gb2312是gbk的一个子集。
buaawhl
2006-11-01
HZ-GB2312 ————> GBK
从道理上说,应该是
HZ-GB2312 byte[] ——> String ——> GBK byte[]
从道理上说,应该是
HZ-GB2312 byte[] ——> String ——> GBK byte[]
zelsa
2006-11-01
我目前碰到的最头大的编码是HZ-GB2312,邮件系统里用到,目前还没完美的解决方法,HZ-GB2312 ————> GBK 。
不知道各位有谁碰到过?
不知道各位有谁碰到过?
buaawhl
2006-11-01
我也不是很确认,只是根据 char 类型长度猜测的。这些细节问题,并不影响代码处理。
稍微搜索了一下。发现。
http://www.ingrid.org/java/i18n/utf-16/
UTF-16 Support in Java
Unicode Technical Report #17 says "An example of the first are Java String and char APIs, which use UTF-16 code units [Whistler 99]." But current Java specification and implementation don't support UTF-16 properly.
看来这里面的细节问题还是不少。
稍微搜索了一下。发现。
http://www.ingrid.org/java/i18n/utf-16/
UTF-16 Support in Java
Unicode Technical Report #17 says "An example of the first are Java String and char APIs, which use UTF-16 code units [Whistler 99]." But current Java specification and implementation don't support UTF-16 properly.
看来这里面的细节问题还是不少。
Gene
2006-11-01
bigpanda 写道
Gene 写道
这样的理解不正确, Big5, GBK, UTF-8都是Unicode的一种encoding. 可以说他们就是unicode, 并非从big5转到unicode. 所以, byte[]只是10101010101, 你要把它转成string就需要指定一种encoding, 这样jvm就知道取多少位, 通过什么样的标识符来区分一个char.
UTF-8可以说是Unicode的编码的一种.Big5, GBK可绝对不是.GBK里面有的字符是一个字节,有的是两个.Unicode可是全部用两个字节来编成一个字符.
可以看看我的博客,UTF-8编码是如何工作的
http://bigpanda.javaeye.com/admin/show/31890
以前曾经讨论过这个话题,不过没有讨论完:
http://www.javaeye.com/topic/21680
不小心按了两下,重复发了,管理员把这个删了吧
我去sun的网站上看了一下, 的确是我错了.
引用
The classes java.io.InputStreamReader, java.io.OutputStreamWriter, and java.lang.String can convert between Unicode and a number of other character encodings.
只是char[]中使用的真如b兄所说的是Utf16吗?
wiley
2006-10-31
今天对编码又加深了理解,收获不小
buaawhl
2006-10-31
bigpanda的文章在这里。
http://bigpanda.javaeye.com/blog/31890
注意,带有 admin 的url,带有权限,别人看不到。
------------------------
关于byte[] 和 String (char[])。
我用一个比喻。
String可以看作明文,byte[] 可以看作加密后的密文。
编码就相当于加密策略。
byte[] -> String 就相当于解密。
String -> byte[] 就相当于加密。
加密解密都需要事先知道加密策略(就是编码)。
-------------------------
String 的 char 是 16 bits, 2 bytes. 存放的应该是 UTF-16。
http://bigpanda.javaeye.com/blog/31890
注意,带有 admin 的url,带有权限,别人看不到。
------------------------
关于byte[] 和 String (char[])。
我用一个比喻。
String可以看作明文,byte[] 可以看作加密后的密文。
编码就相当于加密策略。
byte[] -> String 就相当于解密。
String -> byte[] 就相当于加密。
加密解密都需要事先知道加密策略(就是编码)。
-------------------------
String 的 char 是 16 bits, 2 bytes. 存放的应该是 UTF-16。
bigpanda
2006-10-31
Gene 写道
这样的理解不正确, Big5, GBK, UTF-8都是Unicode的一种encoding. 可以说他们就是unicode, 并非从big5转到unicode. 所以, byte[]只是10101010101, 你要把它转成string就需要指定一种encoding, 这样jvm就知道取多少位, 通过什么样的标识符来区分一个char.
UTF-8可以说是Unicode的编码的一种.Big5, GBK可绝对不是.GBK里面有的字符是一个字节,有的是两个.Unicode可是全部用两个字节来编成一个字符.
可以看看我的博客,UTF-8编码是如何工作的
http://bigpanda.javaeye.com/blog/31890
以前曾经讨论过这个话题,不过没有讨论完:
http://www.javaeye.com/topic/21680
不小心按了两下,重复发了,管理员把这个删了吧
bigpanda
2006-10-31
Gene 写道
这样的理解不正确, Big5, GBK, UTF-8都是Unicode的一种encoding. 可以说他们就是unicode, 并非从big5转到unicode. 所以, byte[]只是10101010101, 你要把它转成string就需要指定一种encoding, 这样jvm就知道取多少位, 通过什么样的标识符来区分一个char.
UTF-8可以说是Unicode的编码的一种.Big5, GBK可绝对不是.GBK里面有的字符是一个字节,有的是两个.Unicode可是全部用两个字节来编成一个字符.
可以看看我的博客,UTF-8编码是如何工作的
http://bigpanda.javaeye.com/blog/31890 (多谢布娃娃提醒,改过了)
以前曾经讨论过这个话题,不过没有讨论完:
http://www.javaeye.com/topic/21680
Gene
2006-10-31
引用
JVM里面的任何字符串资源都是Unicode,就是说,任何String类型的数据都是Unicode编码。没有例外。既然只有一种编码,那么,我们可以这么说,JVM里面的String是不带编码的。String相当于 char[]。
JVM里面的 byte[] 数据是带编码的。比如,Big5,GBK,GB2312,UTF-8之类的。
一个GBK编码的byte[] 转换成 String,其实就是从GBK编码向Unicode编码转换。
一个String转换成一个Big5编码的byte[],其实就是从Unicode编码向Big5编码转换。
所以,Unicode是所有编码转换的中间介质。所有的编码都有一个转换器可以转换到Unicode,而Unicode也可以转换到其他所有的编码。这样构成了一个总线结构。
比如,如果总共有10种编码,那么只需要 10 + 10 = 20个转换器就够了。如果要是两两直接转换,那么,需要的转换器数量是一个组合数字,需要90个转换器。
JVM里面的 byte[] 数据是带编码的。比如,Big5,GBK,GB2312,UTF-8之类的。
一个GBK编码的byte[] 转换成 String,其实就是从GBK编码向Unicode编码转换。
一个String转换成一个Big5编码的byte[],其实就是从Unicode编码向Big5编码转换。
所以,Unicode是所有编码转换的中间介质。所有的编码都有一个转换器可以转换到Unicode,而Unicode也可以转换到其他所有的编码。这样构成了一个总线结构。
比如,如果总共有10种编码,那么只需要 10 + 10 = 20个转换器就够了。如果要是两两直接转换,那么,需要的转换器数量是一个组合数字,需要90个转换器。
这样的理解不正确, Big5, GBK, UTF-8都是Unicode的一种encoding. 可以说他们就是unicode, 并非从big5转到unicode. 所以, byte[]只是10101010101, 你要把它转成string就需要指定一种encoding, 这样jvm就知道取多少位, 通过什么样的标识符来区分一个char.
引用
我们把浏览器编码叫做 Browser_Charset,把JVM编码叫做JVM_Charset(通常等于服务器系统编码)。
当浏览器的数据过来的时候,是一个带有Browser_Charset的byte[]。
如果用户处理程序需要一个String类型的数据,那么JVM会好心好意地把这个byte[]转换成String。使用的转换器是 JVM_Charset -> Unicode。
注意,如果这个时候,Browser_Charset 和 JVM_Charset并不相等。那么,这个自动转换是错误的。
为了弥补这个错误。我们需要做两步工作。
(1) Unicode -> JVM_Charset,把这个String 转换回到原来的 byte[]。
(2) Browser_Charset -> Unicode,把这个还原的byte[]转换成 String。
这个效果,和直接从HTTP Request取得byte[],然后执行 (2) Browser_Charset -> Unicode 的效果是一样的。
G
当浏览器的数据过来的时候,是一个带有Browser_Charset的byte[]。
如果用户处理程序需要一个String类型的数据,那么JVM会好心好意地把这个byte[]转换成String。使用的转换器是 JVM_Charset -> Unicode。
注意,如果这个时候,Browser_Charset 和 JVM_Charset并不相等。那么,这个自动转换是错误的。
为了弥补这个错误。我们需要做两步工作。
(1) Unicode -> JVM_Charset,把这个String 转换回到原来的 byte[]。
(2) Browser_Charset -> Unicode,把这个还原的byte[]转换成 String。
这个效果,和直接从HTTP Request取得byte[],然后执行 (2) Browser_Charset -> Unicode 的效果是一样的。
JVM有系统默认的charset或者encoding, 这个默认的encoding由jvm, OS和OS的local setting组合决定的. 所以浏览器过来的byte[]如果没有指定encoding, jvm会用默认的encoding转化为String. 所以如果默认的encoding和http 请求里面字符的encoding不同, 那就会出现错误.
BIGN
2006-10-31
谢谢,前段时间也遇到编码问题,现在知道原理了
Arath
2006-10-31
编码是最头痛的
目前常用的ANSI UNICODE(USC2) UTF-8 UTF-7 UCS4等
如果在一个平台开发还比较容易,跨平台更麻烦
目前常用的ANSI UNICODE(USC2) UTF-8 UTF-7 UCS4等
如果在一个平台开发还比较容易,跨平台更麻烦
- 浏览: 591965 次
- 性别:

- 来自: china

- 详细资料
搜索本博客
最近加入圈子
最新评论
-
网上银行的安全操作设计探 ...
其实浦发的动态手机密码也是一种很好的解决方案!
-- by cdredfox -
线程同步
线程同步就是线程排队 ----------------------------- ...
-- by java9981 -
网上银行的安全操作设计探 ...
这方面经验不多, 对这方面比较感兴趣.
-- by grandboy -
网上银行的安全操作设计探 ...
lz关于动态口令(动态令牌)的理解有偏差,请参照ras.com。银行内部设计核心 ...
-- by foodoo -
2008 奥运会开幕式 色彩 ...
楼上,不是LCD,是LED
-- by swflora






评论排行榜