`

java编码与protobuf使用

阅读更多

1、为什么需要编码

    计算机中存储信息的最小单元是一个字节即 8 bit,所以能表示的字符范围是 0~255 个;人类要表示的符号太多,无法用一个字节来完全表示;要解决这个矛盾必须需要一个新的数据结构 char,从 char byte 必须编码。

2、各种常见编码的简介

  • 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 有以下编码规则:

 

  1. 如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 - 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。
  2. 如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节。
  3. 如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节

3、JAVA中的编码场景

       JAVA是使用UTF-16,也就是定长的2个字节来进行编码,例如定义int为4byte,short为2byte。因为任何字符都是2字节的最小单元表示,所以处理起来很方便,但同时也会浪费空间,有些简单的字符只需要一个字节就可以表示,其本质就是用空间换时间。

JAVA中的编码场景主要有I/O操作和内存操作

1> I/O操作

例如InputStreamReader 就是从字节流到字符流的解码过程。而具体字节到字符的解码实现它由 StreamDecoder去实现,在 StreamDecoder解码过程中必须由用户指定 Charset编码格式。值得注意的是如果你没有指定 Charset,将使用本地环境中的默认字符集,例如在中文环境中将使用 GBK 编码。


OutputStreamWriter 转换字符到字节,同样 StreamEncoder 类负责将字符编码成字节,编码格式和默认编码规则与解码是一致的。
 2
>内存操作

      String s = "字符串。。。";

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

      String str = new String(bt,"UTF-8");

      String 类就提供转换到字节的方法,也支持将字节转换为字符串的构造函数。

Charset 提供 encode 与 decode 分别对应 char[] 到 byte[] 的编码和 byte[] 到 char[] 的解码。如下代码所示:

 Charset charset = Charset.forName("UTF-8"); 
 ByteBuffer byteBuffer = charset.encode(string); 
 CharBuffer charBuffer = charset.decode(byteBuffer); 

 字符和字节之间的相互转换只要我们设置编解码格式统一一般都不会出现问题。

4、Java Web 涉及到的编码

         对于使用中文来说,有 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 来存储。所以看一段文本的大小,看字符本身的长度是没有意义的,即使是一样的字符采用不同的编码最终存储的大小也会不同,所以从字符到字节一定要看编码类型。
5、protobuf介绍及应用
1>使用方法

a.http://code.google.com/p/protobuf/downloads/list ,选择其中的win版本下载

b.下载一个protobuf-java-version.jar文件(注意,要与你刚才下的proto.exe版本相同,否则可能出现编译通不过现象)

c. proto.exe同级目录,编写一个.proto文件:支持message的嵌套,对应java的内部类。



 

d、使用protoc命令进行编译



 

2>序列化和反序列化分析

1packageoptionjava_package同时存在,后者会取代前者 。

java代码生成相关参考:https://developers.google.com/protocol-buffers/docs/reference/java-generated?hl=zh-CN

2、PB数据类型

  required: a well-formed message must have exactly one of this field.

  optional: a well-formed message can have zero or one of this field (but not more than one).

  repeated: this field can be repeated any number of times (including zero) in a well-formed message.                    The order of the repeated values will be preserved.

类型参考https://developers.google.com/protocol-buffers/docs/proto?hl=zh-CN&csw=1

3message的编码特点

         PB之所以解析速度快、所占体积小,很大程度上是由它序列化的编码特点来决定的。

3.1 Base 128 Varints

        PB采用了Base 128 Varints来变长编码整数:

    变长编码的整数,它可能包含多个byte,对于每个byte8位,其中后7位表示数值,最高的一位表示是否还有还有另一个byte0表示没有,1表示有;

越前面的byte表示数值的低位,越后面的byte表示数值的高位;

 

例子:
300   varints  编码为:1010 1100 0000 0010

解释如下:
3002进制编码为:0001 0010 1100
按照刚才的规则,高低位颠倒,截取最后的7为放在第一个byte,则第一byte1010 1100(其中最高位1表示,后续还有byte);接着剩下的内容放到第二个byte,为0000 0010(其中最高位0表示,后续无byte,这个数到这里截止了)。
于是,合在一起为 1010 1100 0000 0010

PBkey编码成下面的结构: 

X YYYY ZZZ
其中:最高位
X表示是否还有后续的byte来编码数字别名;YYYY用于编码别名,定义了多于16个属性,则需要用到额外的byte,所以出现频率高的字段应当取1-16的别名);ZZZ表示这个字段的类型,PB支持的属性的对应规则如下表:


 3.2具体实例分析

如下图所示,针对上述定义的proto文件及序列化后的字节流进行分析



              byte[0]:  
10           0 0001 010 别名1,类型repeated

              byte[1]:  14          一个对象所占字节数

              byte[2]:  8           0 0001 000 missionId

              byte[3]:  16         0 0010 000 missionUserId

              byte[4]:  24         0 0011 000 status

              byte[5]:  32         0 0100 000 finishedTime

        PB具有跨平台、解析速度快、序列化数据体积小、扩展性高、使用简单的特点。但是我们也可以看到,相比于XMLPB的数据,并不是自然可读的;同时它生成的代码不是纯pojo,对于代码有一定的侵入性。例如pb不支持像Map<Long, 0bject>,其中Object会出现多种不确定的类型,这样就无法通过pb序列化。从上述分析的数据可以看出,protobuf采用的k-v存储结构,并且采用变长的字节编码格式,所以从空间效率比较高

  • 大小: 10.1 KB
  • 大小: 10.6 KB
  • 大小: 60.8 KB
  • 大小: 79.9 KB
  • 大小: 59.5 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics