编码理解以及关于乱码问题的思考

概述:
我们在写程序时经常会遇到乱码问题,想要更好的解决乱码问题,我们需要对编码知识有一定的了解。然后我们就可以去分析乱码产生的原因以及解决方法。
编码相关概念:
首先说说 字符,字节,bite的关系:
计算机只认识01010这样的东西,我们可以叫它比特流。其中一个0或1叫一个bite。为了方便读取等,我们规定八个bite合起来为一个字节。
字节和字符的关系就比较抽象了,一个字符可能由一个字节组成,也可能由两三个字节组成,最多四个。
我们能看得懂的都是字符,比如一个汉字是一个字符,一个字母是一个字符,一个标点也是一个字符,但是计算机不认识字符,所以要把字符转成字节,再把字节转成bite流,计算机就认识了。
那么一个字符怎么转成对应的字节,转成一个字节还是多个字节?这就引出了我们下面字符集的概念。
字符集其实就是字符和字节之间的映射。它包含了所有我们已知的字符,并且定义好了字符和字节之间的映射。所以一个字符对应几个字节,每个字节长啥样,查一下映射表也就是字符集就知道了。
常见的字符集有ASCII,GBK,Unicode等。ASCII是最早的字符集,它只定义了英文字母与字节之间的映射,后来随着计算机发展,比如中国也开始用计算机了,中文和字节的映射ASCII就没包括,于是中国就搞出了自己的字符集GBK(GB系列,这里不详解)。然后每个国家都搞自己的字符集,这样涉及到国际之间的通信时互相不认识对方的字符集,就乱码了。于是某个神秘组织就推出了unicode字符集,涵盖了世界上所有的字符。
然后ASCII中一个字符占一个字节,GBK兼容ASCII,所以一个英文字母占一个字节,一个汉字占两个字节,但是GBK有全角和半角的分别,因为原ASCII的内容gbk自己也重新实现了一套,在全角模式下,英文字母用的是GBK自己实现的,所以全角模式下任何字符都是占两个字节的。但是我们通常说的都是默认在半角模式下的,全角时一般都明确说明。除了中国其它国家的字符集实现基本都是用双字节的方式实现的,这些统称为DBCS。顺便说下ANSI,ANSI不是指具体的某种字符集编码,它是个广义的概念,在中国ANSI指的就是GBK。
然后说一下编码,像一个字符对应两个字节这种情况,计算机怎么知道两个字节是一起表示一个字符还是单独表示一个字符?这在GBK等字符集中,本身就可以体现出来了,在设计字符集的时候已经考虑到了字节截取的问题,所以ASCII,GBK他们即是字符集也是编码格式。但是对于Unicode字符集就不同了,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。Unicode还有个问题,它采用的是双字节编码的方式,也就是一个字母用Unncode表示要占两个字节,(极少部分特殊字符Unicode会用四个字节表示)。这样对于欧美国家来说Unocde比ASCII或其它DBCS的字符集表示英文要占双倍的空间,尤其在网络传输中双倍带宽的消耗是不能忍受的。于是针对Unicode产生了UTF系列编码:UTF-8是不定长的,它表示英文时占一个字节,表示中文时占三个字节。为啥是三个字节不是两个,这跟UTF-8怎么定义几个字节记表示一个字符有关系,字节截取设置占用了一些位置。除了UTF-8还有UTF-16,UTF-16每个字符都是用两个字节表示的,它跟UTF-8比优点是字符定长,读取快。缺点是占用空间大,因为字母UTF-8占一个字节,它要占俩。但是UTF-8跟GBK比还是UTF-8比较浪费空间的,所以一般纯国内环境的系统还是用的GBK编码,但是UTF-8是国际化的,能兼容更多的字符,为了国际统一,所以我们在编写程序的时候尽量使用UTF-8编码,那些多出来的内存消耗影响是没那么大的,但是编码使用不当系统经常乱码会让你很头疼,以后软件的趋势也都是全部用UTF-8了。
java的class文件都是用UTF-8存储的,省空间。但是class文件内容读到内存中时用的是utf-16编码,运行快,这里指的是运行时数据区的编码格式是utf-16的,而不是原来GBK编码的数据内容到内存就变成UTF-16了,个人理解这里编码指的是那些指令代码在内存的存储方式,而不是数据内容的。
乱码问题:
了解了编码的相关信息,我们再说一下乱码的问题。在此只以java程序举例。比如我们在java类里面写了个汉字“哈”,这时候这个汉字是什么编码?这要分两种情况考虑,一是java文件保存的编码,一是程序运行时的编码。
java文件保存时你定义的常量只是作为一个显示字符要保存起来,这时候用的编码就是给java文件设置的编码。比如设置了gbk编码,那么常量就用gbk编码保存好了,此时如果你把文件的编码改成utf-8,那么系统用读utf-8的方式去读GBK保存的内容,如果常量是英文,那么不会乱码,因为gbk和utf-8存储英文都是一个字节,都跟ascii一样。但是如果是中文,那就不一样了,就会乱码。改java文件编码你会看到你原来写的汉字变成乱码了,但是字母没事。
再说程序运行时,你定义了一个常量,jvm要通过编译器把它弄成字节码加载到内存里,你写的“哈”字是一个字符,程序要认识它,jvm就需要去找一个字符集来把你写的字符转成字节,那它找什么字符集呢?它会找main函数所在的java文件使用的字符集,如果没指定就会用当前操作系统使用的字符集。比如当前默认字符集是gbk,那你写的这个“哈”之后就一定要用gbk的方式才能读出来。这时候如果不进行任何指定,系统还会采用原来的默认编码把它读出来,这没什么问题。但是如果你要把它输入到一个文件上,而这个文件的编码是utf-8的,那这时候文件系统用utf-8去读jvm输出的gbk bite流,那这时候文件里显示的就是乱码了。你要修改文件的编码,或者在程序里先把“哈”用gbk读出来,再用utf-8编码,再输出到文件,才可以。要注意一点,数据在jvm里流通是不会产生乱码的,乱码的产生都是数据解码这个过程使用了错误的编码格式导致的。
我们遇到了乱码,要想清楚几个问题,首先信息来源是哪里,它来的时候是什么编码;然后信息经过了几层编码处理,在每一层中的处理是否正确。要保证解码数据时用的编码格式跟编码数据时用的编码格式一致。如果中途想改变编码,那一定要先用原来的编码格式进行解码,再用新的编码格式进行编码。
转码原理:
然后再说一下转码的大概原理。程序中流通的都是bite流,要转码必须要知道这个bite流是用什么编码格式弄出来的,通过编码格式把它们先转成二进制码,然后再去对应的字符集上找它们对应的字符。然后拿着这些字符去新的字符集中找它们对应的二进制码,然后根据编码格式变成新的bite流在程序里流通。这就是转码大概的流程。



你可能感兴趣的:(java,编码,java,乱码,字符集)