编码_编码器 - CSDN
  • 几种常见编码总结

    2018-08-22 08:41:11
    Unicode 编码 ASCII码 所有的信息最终都表示为一个二进制的字符串,每一个二进制位有 0 和 1 两种状态,通过不同的排列组合,使用 0 和 1 就可以表示世界上所有的东西。 在计算机种中,1 字节对应 8 位二进制数,...

    Unicode 编码


    ASCII码

    所有的信息最终都表示为一个二进制的字符串,每一个二进制位有 0 和 1 两种状态,通过不同的排列组合,使用 0 和 1 就可以表示世界上所有的东西。

    在计算机种中,1 字节对应 8 位二进制数,而每位二进制数有 0、1 两种状态,因此 1 字节可以组合出 256 种状态。如果这 256 中状态每一个都对应一个符号,就能通过 1 字节的数据表示 256 个字符。美国人于是就制定了一套编码(其实就是个字典),描述英语中的字符和这 8 位二进制数的对应关系,这被称为 ASCII 码。

    ASCII 码一共定义了 128 个字符,例如大写的字母 A 是 65(这是十进制数,对应二进制是0100 0001)。这 128 个字符只使用了 8 位二进制数中的后面 7 位,最前面的一位统一规定为 0。


    历史问题

    英语用 128 个字符来编码完全是足够的,但是用来表示其他语言,128 个字符是远远不够的。于是,一些欧洲的国家就决定,将 ASCII 码中闲置的最高位利用起来,这样一来就能表示 256 个字符。但是,这里又有了一个问题,那就是不同的国家的字符集可能不同,就算它们都能用 256 个字符表示全,但是同一个码点(也就是 8 位二进制数)表示的字符可能可能不同。例如,144 在阿拉伯人的 ASCII 码中是گ,而在俄罗斯的 ASCII 码中是 ђ

    因此,ASCII 码的问题在于尽管所有人都在 0 - 127 号字符上达成了一致,但对于 128 - 255 号字符上却有很多种不同的解释。与此同时,亚洲语言有更多的字符需要被存储,一个字节已经不够用了。于是,人们开始使用两个字节来存储字符

    各种各样的编码方式成了系统开发者的噩梦,因为他们想把软件卖到国外。于是,他们提出了一个“内码表”的概念,可以切换到相应语言的一个内码表,这样才能显示相应语言的字母。在这种情况下,如果使用多语种,那么就需要频繁的在内码表内进行切换。


    Unicode字符集

    最终,美国人意识到他们应该提出一种标准方案来展示世界上所有语言中的所有字符,出于这个目的,Unicode诞生了。

    Unicode 当然是一本很厚的字典,记录着世界上所有字符对应的一个数字。具体是怎样的对应关系,又或者说是如何进行划分的,就不是我们考虑的问题了,我们只用知道 Unicode 给所有的字符指定了一个数字用来表示该字符

    对于 Unicode 有一些误解,它仅仅只是一个字符集,规定了符合对应的二进制代码,至于这个二进制代码如何存储则没有任何规定。它的想法很简单,就是为每个字符规定一个用来表示该字符的数字,仅此而已。


    Unicode 编码方案

    之前提到,Unicode 没有规定字符对应的二进制码如何存储。以汉字“汉”为例,它的 Unicode 码点是 0x6c49,对应的二进制数是 110110001001001,二进制数有 15 位,这也就说明了它至少需要 2 个字节来表示。可以想象,在 Unicode 字典中往后的字符可能就需要 3 个字节或者 4 个字节,甚至更多字节来表示了。

    这就导致了一些问题,计算机怎么知道你这个 2 个字节表示的是个字符,而不是分别表示个字符呢?这里我们可能会想到,那就取个最大的,假如 Unicode 中最大的字符用 4 字节就可以表示了,那么我们就将所有的字符都用 4 个字节来表示,不够的就往前面补 0。这样确实可以解决编码问题,但是却造成了空间的极大浪费,如果是一个英文文档,那文件大小就大出了 3 倍,这显然是无法接受的。

    于是,为了较好的解决 Unicode 的编码问题, UTF-8 和 UTF-16 两种当前比较流行的编码方式诞生了。当然还有一个 UTF-32 的编码方式,也就是上述那种定长编码,字符统一使用 4 个字节,虽然看似方便,但是却不如另外两种编码方式使用广泛。


    UTF-8

    UTF-8 是一个非常惊艳的编码方式,漂亮的实现了对 ASCII 码的向后兼容,以保证 Unicode 可以被大众接受。

    UTF-8 是目前互联网上使用最广泛的一种 Unicode 编码方式,它的最大特点就是可变长。它可以使用 1 - 4 个字节表示一个字符,根据字符的不同变换长度。编码规则如下:

    对于单个字节的字符,第一位设为 0,后面的 7 位对应这个字符的 Unicode 码点。因此,对于英文中的 0 - 127 号字符,与 ASCII 码完全相同。这意味着 ASCII 码那个年代的文档用 UTF-8 编码打开完全没有问题。 
    对于需要使用 N 个字节来表示的字符(N > 1),第一个字节的前 N 位都设为 1,第 N + 1 位设为0,剩余的 N - 1 个字节的前两位都设位 10,剩下的二进制位则使用这个字符的 Unicode 码点来填充。 
    编码规则如下:

    Unicode 十六进制码点范围 UTF-8 二进制
    0000 0000 - 0000 007F 0xxxxxxx
    0000 0080 - 0000 07FF 110xxxxx 10xxxxxx
    0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
    0001 0000 - 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

    根据上面编码规则对照表,进行 UTF-8 编码和解码就简单多了。

    下面以汉字“汉”为利,具体说明如何进行 UTF-8 编码和解码。

    “汉”的 Unicode 码点是 0x6c49110 1100 0100 1001),通过上面的对照表可以发现,0x0000 6c49位于第三行的范围,那么得出其格式为 1110xxxx 10xxxxxx 10xxxxxx。接着,从“汉”的二进制数最后一位开始,从后向前依次填充对应格式中的 x,多出的 x 用 0 补上。这样,就得到了“汉”的 UTF-8 编码为 11100110 10110001 10001001,转换成十六进制就是 0xE6 0xB7 0x89

    解码的过程也十分简单:如果一个字节的第一位是 0 ,则说明这个字节对应一个字符;如果一个字节的第一位1,那么连续有多少个 1,就表示该字符占用多少个字节。


    UTF-16

    在了解 UTF-16 编码方式之前,先了解一下另外一个概念——”平面”。

    在上面的介绍中,提到了 Unicode 是一本很厚的字典,她将全世界所有的字符定义在一个集合里。这么多的字符不是一次性定义的,而是分区定义。每个区可以存放 65536 个(216216)字符,称为一个平面(plane)。目前,一共有 17 个(2525)平面,也就是说,整个 Unicode 字符集的大小现在是 221221。

    最前面的 65536 个字符位,称为基本平面(简称 BMP ),它的码点范围是从 0 到 216−1216−1,写成 16 进制就是从 U+0000 到 U+FFFF。所有最常见的字符都放在这个平面,这是 Unicode 最先定义和公布的一个平面。剩下的字符都放在辅助平面(简称 SMP ),码点范围从 U+010000 到 U+10FFFF

    基本了解了平面的概念后,再说回到 UTF-16。UTF-16 编码介于 UTF-32 与 UTF-8 之间,同时结合了定长和变长两种编码方法的特点。它的编码规则很简单:基本平面的字符占用 2 个字节,辅助平面的字符占用 4 个字节。也就是说,UTF-16 的编码长度要么是 2 个字节(U+0000 到 U+FFFF),要么是 4 个字节(U+010000 到 U+10FFFF)。那么问题来了,当我们遇到两个字节时,到底是把这两个字节当作一个字符还是与后面的两个字节一起当作一个字符呢?

    这里有一个很巧妙的地方,在基本平面内,从 U+D800 到 U+DFFF 是一个空段,即这些码点不对应任何字符。因此,这个空段可以用来映射辅助平面的字符。

    辅助平面的字符位共有 220220 个,因此表示这些字符至少需要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半,前 10 位映射在 U+D800 到 U+DBFF,称为高位(H),后 10 位映射在 U+DC00 到 U+DFFF,称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。

    因此,当我们遇到两个字节,发现它的码点在 U+D800 到 U+DBFF 之间,就可以断定,紧跟在后面的两个字节的码点,应该在 U+DC00 到 U+DFFF 之间,这四个字节必须放在一起解读。

    例子: 
    这里写图片描述 
    接下来,以汉字”ji”(见上图)为例,说明 UTF-16 编码方式是如何工作的。

    汉字”ji”的 Unicode 码点为 0x20BB7,该码点显然超出了基本平面的范围(0x0000 - 0xFFFF),因此需要使用四个字节表示。首先用 0x20BB7 - 0x10000 计算出超出的部分,然后将其用 20 个二进制位表示(不足前面补 0 ),结果为0001000010 1110110111。接着,将前 10 位映射到 U+D800 到 U+DBFF之间,后 10 位映射到 U+DC00 到 U+DFFF 即可。U+D800 对应的二进制数为 1101100000000000,直接填充后面的 10 个二进制位即可,得到 1101100001000010,转成 16 进制数则为 0xD842。同理可得,低位为 0xDFB7。因此得出汉字”ji”的 UTF-16 编码为 0xD842 0xDFB7

    Unicode3.0 中给出了辅助平面字符的转换公式:

    H = Math.floor((c-0x10000) / 0x400)+0xD800
    
    L = (c - 0x10000) % 0x400 + 0xDC00
    • 根据编码公式,可以很方便的计算出字符的 UTF-16 编码。
    展开全文
  • 编码

    2016-10-08 10:04:21
    编码1.什么是编码? 计算机中存储的都是二进制,但是要显示的时候,就是我们看到的却可以有中国 ,a 1 等字符计算机中是没有存储字符的,但是我们却看到了。计算机在存储这些信息的时候,根据一个有规则的编号,当...

    编码

    1.什么是编码?
        计算机中存储的都是二进制,但是要显示的时候,就是我们看到的却可以有中国 ,
    a、1 等字符计算机中是没有存储字符的,但是我们却看到了。计算机在存储这些信息的时
    就是编码。
    2.计算机只能识别二进制数据。
        为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来
        表示,并一一对应,形成一张表,这就是编码表。
        例如:汉字 中  有一种编码:中字在utf 8中对映的编码utf-8  -->100  
                         在gbk中呢?有可能就不是100了gbk    -->  150
    是不同的,gbk 是中国大陆,bjg5 是台湾同胞中的繁体字。所以如果给big5一个简体字
    是不认识的。还有ASCII 美国标准信息交换码。
    

    码表

    常见的码表如下:
    ASCII:      美国标准信息交换码。用一个字节的7位可以表示。  -128~127  256
    或“西欧语言”。ASCII码是包含的仅仅是英文字母,并且没有完全占满256个编码位置,所
    以它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入192个字母及符号,
    藉以供使用变音符号的拉丁字母语言使用。从而支持德文,法文等。因而它依然是一个单字节编码,只是比ASCII更全面。
    GB2312: 英文占一个字节, 中文占两个字节。中国的中文编码表。
    GBK:        中国的中文编码表升级,融合了更多的中文文字符号。
    就是unicode。
    UTF-8:  英文占一个字节,中文占三个字节。    最多用三个字节来表示一个字符。
    UTF-16:  不管英文中文都是占两个字节。
    

    编码:

    字符串---》字节数组
    String类的getBytes() 方法进行编码,将字符串,转为对映的二进制,并且这个方法
    可以指定编码表。如果没有指定码表,该方法会使用操作系统默认码表。
    System.getProperty("file.encoding")方式得到当前的默认编码。
    

    解码:

    字节数组---》字符串
    String类的构造函数完成。
    String(byte[] bytes)  使用系统默认码表
    String(byte[],charset)指定码表
    注意:我们使用什么字符集(码表)进行编码,就应该使用什么字符集进行解码,否则很有
    可能出现乱码(兼容字符集不会)。
    

    例子:字节流写出中文

    //使用utf-8进行编码
    public class TestIo {
        public static void main(String[] args) throws IOException {
    
            String path = "c:\\test.txt";
            writeFileByOutputStream(path, "世界你好");
            readFileByInputStream(path);
        }
    
        private static void writeFileByOutputStream(String path, String content)
                throws IOException {
            FileOutputStream fos = new FileOutputStream(path);
    
            // 把字符串进行编码操作
            byte[] bytes = content.getBytes("utf-8");
            // 内容通过字节流写入到文件中。
            fos.write(bytes);
            fos.close();
        }
    
        private static void readFileByInputStream(String path) throws IOException {
            FileInputStream fis = new FileInputStream(path);
            int len = 0;
            byte[] buffer = new byte[1024];
    
            while ((len = fis.read(buffer)) != -1) {
                // 二进制解码,使用系统默认编码
                System.out.println(new String(buffer, 0, len,"utf-8"));
            }
    
        }
    }
    
    
    展开全文
  • 原文:http://www.cnblogs.com/luguo3000/p/3592562.html  编码问题一直都伴随着程序猿从不间断,刚开始学编程的时候好多次遇到编码问题,解 决了文件读取的编码问题,又遇到了网络编码问题,解决了网络编码问题又...

    原文:http://www.cnblogs.com/luguo3000/p/3592562.html

      编码问题一直都伴随着程序猿从不间断,刚开始学编程的时候好多次遇到编码问题,解 决了文件读取的编码问题,又遇到了网络编码问题,解决了网络编码问题又遇到了数据库编码问题。总结一下无非就是编码原理没搞清楚,希望本文能从原理上让菜 鸟们理解编码,遇到问题可以从原理上搞定编码。

     

      一.编码

      人类先有了自己的语言,交流了若干个世纪,然后出现了计算机。可惜计算机只认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。当然读写二进制文件的类也可以读写文本文件,因为文本文件和二进制文件的存储在本质 上是没有区别的,都是二进制。只不过专门读写文本文件的类封装的更好,读写文本文件更方便。

    展开全文
  • 几种常见的编码格式

    2018-07-18 15:05:09
    为什么要编码  不知道大家有没有想过一个问题,那就是为什么要编码?我们能不能不编码?要回答这个问题必须要回到计算机是如何表示我们人类能够理解的符号的,这些符号也就是我们人类使用的语言。由于人类的语言有...

    为什么要编码 
    不知道大家有没有想过一个问题,那就是为什么要编码?我们能不能不编码?要回答这个问题必须要回到计算机是如何表示我们人类能够理解的符号的,这些符号也就是我们人类使用的语言。由于人类的语言有太多,因而表示这些语言的符号太多,无法用计算机中一个基本的存储单元—— byte 来表示,因而必须要经过拆分或一些翻译工作,才能让计算机能理解。我们可以把计算机能够理解的语言假定为英语,其它语言要能够在计算机中使用必须经过一次翻译,把它翻译成英语。这个翻译的过程就是编码。所以可以想象只要不是说英语的国家要能够使用计算机就必须要经过编码。这看起来有些霸道,但是这就是现状,这也和我们国家现在在大力推广汉语一样,希望其它国家都会说汉语,以后其它的语言都翻译成汉语,我们可以把计算机中存储信息的最小单位改成汉字,这样我们就不存在编码问题了。 
    所以总的来说,编码的原因可以总结为: 
    计算机中存储信息的最小单元是一个字节即 8 个 bit,所以能表示的字符范围是 0~255 个 
    人类要表示的符号太多,无法用一个字节来完全表示 
    要解决这个矛盾必须需要一个新的数据结构 char,从 char 到 byte 必须编码 
    如何“翻译” 
    明白了各种语言需要交流,经过翻译是必要的,那又如何来翻译呢?计算中提拱了多种翻译方式,常见的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它们都可以被看作为字典,它们规定了转化的规则,按照这个规则就可以让计算机正确的表示我们的字符。目前的编码格式很多,例如 GB2312、GBK、UTF-8、UTF-16 这几种格式都可以表示一个汉字,那我们到底选择哪种编码格式来存储汉字呢?这就要考虑到其它因素了,是存储空间重要还是编码的效率重要。根据这些因素来正确选择编码格式,下面简要介绍一下这几种编码格式。

     ASCII 码 

    学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。 

    ISO-8859-1 

    128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。 GB2312 
    它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。

     GBK 

    全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。 

    GB18030 

    全称是《信息交换用汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与 GB2312 编码兼容,这个虽然是国家标准,但是实际应用系统中使用的并不广泛。 

    UTF-16 

    说到 UTF 必须要提到 Unicode(Universal Code 统一码),ISO 试图想创建一个全新的超语言字典,世界上所有的语言都可以通过这本字典来相互翻译。可想而知这个字典是多么的复杂,关于 Unicode 的详细规范可以参考相应文档。Unicode 是 Java 和 XML 的基础,下面详细介绍 Unicode 在计算机中的存储形式。 
    UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。 

    UTF-8 

    UTF-16 统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。 
    UTF-8 有以下编码规则: 
    如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 - 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。 
    如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节。 
    如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节 
    Java 中需要编码的场景 
    前面描述了常见的几种编码格式,下面将介绍 Java 中如何处理对编码的支持,什么场合中需要编码。 
    I/O 操作中存在的编码 
    我们知道涉及到编码的地方一般都在字符到字节或者字节到字符的转换上,而需要这种转换的场景主要是在 I/O 的时候,这个 I/O 包括磁盘 I/O 和网络 I/O,关于网络 I/O 部分在后面将主要以 Web 应用为例介绍。下图是 Java 中处理 I/O 问题的接口: 
     

    Reader 类是 Java 的 I/O 中读字符的父类,而 InputStream 类是读字节的父类,InputStreamReader 类就是关联字节到字符的桥梁,它负责在 I/O 过程中处理读取字节到字符的转换,而具体字节到字符的解码实现它由 StreamDecoder 去实现,在 StreamDecoder 解码过程中必须由用户指定 Charset 编码格式。值得注意的是如果你没有指定 Charset,将使用本地环境中的默认字符集,例如在中文环境中将使用 GBK 编码。 
    写的情况也是类似,字符的父类是 Writer,字节的父类是 OutputStream,通过 OutputStreamWriter 转换字符到字节。如下图所示: 

     


    同样 StreamEncoder 类负责将字符编码成字节,编码格式和默认编码规则与解码是一致的。 
    如下面一段代码,实现了文件的读写功能: 

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    String file = "c:/stream.txt";   

    String charset = "UTF-8";   

    // 写字符换转成字节流  

    FileOutputStream outputStream = new FileOutputStream(file);   

    OutputStreamWriter writer = new OutputStreamWriter(   

    outputStream, charset);   

    try {   

       writer.write("这是要保存的中文字符");   

    finally {   

       writer.close();   

    }   

    // 读取字节转换成字符  

    FileInputStream inputStream = new FileInputStream(file);   

    InputStreamReader reader = new InputStreamReader(   

    inputStream, charset);   

    StringBuffer buffer = new StringBuffer();   

    char[] buf = new char[64];   

    int count = 0;   

    try {   

       while ((count = reader.read(buf)) != -1) {   

           buffer.append(buffer, 0, count);   

       }   

    finally {   

       reader.close();   

    }

    在我们的应用程序中涉及到 I/O 操作时只要注意指定统一的编解码 Charset 字符集,一般不会出现乱码问题,有些应用程序如果不注意指定字符编码,中文环境中取操作系统默认编码,如果编解码都在中文环境中,通常也没问题,但是还是强烈的不建议使用操作系统的默认编码,因为这样,你的应用程序的编码格式就和运行环境绑定起来了,在跨环境下很可能出现乱码问题。 
    内存中操作中的编码 
    在 Java 开发中除了 I/O 涉及到编码外,最常用的应该就是在内存中进行字符到字节的数据类型的转换,Java 中用 String 表示字符串,所以 String 类就提供转换到字节的方法,也支持将字节转换为字符串的构造函数。如下代码示例: 

    1

    2

    3

    String s = "这是一段中文字符串";   

     byte[] b = s.getBytes("UTF-8");   

     String n = new String(b,"UTF-8");

    另外一个是已经被被废弃的 ByteToCharConverter 和 CharToByteConverter 类,它们分别提供了 convertAll 方法可以实现 byte[] 和 char[] 的互转。如下代码所示: 

    1

    2

    3

    4

    ByteToCharConverter charConverter = ByteToCharConverter.getConverter("UTF-8");   

    char c[] = charConverter.convertAll(byteArray);   

    CharToByteConverter byteConverter = CharToByteConverter.getConverter("UTF-8");   

    byte[] b = byteConverter.convertAll(c);

    这两个类已经被 Charset 类取代,Charset 提供 encode 与 decode 分别对应 char[] 到 byte[] 的编码和 byte[] 到 char[] 的解码。如下代码所示: 

    1

    2

    3

    Charset charset = Charset.forName("UTF-8");   

     ByteBuffer byteBuffer = charset.encode(string);   

     CharBuffer charBuffer = charset.decode(byteBuffer);

    编码与解码都在一个类中完成,通过 forName 设置编解码字符集,这样更容易统一编码格式,比 ByteToCharConverter 和 CharToByteConverter 类更方便。 
    Java 中还有一个 ByteBuffer 类,它提供一种 char 和 byte 之间的软转换,它们之间转换不需要编码与解码,只是把一个 16bit 的 char 格式,拆分成为 2 个 8bit 的 byte 表示,它们的实际值并没有被修改,仅仅是数据的类型做了转换。如下代码所以: 

    1

    2

    ByteBuffer heapByteBuffer = ByteBuffer.allocate(1024);   

     ByteBuffer byteBuffer = heapByteBuffer.putChar(c);


    以上这些提供字符和字节之间的相互转换只要我们设置编解码格式统一一般都不会出现问题。 
    Java 中如何编解码 
    前面介绍了几种常见的编码格式,这里将以实际例子介绍 Java 中如何实现编码及解码,下面我们以“I am 君山”这个字符串为例介绍 Java 中如何把它以 ISO-8859-1、GB2312、GBK、UTF-16、UTF-8 编码格式进行编码的。 

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    public static void encode() {   

            String name = "I am 君山";   

            toHex(name.toCharArray());   

            try {   

                byte[] iso8859 = name.getBytes("ISO-8859-1");   

                toHex(iso8859);   

                byte[] gb2312 = name.getBytes("GB2312");   

                toHex(gb2312);   

                byte[] gbk = name.getBytes("GBK");   

                toHex(gbk);   

                byte[] utf16 = name.getBytes("UTF-16");   

                toHex(utf16);   

                byte[] utf8 = name.getBytes("UTF-8");   

                toHex(utf8);   

            catch (UnsupportedEncodingException e) {   

                e.printStackTrace();   

            }   

     }


    我们把 name 字符串按照前面说的几种编码格式进行编码转化成 byte 数组,然后以 16 进制输出,我们先看一下 Java 是如何进行编码的。 
    下面是 Java 中编码需要用到的类图 

    图 1. Java 编码类图 

     


    首先根据指定的 charsetName 通过 Charset.forName(charsetName) 设置 Charset 类,然后根据 Charset 创建 CharsetEncoder 对象,再调用 CharsetEncoder.encode 对字符串进行编码,不同的编码类型都会对应到一个类中,实际的编码过程是在这些类中完成的。下面是 String. getBytes(charsetName) 编码过程的时序图 

    图 2.Java 编码时序图 

     


    从上图可以看出根据 charsetName 找到 Charset 类,然后根据这个字符集编码生成 CharsetEncoder,这个类是所有字符编码的父类,针对不同的字符编码集在其子类中定义了如何实现编码,有了 CharsetEncoder 对象后就可以调用 encode 方法去实现编码了。这个是 String.getBytes 编码方法,其它的如 StreamEncoder 中也是类似的方式。下面看看不同的字符集是如何将前面的字符串编码成 byte 数组的? 
    如字符串“I am 君山”的 char 数组为 49 20 61 6d 20 541b 5c71,下面把它按照不同的编码格式转化成相应的字节。 
    按照 ISO-8859-1 编码 
    字符串“I am 君山”用 ISO-8859-1 编码,下面是编码结果: 

     


    从上图看出 7 个 char 字符经过 ISO-8859-1 编码转变成 7 个 byte 数组,ISO-8859-1 是单字节编码,中文“君山”被转化成值是 3f 的 byte。3f 也就是“?”字符,所以经常会出现中文变成“?”很可能就是错误的使用了 ISO-8859-1 这个编码导致的。中文字符经过 ISO-8859-1 编码会丢失信息,通常我们称之为“黑洞”,它会把不认识的字符吸收掉。由于现在大部分基础的 Java 框架或系统默认的字符集编码都是 ISO-8859-1,所以很容易出现乱码问题,后面将会分析不同的乱码形式是怎么出现的。 
    按照 GB2312 编码 
    字符串“I am 君山”用 GB2312 编码,下面是编码结果: 

     


    GB2312 对应的 Charset 是 sun.nio.cs.ext. EUC_CN 而对应的 CharsetDecoder 编码类是 sun.nio.cs.ext. DoubleByte,GB2312 字符集有一个 char 到 byte 的码表,不同的字符编码就是查这个码表找到与每个字符的对应的字节,然后拼装成 byte 数组。查表的规则如下: 

    1

    c2b[c2bIndex[char >> 8] + (char 0xff)]

    如果查到的码位值大于 oxff 则是双字节,否则是单字节。双字节高 8 位作为第一个字节,低 8 位作为第二个字节,如下代码所示: 

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    if (bb > 0xff) {    // DoubleByte   

                if (dl - dp < 2)   

                    return CoderResult.OVERFLOW;   

                da[dp++] = (byte) (bb >> 8);   

                da[dp++] = (byte) bb;   

     else {                      // SingleByte   

                if (dl - dp < 1)   

                    return CoderResult.OVERFLOW;   

                da[dp++] = (byte) bb;   

     }


    从上图可以看出前 5 个字符经过编码后仍然是 5 个字节,而汉字被编码成双字节,在第一节中介绍到 GB2312 只支持 6763 个汉字,所以并不是所有汉字都能够用 GB2312 编码。 
    按照 GBK 编码 
    字符串“I am 君山”用 GBK 编码,下面是编码结果: 

     


    你可能已经发现上图与 GB2312 编码的结果是一样的,没错 GBK 与 GB2312 编码结果是一样的,由此可以得出 GBK 编码是兼容 GB2312 编码的,它们的编码算法也是一样的。不同的是它们的码表长度不一样,GBK 包含的汉字字符更多。所以只要是经过 GB2312 编码的汉字都可以用 GBK 进行解码,反过来则不然。 
    按照 UTF-16 编码 
    字符串“I am 君山”用 UTF-16 编码,下面是编码结果: 

     


    用 UTF-16 编码将 char 数组放大了一倍,单字节范围内的字符,在高位补 0 变成两个字节,中文字符也变成两个字节。从 UTF-16 编码规则来看,仅仅将字符的高位和地位进行拆分变成两个字节。特点是编码效率非常高,规则很简单,由于不同处理器对 2 字节处理方式不同,Big-endian(高位字节在前,低位字节在后)或 Little-endian(低位字节在前,高位字节在后)编码,所以在对一串字符串进行编码是需要指明到底是 Big-endian 还是 Little-endian,所以前面有两个字节用来保存 BYTE_ORDER_MARK 值,UTF-16 是用定长 16 位(2 字节)来表示的 UCS-2 或 Unicode 转换格式,通过代理对来访问 BMP 之外的字符编码。 
    按照 UTF-8 编码 
    字符串“I am 君山”用 UTF-8 编码,下面是编码结果: 

     


    UTF-16 虽然编码效率很高,但是对单字节范围内字符也放大了一倍,这无形也浪费了存储空间,另外 UTF-16 采用顺序编码,不能对单个字符的编码值进行校验,如果中间的一个字符码值损坏,后面的所有码值都将受影响。而 UTF-8 这些问题都不存在,UTF-8 对单字节范围内字符仍然用一个字节表示,对汉字采用三个字节表示。它的编码规则如下: 

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    private CoderResult encodeArrayLoop(CharBuffer src,   

     ByteBuffer dst){   

                char[] sa = src.array();   

                int sp = src.arrayOffset() + src.position();   

                int sl = src.arrayOffset() + src.limit();   

                byte[] da = dst.array();   

                int dp = dst.arrayOffset() + dst.position();   

                int dl = dst.arrayOffset() + dst.limit();   

                int dlASCII = dp + Math.min(sl - sp, dl - dp);   

                // ASCII only loop   

                while (dp < dlASCII && sa[sp] < '\u0080')   

                    da[dp++] = (byte) sa[sp++];   

                while (sp < sl) {   

                    char c = sa[sp];   

                    if (c < 0x80) {   

                        // Have at most seven bits   

                        if (dp >= dl)   

                            return overflow(src, sp, dst, dp);   

                        da[dp++] = (byte)c;   

                    else if (c < 0x800) {   

                        // 2 bytes, 11 bits   

                        if (dl - dp < 2)   

                            return overflow(src, sp, dst, dp);   

                        da[dp++] = (byte)(0xc0 | (c >> 6));   

                        da[dp++] = (byte)(0x80 | (c & 0x3f));   

                    else if (Character.isSurrogate(c)) {   

                        // Have a surrogate pair   

                        if (sgp == null)   

                            sgp = new Surrogate.Parser();   

                        int uc = sgp.parse(c, sa, sp, sl);   

                        if (uc < 0) {   

                            updatePositions(src, sp, dst, dp);   

                            return sgp.error();   

                        }   

                        if (dl - dp < 4)   

                            return overflow(src, sp, dst, dp);   

                        da[dp++] = (byte)(0xf0 | ((uc >> 18)));   

                        da[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f));   

                        da[dp++] = (byte)(0x80 | ((uc >>  6) & 0x3f));   

                        da[dp++] = (byte)(0x80 | (uc & 0x3f));   

                        sp++;  // 2 chars   

                    else {   

                        // 3 bytes, 16 bits   

                        if (dl - dp < 3)   

                            return overflow(src, sp, dst, dp);   

                        da[dp++] = (byte)(0xe0 | ((c >> 12)));   

                        da[dp++] = (byte)(0x80 | ((c >>  6) & 0x3f));   

                        da[dp++] = (byte)(0x80 | (c & 0x3f));   

                    }   

                    sp++;   

                }   

                updatePositions(src, sp, dst, dp);   

                return CoderResult.UNDERFLOW;   

     }  

      

     

    UTF-8 编码与 GBK 和 GB2312 不同,不用查码表,所以在编码效率上 UTF-8 的效率会更好,所以在存储中文字符时 UTF-8 编码比较理想。 
    几种编码格式的比较 
    对中文字符后面四种编码格式都能处理,GB2312 与 GBK 编码规则类似,但是 GBK 范围更大,它能处理所有汉字字符,所以 GB2312 与 GBK 比较应该选择 GBK。UTF-16 与 UTF-8 都是处理 Unicode 编码,它们的编码规则不太相同,相对来说 UTF-16 编码效率最高,字符到字节相互转换更简单,进行字符串操作也更好。它适合在本地磁盘和内存之间使用,可以进行字符和字节之间快速切换,如 Java 的内存编码就是采用 UTF-16 编码。但是它不适合在网络之间传输,因为网络传输容易损坏字节流,一旦字节流损坏将很难恢复,想比较而言 UTF-8 更适合网络传输,对 ASCII 字符采用单字节存储,另外单个字符损坏也不会影响后面其它字符,在编码效率上介于 GBK 和 UTF-16 之间,所以 UTF-8 在编码效率上和编码安全性上做了平衡,是理想的中文编码方式。 
    Java Web 涉及到的编码 
    对于使用中文来说,有 I/O 的地方就会涉及到编码,前面已经提到了 I/O 操作会引起编码,而大部分 I/O 引起的乱码都是网络 I/O,因为现在几乎所有的应用程序都涉及到网络操作,而数据经过网络传输都是以字节为单位的,所以所有的数据都必须能够被序列化为字节。在 Java 中数据被序列化必须继承 Serializable 接口。 
    这里有一个问题,你是否认真考虑过一段文本它的实际大小应该怎么计算,我曾经碰到过一个问题:就是要想办法压缩 Cookie 大小,减少网络传输量,当时有选择不同的压缩算法,发现压缩后字符数是减少了,但是并没有减少字节数。所谓的压缩只是将多个单字节字符通过编码转变成一个多字节字符。减少的是 String.length(),而并没有减少最终的字节数。例如将“ab”两个字符通过某种编码转变成一个奇怪的字符,虽然字符数从两个变成一个,但是如果采用 UTF-8 编码这个奇怪的字符最后经过编码可能又会变成三个或更多的字节。同样的道理比如整型数字 1234567 如果当成字符来存储,采用 UTF-8 来编码占用 7 个 byte,采用 UTF-16 编码将会占用 14 个 byte,但是把它当成 int 型数字来存储只需要 4 个 byte 来存储。所以看一段文本的大小,看字符本身的长度是没有意义的,即使是一样的字符采用不同的编码最终存储的大小也会不同,所以从字符到字节一定要看编码类型。 
    另外一个问题,你是否考虑过,当我们在电脑中某个文本编辑器里输入某个汉字时,它到底是怎么表示的?我们知道,计算机里所有的信息都是以 01 表示的,那么一个汉字,它到底是多少个 0 和 1 呢?我们能够看到的汉字都是以字符形式出现的,例如在 Java 中“淘宝”两个字符,它在计算机中的数值 10 进制是 28120 和 23453,16 进制是 6bd8 和 5d9d,也就是这两个字符是由这两个数字唯一表示的。Java 中一个 char 是 16 个 bit 相当于两个字节,所以两个汉字用 char 表示在内存中占用相当于四个字节的空间。 
    这两个问题搞清楚后,我们看一下 Java Web 中那些地方可能会存在编码转换? 
    用户从浏览器端发起一个 HTTP 请求,需要存在编码的地方是 URL、Cookie、Parameter。服务器端接受到 HTTP 请求后要解析 HTTP 协议,其中 URI、Cookie 和 POST 表单参数需要解码,服务器端可能还需要读取数据库中的数据,本地或网络中其它地方的文本文件,这些数据都可能存在编码问题,当 Servlet 处理完所有请求的数据后,需要将这些数据再编码通过 Socket 发送到用户请求的浏览器里,再经过浏览器解码成为文本。这些过程如下图所示: 

     


    如上图所示一次 HTTP 请求设计到很多地方需要编解码,它们编解码的规则是什么?下面将会重点阐述一下: 
    URL 的编解码 
    用户提交一个 URL,这个 URL 中可能存在中文,因此需要编码,如何对这个 URL 进行编码?根据什么规则来编码?有如何来解码?如下图一个 URL: 

    图 4.URL 的几个组成部分 

     


    上图中以 Tomcat 作为 Servlet Engine 为例,它们分别对应到下面这些配置文件中: 
    Port 对应在 Tomcat 的 <Connector port="8080"/> 中配置,而 Context Path 在 <Context path="/examples"/> 中配置,Servlet Path 在 Web 应用的 web.xml 中的 
    <servlet-mapping> 
            <servlet-name>junshanExample</servlet-name> 
            <url-pattern>/servlets/servlet/*</url-pattern> 
    </servlet-mapping> 

    <url-pattern> 中配置,PathInfo 是我们请求的具体的 Servlet,QueryString 是要传递的参数,注意这里是在浏览器里直接输入 URL 所以是通过 Get 方法请求的,如果是 POST 方法请求的话,QueryString 将通过表单方式提交到服务器端,这个将在后面再介绍。 
    上图中 PathInfo 和 QueryString 出现了中文,当我们在浏览器中直接输入这个 URL 时,在浏览器端和服务端会如何编码和解析这个 URL 呢?为了验证浏览器是怎么编码 URL 的我们选择 FireFox 浏览器并通过 HTTPFox 插件观察我们请求的 URL 的实际的内容,以下是 URL:HTTP://localhost:8080/examples/servlets/servlet/ 君山 ?author= 君山在中文 FireFox3.6.12 的测试结果 

    展开全文
  • 编码有几种 ,计算机最初是在美国等国家发明的 所以表示字符只有简单的几个字母只要对字母进行编码就好 我们标准码 iso-8859-1 这就是一个标准 但是后来计算机普及了 于是就中国要使用计算机了 但是机器不认得中文,...
  • 各种编码类型总结

    2018-08-22 19:15:29
    编码:GB2312 字节数 : 1;编码:GBK 字节数 : 1;编码:GB18030 字节数 : 1;编码:ISO-8859-1 字节数 : 1;编码:UTF-8 字节数 : 4;编码:UTF-16 字节数 : 2;编码:UTF-16BE 字节数 : 2;编码:UTF-16LE   ...
  • 什么是编码

    2018-12-10 10:19:12
    什么是编码? 事实上计算机只认识 0 和 1,然而我们却可以通过计算机来显示文本,这就是靠编码实现的。编码其实就是约定的一个协议,比如 ASCII 编码约定了大写字母 A 对应十进制数 65,那么在读取一个字符串的时候...
  • 算数编码详解

    2019-06-07 20:49:02
    算数编码是一种非常有用的无损信源压缩编码方式,以下是它的详解: 转自https://segmentfault.com/a/1190000011561822 编码原理 算数编码的原理我个人感觉其实并不太容易用三言两语直观地表达出来,其背后的数学...
  • 编码有几种 ,计算机最初是在美国等国家发明的 所以表示字符只有简单的几个字母只要对字母进行编码就好 我们标准码 iso-8859-1 这就是一个标准 但是后来计算机普及了 于是就中国要使用计算机了 但是机器不认得中文,...
  • NRC编码字符大全

    2016-01-28 11:41:09
    最近系统改造时,发现有...,那么【’&#’+数字】是一种什么编码呢?Numeric Character Reference numeric character reference(NCR),直译就是数字字符引用。NCR是一种常见的标记结构,用于SGML和其他SGML相似的标
  • VCG��Ա������ 一个JAVA对象直接输出产生这个结果。问这个结果经过何种编码转换。
  • 本文介绍一个最简单的基于FFMPEG的视频编码器。该编码器实现了YUV420P的像素数据编码为H.264的压缩编码数据。编码器代码十分简单,但是每一行代码都很重要,适合好好研究一下。弄清楚了本代码也就基本弄清楚了FFMPEG...
  • 问题一:File-&gt;Settings-&gt;Editor-&gt;File Encodings问题二:...File Encodings问题三:将项目中的.idea文件夹中的encodings.xml文件中的编码格式改为uft-8问题四:File-&gt;Settings-&...
  • 查看文件编码 在Linux中查看文件编码可以通过以下几种方式: 1.在Vim中可以直接查看文件编码 :set fileencoding 即可显示文件编码格式。 如果你只是想查看其它编码格式的文件或者想解决用Vim查看文件乱码的问题,...
  • Eclipse设置编码格式

    2018-02-25 09:27:32
    在开发过程中,我们常会遇到中文乱码问题,因此我们需要将编码格式设一致,一般将编码格式设置为UTF-8格式(根据各自的项目需要设置编码,一般项目大多用UTF-8编码)。下面我们将介绍一下使用Eclipse开发工具进行开发...
  • 霍夫曼编码(Huffman Coding)是一种编码方法,霍夫曼编码是可变字长编码(VLC)的一种。 霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的...
  • 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,...
  • 信源编码与信道编码

    2017-03-26 17:02:44
    但现代通信应用中常见的信源编码方式有:Huffman编码、算术编码、L-Z编码,这三种都是无损编码,另外还有一些有损的编码方式。信源编码的目标就是使信源减少冗余,更加有效、经济地传输,最常见的应用形式就是压缩。...
  • 1.编码器简介编码器分类: 按工作原理:光电式、磁电式和触点电刷式 按码盘的刻孔方式:增量式和绝对式两类 由于博主接触面还不是很广,一共就用过两个种类的编码器,都是属于光电的 差分编码器:一般由8根线连接...
  • 这样就有了对齐的点,方便编码后进行逐个方波进行对比, 接着对上下两个部分进行N分类,每分类值范围为 (max−m)/N,(m−min)/N (max-m)/N,(m-min)/N (max−m)/N,(m−min)/N 一个值v在某段值范围内就取值为该范围内的...
1 2 3 4 5 ... 20
收藏数 2,047,615
精华内容 819,046
关键字:

编码