上文中,介绍了Java序列化,普通的三个long类型序列化,达到了198字节
为了支持以上这些特性, Hadoop引入 org.apache.hadoop.io.Writable接口,作为所有可序列化对象必须实现的接口
Writable机制紧凑、快速(但不容易扩展到Java以外的语言,如C、 Python等)。和 java.io.Serializable不同, Writable接口不是一个说明性接口,它包含两个方法。
Writable.write方法用于将对象状态写入二进制的 DataOutput中,反序列化的过程由readFields(从 DataInput流中读取状态完成。下面是一个例子:
同样序列化三个long,只占用了24字节。
这个例子使用的是前面分析Java序列化机制的 Block类, Block实现了 Writable接口,即需要实现 write方法和 read Fields方法,这两个方法的实现都很简单: Block有三个成员变量, write方法简单地把这三个变量写入流中,而 readFields()则从流中依次读入这些数据,并做必要的检查。
Hadoop序列化机制中还包括另外几个重要接口: Writable Comparable、 Raw Comparator和 Writable Comparator。
WritableComparable,顾名思义,它提供类型比较的能力,这对 MapReduce至关重要。该接口继承自 Writable接口和 Comparable接口,其中 Comparable用于进行类型比较。
Byte Writable、 Int Writable、 Double writable等Java基本类型对应的 Writable类型,都继承自WritableComparable。
效率在 Hadoop中非常重要,因此 Hadoop/C包中提供了具有高效比较能力的Raw Comparator接口。 Raw Comparator和 Writable Comparable类图如图所示
Raw Comparator接口允许执行者比较流中读取的未被反序列化为对象的记录,从而省去了创建对象的所有开销。其中, compareD比较时需要的两个参数所对应的记录位于字节数组bl和b2的指定开始位置sl和s1,记录长度为l1和12,代码如下:
以 Intwritable为例,它的 Raw Comparator实现中( Writable Comparator是一个辅助类,实现了 Raw Comparator接口), compareD方法通过 readIne直接在字节数组中读入需要比较的两个整数,然后输出 Comparable接口要求的比较结果。值得注意的是,该过程中compare方法避免使用 Int Writable对象,从而避免了不必要的对象分配。相关代码如下:
典型的 Writable类详解
Hadoop将很多 Writable类归入 org.apache hadoop.io包中。在这些类中,比较重要的有Java基本类、Text、 Writable集合、 Object Writable等,本次重点介绍Java基本类和 Object Writable的实现。
Java基本类型的 Writable封装
目前Java基本类型对应的 Writable封装如表所示。所有这些 Writable类都继承自Writable Comparable。也就是说,它们是可比较的。同时,它们都有get()和set()方法,用于获得和设置封装的值。
对整型(int和long)进行编码的时候,有固定长度格式(lntWritable和LongWritable)和可变长度格式(VlntWritable和VLongWritable)两种选择。固定长度格式的整型,序列化后的数据是定长的,而可变长度格式则使用一种比较灵活的编码方式,对于数值比较小的整型,它们往往比较节宵空间。同时,由干VlnWritable和VLongWritable的编码规则是一样的,所以VintWritable的输出可以用VLongWritable读入,下面以VlntWritable为例,说明Writable的java堪本类封装实现。代码
每个java类型的Writable封装,其内部都包含一个对应类型的成员变量value,get()和set()方法就是用来对该变量进行取值/赋值操作的。而Writable接口要求的readFields()和write()方法,VIntWritable則是通过调用Writable工具类中提供的readVInt()和writeVInt()读/写数据。方法readVint()和writeVInt(>的实现也只是简单调用了readVLong()和writeVLong(),所以,通过writeVInt()写的数据自然可以通过readVLong()读入。
Hadoop序列化框架
目前,除了前面介绍过的Java序列化机制和Hadoop使用的Writable机制,还流行其他序列化框架,如HadoopAvro、ApacheThrift和GoogleProtocolBuffer。
1.Avro是一个数据序列化系统.用千支持大批量数据交换的应用。它的主要特点有:支持二进制序列化方式.可以便拢、快速地处理大量数据:动态语言友好。Avro提供的机制使动态语言可以方便地处理Avro数据。
2.Thrift是一个可伸缩的、跨语言的服务开发框架,由Facebook贡献给开源社区,是
Facebook的核心框架之一。基于Thrift的跨乎台能力封装的Hadoop文件系统ThriftAPI(畚考contrib的丨hriftfs模块),提供了不同开发语言开发的系统访问HDFS的能力。
3.GoogleProtocolBuffer是Google内部的混合语言数据标准,提供了一种轻便高效的结构化数据存储格式。目前,ProtocolBuffers提供了C++、Java、Python三种语言的API,广泛应用于Google内部的通信协议、数据存储等领域中。
Hadoop提供了一个简单的序列化框架API,用于集成各种序列化实现,该框架由Serialization实现(在org.apache.hadoop.io.serializer包中)。
Hadoop目前支持两个Serialization实现,分别是支持Writable机制的WritableSerialization和支持Java序列化的JavaSerialization。通过JavaSerialization可以在Mapreduce中方便的使用标准的Java类型,如int和String,但如同前面所分析的,Java的ObjectSerialization不如Hadoop的序列化机制有限,非特殊情况,不要尝试。
总结:
Java的序列化机制虽然强大,却不符合Hadoop的要求。 Java Serialization将每个对象的类名写入输出流中,这导致了Java序列化对象需要占用比原对象更多的存储空间。同时,为了减少数据量,同一个类的对象的序列化结果只输出一份元数据,并通过某种形式的引用,来共享元数据。引用导致对序列化后的流进行处理的时候,需要保持一些状态。想象如下一种场景,在一个上百G的文件中,反序列化某个对象,需要访问文件中前面的某一个元数据,这将导致这个文件不能切割,并通过 MapReduce来处理。同时,Java序列化会不断地创建新的对象,对于 MapReduce应用来说,不能重用对象,在已有对象上进行反序列化操作,而是不断创建反序列化的各种类型记录,这会带来大量的系统开销。