做 JAVA 经常会碰到中文乱码问题,还有各种编码的问题,特别是 String 类的内容需要重新编码的问题。要解决这些问题,必须了解清楚 JAVA 对于字符串是怎么处理的。
1,“字符” 是由数字来表示的
先来重新了解一下计算机是如何处理 “字符” 的,这个原理是大家必须记住的,特别是在用 JAVA 写程序的时候,万万不可模糊。我们知道,计算机把任何东西都用数字来表示,“字符” 也不例外。比如我们要显示一个阿拉伯数字 “3”,在我们的 PC 里,其实并不是仅仅用一个数字 3 来代表我们要写的 “3”,而是以十六进制的 0x33 来代表,包括放在内存或者是写到文件里,其实都是写着 0x33 的,不信你可以编辑一个文本文件,写一个 “3”,然后用 ultraEdit 看他的原始码。
2,一切 “字符” 都必定用数字 + 编码表表示。
这时候,有一个问题:为什么一定要用 0x33 来代表 “3” 呢?而不用 0x43 来代表呢?或者是直接用 0x03 来代替?其实用什么来代表都可以,只不过大家都习惯了用 ASCII 编码表(是美国国家信息交换表)来确定各字符应该是用什么数字代表的。同样,为了表示中国字,我国也指定了中文的编码表,其中最广泛使用的是 GB2312。比如中文的 “当” 字,就是用 0xB5, 0xB1 这两个八位的数字来表示的。所以 如果显示字符的程序不知道一列数字到底是按什么编码表编码的,他也无法去判断到底这些是什么文字。如果随便用一个不对的编码表来处理这些数字,处理出来的字符很可能完全是错的。比如在英文系统上,没有 GB2312 编码表,送给他一个 0xB5,0xB1,他就傻傻的当作 ASCII 来处理(操作系统通常都有自己默认的编码表),结果显示出来就是两个奇怪的符号,因为这两个字在 ASCII 表里就是那两个符号。同样在繁体中文系统里,他的编码表是 BIG5,显示出来也是一个奇怪的中文,不是 “当” 字。
3,UNICODE 让全世界都说一种语言
看完上面的文字,是否觉得,世界有那么多语言,每个都有自己的一套编码表,很麻烦呢?就算是中文,也有两套流行的编码表,一个是 GB2312,一个是 BIG5。要使用不同中文的编码的字符时,还要转来转去,的确很麻烦。不光这个,如果想要写一篇包含很多过国文字的文章,就麻烦了,必须要让处理这个文章的程序知道,哪个字是什么编码标准的。如果你想要在文章里找一个字,也必须指定你要找的是哪种编码的哪个字。否则,你要找一个 0xB5,0xB1 的中文 “当” 字,很可能把同样数字表示的日文、波兰文这些不相干的字一起给你找出来,够麻烦的吧!
所以人们想,不如大家都用同一个编码标准吧,各种文字都在编码表里有一席之地,处理文字的程序只需要都按这个编码表来处理就可以了。不过要一个编码表里包含所有的文字,这张表就大了,本来英文字 + 数字一共只有 128 个以内。但加上中文后,忽然就多了数万个,所以存放一个字符需要的大小也大了很多。现在 UNICODE 规定了一个字符必须由 2 个 8 位数字来表示,想想,8x8x8x8x = 65536,是多大的一个数字啊!所以全世界的文字才能都包含进去。当然拉,也有人说中国字可能都不止 6 万个拉,还要包括别的文字,但人家外国人觉得你们中国人常用的也没那么多,所以就这么定了,我们也没办法。需要注意的是 GB2312 和 UNICODE 虽然都是用两个 8 位数来代表一个中文字,但具体的规格可不一样,比如 0xB5,0xB1 在 UNICODE 里面可不是 “当” 字,而是另外一国的文字来的。
4,C 是如何简洁的处理字符的
我们来谈谈 C 的字符串。C 语言诞生在 JAVA 之前,C 语言的基本数据类型是没有字符串这个类型的,它只有 char[]。也就是 C 把字符顺序放入一个字节数组就完了。而且 C 也不管放在数组里的是什么文字,也不管那些字是按什么编码标准的。而且他的 char 的大小也不一定是 8 位数字,有时候是 16 位也可能,这要看具体的机器和操作系统。所以写程序的人必须要知道正在处理的 char[] 的内容到底是按什么编码表表示的字符串,要知道如果比较两国文字是否相同,可是没任何意义的哦!
5,JAVA 是是如何处理字符的。
世界总会进步的,JAVA 就是一个例子。JAVA 终于有了 String 类了,它是解决字符问题的最好工具。在 JAVA 里,一个基本的要点是:String 类对象是不需要指定编码表的! 为什么它会自己知道一堆数字各代表什么字符呢?就是因为 String 里的字符信息是用 UNICODE 编码存放的。而 JAVA 为了表示字符(注意是单个字符),也有 char 这个数据类型,而且他的大小是固定 2 个 8 位 16 进制数字长度,也就是 0~65535 罗。为的就是对应 UNICODE 里面的一个字符。大家如果想取一个 String 里的按 UNICODE 数字,可以用 getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 方法取得一个 char[],这个 char[] 里就是表示 String 字符的,按 UNICODE 编码表编码的数字。
可惜现在绝大多数的系统和程序都不是按 UNICODE 来处理字符,而 JAVA 程序总是要和别的程序和系统交换数据的,所以在接收一个字符,或者是发送一个字符的时候,就必须要留意当前系统和 UNICODE 的关系了。比如你从网络或者文件接受到一数字:0xB5,0xB1,JAVA 程序并不知道这两个字到底是中文呢?还是日文,或者英文。你如果不指明这个两个数字的编码表,JAVA 就会按当前系统默认的编码表来处理。如果这两个数字是从中文 WIN98 发出去的,JAVA 程序又是在英文 LINUX 上运行的,那就出现了所谓的乱码问题了。也就是 JAVA 按英文的编码表 ASCII 来处理这两个数字,当通过 new String({0xB5,0xB1})得到的 String 的时候,这个 String 代表的已经不是中文的 “当” 字,而是两个英文的奇怪字符了。不过如果你知道这两个数字一定是中文的话,就可以指定用 new String({0xB5,0xB1},”GB2312”)来处理,这时候新建立的 String 才真的是一个 “当” 字。当然拉,如果你要把一个 “当” 字的 JAVA 的 String 显示在中文 WIN98 上,必须把这个字输出成两个 8 位数字:0xB5,0xB1,不管是写成文件还是输出到浏览器上,都必须是 0xB5,0xB1。如何把 “当” 字用 GB2312 输出?String.getBytes(“GB2312”) 就可以拉!所以有一点要记住:和外界交换任何信息都是以 byte[] 来进行的!。你可以留意一下 JAVA 大多数的 I/O 类,都有以 byte[] 作为参数和返回值的方法。不过,也有很多写的比较糊涂的程序,没有提供 byte[] 交换信息的方法,害的不同文字平台的程序员很头疼。Servlet 的 HttpRequest.getParameter() 就是这样。好在有的 JSP/SERVLET 容易还提供先指定编码表的方法,才能比较简单的解决这个问题。
6,网上关于 JAVA 中文问题的一些错误处理方法。
一个是最常见的,不管什么内容,都用 new String(…,”ISO-8859-1”) 来建立字符串,然后使用的时候按默认的编码格式(通常在服务器上都是英文系统)输出字符串。这样其实你使用的 String 并不是按 UNICODE 来代表真正的字符,而是强行把 BYTE 数组复制到 String 的 char[] 里,一旦你的运行环境改变,你就被迫要修改一大堆的代码。而且也无法在同一个字符串里处理几种不同编码的文字。
另一个是把一种编码格式的字符串,比如是 GB2312,转换成另一种格式的字符串,比如 UTF-8,然后不指明是 UTF-8 编码,而直接用 new String(…)来建立 String,这样放在 String 里面的字符也是无法确定的,它在不同的系统上代表不同的字符。如果要求别人用 “UTF-8 格式” 的 String 来交换信息的时候,其实已经破坏了 JAVA 为了兼容各种语言所做的规定。这种错误的本质思想是还按写 C 语言的方式,把字符串纯粹当作可以自己自由编码的存储器使用,而忽略了 JAVA 字符串只有一种编码格式。如果真的想自由编码,用 byte[]或者 char[]就完全了解决问题的了。
7,其他资料
本文链接:http://agehua.github.io/2016/05/18/javaString-and-bytes/