的背景

很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们看到8个开关状态是好的,于是他们把这称为”字节“。

再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为”计算机“。

开始计算机只在美国用。八位的字节一共可以组合出256(2的8次方)种不同的状态。 他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作:遇上0×10, 终端就换行;遇上0×07, 终端就向人们嘟嘟叫;遇上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。

他们看到这样很好,于是就把这些0×20以下的字节状态称为”控制码”。他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。大家看到这样,都感觉很好,于是大家都把这个方案叫做Ascii编码。

当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。后来,就像建造巴比伦塔一样,世界各地都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称”扩展字符集“。

从此之后,贪婪的人类再没有新的状态可以用了,因为一个字节总共只有256中状态为什么utf-8中文乱码,也就是说最多只能有256个不同的字符。

美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧!等中国人们得到计算机时,已经没有可以用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民。

我们不客气地把那些127号之后的奇异符号们直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。

中国人民看到这样很不错,于是就把这种汉字方案叫做 ““。 是对 ASCII 的中文扩展。但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来。于是我们不得不继续把 没有用到的码位找出来老实不客气地用上。后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为GBK标准。

GBK包括了 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK扩成了 。从此之后,中华民族的文化就可以在计算机时代中传承了。 中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 “DBCS“( Byte Set 双字节字符集)。

在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣们都要每天念下面这个咒语数百遍: “一个汉字算两个英文字符!一个汉字算两个英文字符……”因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不同的 DBCS 编码方案——当时的中国人想让电脑显示汉字,就必须装上一个”汉字系统”,专门用来处理汉字的显示、输入的问题,这怎么办?而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办?

正在这时,大天使加百列及时出现了——一个叫 ISO(国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们打算叫它” -Octet Coded Set”,简称 UCS, 俗称 ““。

开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ASCII里的那些“半角”字符,包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于”半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。

这时候,从旧社会里走过来的程序员开始发现一个奇怪的现象:他们的 函数靠不住了,一个汉字不再是相当于两个字符了,而是一个!是的,从开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的”一个字符“!同时,也都是统一的”两个字节“,请注意”字符”和”字节”两个术语的不同,“字节”是一个8位的物理存贮单元,而“字符”则是一个文化相关的符号。在中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。

在很长一段时间内无法推广,直到互联网的出现,为解决如何在网络上传输的问题,于是面向传输的众多 UTF(UCS )标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。UTF-8就是在互联网上使用最广的一种的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

为什么utf-8中文乱码_为什么utf-8中文乱码_为什么utf-8中文乱码

字符的形态

字符有三种形态:形状(显示在显示设备上)、字符代码(Java class字节码统一为编码)和字节数组(不同的字符集有不同的映射方案)。

如此就可以明白几个重要的实体概念了:

字符集合( set):

是一组形状的集合,例如所有汉字的集合,它体现了字符的“形状”,它与计算机、编码等无关。要记住,计算机是只认识0和1的,其他都不认识,我们只能通过将各种01组合映射到一个代码,这个代码再对应到某个形状,从而让我们看到这个形状。

编码字符集(Coded set):

是一组字符对应的编码(即数字),为字符集合中的每一个字符给予一个数字。例如最早的编码字符集ASCII,再例如Java使用的(一个字符使用两字节,共16位二进制)。

字符编码方案(- ):将字符编码(数字)映射到一个字节数组的方案,因为在磁盘里,所有信息都是以字节的方式存储的。因此Java的16位字符必须转换为一个字节数组才能够存储。例如UTF-8字符编码方案,它可以将一个字符转换为1、2、3或者4个字节。

Java中怎样处理字符编码

Java的文件使用编码。然而我们的源文件可能使用utf-8或者gbk,Java编译器如何能正确的将源文件转成utf-8的class文件,运行时再转换成?

只有正确读取到了源文件,javac会保证将其转换成正确的。

javac -encoding utf-8 Test.java

如果不指定,将使用操作系统默认的编码。

从JVM中的字符编码,到屏幕上显示的形状。这个转换是在字体库的帮助下完成的。例如默认的一些汉字字体,在Java中运行时是一个个的数字编码,例如,通过查找字体库,得到一个形状“中”,然后显示在屏幕上。

从JVM中的编码,到字节数组,这个转换被称之为编码。转换的目的是为了存储,或者发送信息。

同一个字符代码,例如,采用不同的字符集进行编码为什么utf-8中文乱码,如UTF-8、GBK、UTF-16,能得到不同的字节数组。

编码可使用的方法

从一个字节数组,到一个代表字符的数字,这个转换被称之为解码。解码一般是将从磁盘或者网络上得到的信息,转换为字符或字符串。

注意解码时一定要指定字符集,否则将会使用默认的字符集进行解码。如果使用了错误的字符集,则会出现乱码。

解码使用的构造函数。

乱码问题都是因为在编码或者解码时使用了错误的字符集导致的。如果不能明白什么是默认的字符集,则很有可能导致乱码。

Java的默认字符集,可以在两个地方设定,一是执行java程序时使用-Dfile.参数指定,例如-Dfile.=UTF-8就指定默认字符集是UTF-8。二是在程序执行时使用系统进行指定,如下:

private static void setEncoding(String charset) {    Properties properties = System.getProperties();    properties.put("file.encoding",charset);    System.out.println(properties.get("file.encoding")); }

注意,这两种方法如果同时使用,则程序开始时使用参数指定的字符集,在方法后使用指定的字符集。

如果这两种方法都没有使用,则使用操作系统默认的字符集。例如中文版 7的默认字符集是GBK。

默认字符集的优先级如下:

1.程序执行时使用指定的字符集;

2.java命令的-Dfile.参数指定的字符集;

3.操作系统默认的字符集;

4.JDK中默认的字符集,其默认字符集指定为ISO-8859-1。

如何产生乱码

从上述章节可知,字符的形态有三种,分别是“形状”、“数字”和“字节”。字符的三种形态之间的转换也有三类:从数字到形状,从数字到字节(编码),从字节到数字(解码)。

从数字到形状不会产生乱码,乱码就产生在编码和解码的时候。仔细想来,编码也是不会产生乱码的,因为从数字到字节(指定某个字符集)一定能够转换成功,即使某字符集中不包含该数字,它也会用指定的字节来代替,并在转换时给出指示。

如此一来,乱码只会产生在解码时:例如使用某字符集A编码的字节,使用字符集B来进行解码,而A和B并不兼容。这样一来,解码产生的数字(字符编码)就是错误的,那么它显示出来也是错误的,典型的乱码例子如下(使用UTF-8编码,使用GBK解码):

private static void generateGrabledCode() throws UnsupportedEncodingException {    
   String str = "中国";    
   byte[] bytes = str.getBytes("UTF-8");    
   str = new String(bytes, "GBK");    System.out.println(str); }

总结

总而言之,就是一种囊括了世界上所有的文化符号的一个代码库。任何一个文字、符合都能在代码库中找到一个与之对应的代码。它与存储无关,仅仅是一个标准,大家都按这个标准来处理字符,就不需要每个国家都搞一套自己的标准了。

本身虽然只是一个统一的字符规范,实际上,在Java中,它本身也是一个编码方案,实际上就是UTF-16,即一个字符占两个字节。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注