编码问题一直都伴随着程序猿从不间断,刚开始学编程的时候好多次遇到编码问题,解决了文件读取的编码问题,又遇到了网络编码问题,解决了网络编码问题又遇到了数据库编码问题。总结一下无非就是编码原理没搞清楚,希望本文能从原理上让菜鸟们理解编码,遇到问题可以从原理上搞定编码。
一.编码
人类先有了自己的语言,交流了若干个世纪,然后出现了计算机。可惜计算机只认0和1,人类只能认文字,双方都不能妥协,那就必须要有一个从文字到0、1的映射了。从文字到0、1的映射称为编码,反过来从0、1到文字叫解码。
具体什么是编码?先来咬文嚼字一下吧。编就是将某样东西按照一定的规则放到一起,码在这里是数字的意思。编码就是将某东西编成数字。比如邮政编码,就是将不同范围内的邮局编成不同的数字。计算机里只有0和1,编码就是将文本字符编成一系列的0和1,看起来好像是废话啊,但这确实是编码的本质。
后来经过编码,计算机屏幕上终于可以显示“Hello
World”了。学计算机之前谁都不知道有这么一个过程,因为一切看上去都理所当然。这种根深蒂固的认识让我们对编码理解起来犯了难。
首先屏幕的显示跟计算机存储是两码事,屏幕对应人们的视觉认知,它是无形的,你找不出来在哪里刻了这么两个单词,而计算机存储是客观存在的。计算机里只有0和1,怎么来表示“Hello
World”呢(假如我们在美国),那就需要将字母数字及标点符号编一个号。一个字节可以表示256个数字,表示字母数字标点足够了,所以用一个字节就可以对应一个字符了。这样一来计算机在显示文字的时候,先将0、1解码成对应的文字,然后在屏幕上渲染出来就可以了。我们将“Hello
World”叫做字符,计算机实际存储的是字符对应的编号,这些编号就叫字节流。
上边这种编码就是ASCII码,如果计算机只在美国用或者只显示英语,那编码就是透明的,谁都不需要去关心编码,一切都觉得理所当然。可是计算机应用到了像中国这样的国家,这些国家的语言哪里只是几个字母啊,有成千上万种不同的字符。很显然ASCII码就不能满足需求了,怎么办呢,每个国家都研制自己的编码呗,很显然这样做并不长久,每个国家都有自己的编码实在有点乱,连两个国家的语言都不能放在一起。所以可以将世界当成一个整体,把所有的文字统一编号,这时候就出现了unicode编码。用一个字节来表示一个字符显然是不够的,unicode编码用了两个字节来表示一个字符。其实,编码的发展过程并没有这么顺利,中间还是出现了很多其他的编码,以后的文章可以详细说一下几种常用的编码。那问题岂不是解决了,大家都用unicode不就完事了吗,哪有这么简单呢,unicode出现之前计算机领域已经有很多成型的操作系统软件甚至标准,不可能都统一改成unicode编码。所以到现在还是会遇到编码问题,unicode只是给我们提供了一种统一解释所有文字的编码方案。要搞清楚,这里讨论的编码都是针对文本字符的。
二.乱码
编码之所以受到关注,乱码几乎起到了决定性的作用,如果没有乱码,一切都让大家觉得顺理成章,那谁还会关注编码呢。
出现乱码的原因就是文本字符编码过程与字节流解码过程使用了不同的编码格式,这个往往归咎于解码格式选择错误,也就是说在解码的过程中出现了问题。如果我的字符是用utf-8编码,你用GBK解码那肯定出问题。因为文字按照utf-8的编码规则编成的0、1,按照GBK的规则解码回来的文字并不是原来的文字,这时候就会出现乱码了。这种问题会出现在文件读写、网络编码传输、数据库存取上。只要牵涉到字符都有可能出现乱码,因为只要有字符就会有解码过程。
还有一种情况就是文件压根不是文本文件,也就是说根本就没有经过编码这个过程,那你去解码当然乱码了。比如64,你如果看做文本字符就是6和4两个字符,可以对应编码格式进行编码。如果看做是数字64,那对应的存储结构是01000000,就没有编码过程,也就不需要去解码。
要搞清楚的一点就是同样的文本字符,经过不同的编码,在存储结构上是不一样的,但是代表的字符是一样的,不同编码真正的区别在于存储结构。反过来,相同的存储结构,经过不同的解码,对应的文本字符并不一样,但是在内存上结构上并没有改变。如果碰到乱码,不要慌张,因为原始存储结构一动没动,只不过用错了解码方式。就像一千个读者有一千个哈姆雷特一样,真实的哈姆雷特就在那里。
乱码是显示在屏幕上才被认为是乱码,也就是说乱码取决于人的感官,乱码只有人才知道﹐计算机不认为这是乱码。
三.文件编码
不管是文本还是图片或视频,在计算机存储上都是一视同仁,全都是字节流。但是从方便人们阅读的角度上还是分为文本文件和二进制文件。文本文件的可视形式就是文本字符,在存储和显示时有文本字符编解码的过程,可以直接用文本编辑器阅读。除文本文件以外就是二进制文件,不同类型的二进制文件都有相应的结构标准,例如java的class文件,前四个字节代表文件类型,后边两个字节代表大版本号,再后边两个字节代表小版本号。具体哪些字节代表什么意思,值是float类型还是int类型,都有一定的标准,所以需要特定的软件按照标准去读取解析。
在不同的编程语言中,往往提供不同的类对文本文件和二进制文件进行读写。最常用的就是文本文件的读写例如C#中有StreamReader和StreamWriter,Java中有BufferedReader和BufferedWriter。还有二进制文件的读写例如C#中有BinaryReader和BinaryWriter,Java中有DataInputStream和DataOutputStream。当然读写二进制文件的类也可以读写文本文件,因为文本文件和二进制文件的存储在本质上是没有区别的,都是二进制。只不过专门读写文本文件的类封装的更好,读写文本文件更方便。
纯文字表达,理解上肯定会有障碍,有疑问欢迎沟通。下篇文章介绍一下Web编码,需要的可以关注一下。