serializable 序列化id_万字长文Hadoop序列化机制+框架+Writable类详解

序列化与压缩

传统的计算机系统通过 I/O 操作与外界进行交流,Hadoop 的 I/O 由传统的 I/O 系统发展而来,但又有些不同,Hadoop 需要处理 P、T 级别的数据,所以在 org.apache.hadoop.io 包中包含了一些面向海量数据处理的基本输入输出工具,本章会对其中的序列化和压缩进行研究。

序列化

对象的序列化(Serialization)用于将对象编码成一个字节流,以及从字节流中重新构建对象。“将一个对象编码成一个字节流”称为序列化该对象(Serializing);相反的处理过程称为反序列化(Deserializing)。

序列化有三种主要的用途:

❑作为一种持久化格式 :一个对象被序列化以后,它的编码可以被存储到磁盘上,供以后反序列化用。

❑作为一种通信数据格式 :序列化结果可以从一个正在运行的虚拟机,通过网络被传递到另一个虚拟机上。

❑作为一种拷贝、克隆(clone)机制:将对象序列化到内存的缓存区中,然后通过反序列化,可以得到一个对已存对象进行深拷贝的新对象。

在分布式数据处理中,主要使用上面提到的前两种功能:数据持久化和通信数据格式。

在分析 Hadoop 的序列化机制前,先介绍一下 Java 内建的序列化机制。

Java 内建序列化机制

Java 序列化机制将对象转换为连续的 byte 数据,这些数据可以在日后还原为原先的对象状态,该机制还能自动处理不同操作系统上的差异,在 Windows 系统上序列化的 Java 对象,可以在 UNIX 系统上被重建出来,不需要担心不同机器上的数据表示方法,也不需要担心字节排列次序,如大端(big endian)、小端(little endian)或其他细节。

在 Java 中,使一个类的实例可被序列化非常简单,只需要在类声明中加入 implementsSerializable 即可。Serializable 接口是一个标志,不具有任何成员函数,其定义如下:

public interface Serializable {
}

Serializable 接口没有任何方法,所以不需要对类进行修改,Block 类通过声明它实现了Serializable 接口,立即可以获得 Java 提供的序列化功能。代码如下:

public class Block implements Writable, Comparable<Block>, Serializable

由于序列化主要应用在与 I/O 相关的一些操作上,其实现是通过一对输入 / 输出流来实现的。如果想对某个对象执行序列化动作,可以在某种 OutputStream 对象(后面还会讨论Java 的流)的基础上创建一个对象流 ObjectOutputStream 对象,然后调用 writeObject() 就可达到目的。

writeObject() 方 法 负 责 写 入 实 现 了 Serializable 接 口 对 象 的 状 态 信 息, 输 出 数 据 将被送至该 OutputStream。多个对象的序列化可以在 ObjectOutputStream 对象上多次调用writeObject(),分别写入这些对象。下面是序列化一个 Block 对象的例子:

Block block1=new Block(7806259420524417791L, 39447755L, 56736651L);
Block block2=new Block(5547099594945187683L, 67108864L, 56736828L);
……ByteArrayOutputStream out=new ByteArrayOutputStream();
// 在 ByteArrayOutputStream 的基础上创建 ObjectOutputStream
ObjectOutputStream objOut=new ObjectOutputStream(out);
// 对 block 进行序列化
objOut.writeObject(block1);

对于 Java 基本类型的序列化,ObjectOutputStream 提供了 writeBoolean()、writeByte()等方法。

输入过程类似,将 InputStream 包装在 ObjectInputStream 中并调用 readObject(),该方法返回一个指向向上转型后的 Object 的引用,通过向下转型,就可以得到正确结果。读取对象时,必须要小心地跟踪存储的对象的数量、顺序以及它们的类型。

Java 的序列化机制非常“聪明”,JavaDoc 中对 ObjectOutputStream 的 writeObject() 方法的说明是 :“……这个对象的类、类签名、类的所有非暂态和非静态成员的值,以及它所有的父类都要被写入”,序列化机制会自动访问对象的父类,以保证对象内容的一致性。同时,序列化机制不仅存储对象在内存中的原始数据,还会追踪通过该对象可以到达的其他对象的内部数据,并描述所有这些对象是如何被链接起来的。对于复杂的情形,Java 序列化机制也能应付自如:在输出 objectA 和 objectB 时,不会重复保存对象的序列化结果(如 objectC,即 objectC只被序列化一次);对于循环引用的对象,序列化也不会陷入死循环(如图 3-1 右图的情形)。

de1662b9cd1f663fc81bc25f7b42eeaf.png

但是,序列化以后的对象在尺寸上有点过于充实了,以 Block 类为例,它只包含 3 个长整数,但是它的序列化结果竟然有 112 字节,而 BlockMetaDataInfo 其实只多了一个 long 型的成员变量,输出结果已经膨胀到 190 字节。包含 3 个长整数的 Block 对象的序列化结果如下:

2284f2fe0efb165c623d1de9adb2f22c.png

仔细看 Block 的输出会发现,序列化的结果中包含了大量与类相关的信息。Java 的序列过程在《Java Object Serialization Specification》中规范,以 Block 为例,其结果的前两个字节是魔数(Magic Number)“AC ED”;后续两个字节是序列化格式的版本号,现在使用的版本号是 5 ;接下来是类的描述信息,包括类的版本 ID、是否实现 writeObject() 和readObject() 方法等信息,对于拥有超类的类(如 BlockMetaDataInfo),超类的信息也会递归地被保存下来 ;这些信息都写入 OutputStream 对象后,接下来才是对象的数据。在这个过程中,序列化输出中保存了大量的附加信息,导致序列化结果膨胀,对于需要保存和处理大规模数据的 Hadoop 来说,需要一个新的序列化机制。

Hadoop 序列化机制

和 Java 序 列 化 机 制 不 同( 在 对 象 流 ObjectOutputStream 对 象 上 调 用 writeObject() 方法),Hadoop 的序列化机制通过调用对象的 write() 方法(它带有一个类型为 DataOutput 的参数),将对象序列化到流中。反序列化的过程也是类似,通过对象的 readFields(),从流中读取数据。值得一提的是,Java 序列化机制中,反序列化过程会不断地创建新的对象,但在Hadoop 的序列化机制的反序列化过程中,用户可以复用对象:如,在 Block 的某个对象上反复调用 readFields(),可以在同一个对象上得到多个反序列化的结果,而不是多个反序列化的结果对象(对象被复用了),这减少了 Java 对象的分配和回收,提高了应用的效率。

public static void main(String[] args) {
try {
Block block1=new Block(7806259420524417791L, 39447755L, 56736651L);
…… ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout=new DataOutputStream(bout);
block1.write(dout); // 序列化对象到输出流 dout 中
dout.close();
System.out.println(……);
SerializationExample.print16(out.toByteArray(), bout.size());
}
……
}

由于 Block 对象序列化时只输出了 3 个长整数,block1 的序列化结果一共有 24 字节,如下所示。和 Java 的序列化机制的输出结果对比,Hadoop 的序列化结果紧凑而且快速。

AC ED 00 05 73 72 00 28 6F 72 67 2E 68 61 64 6F ....sr.( org.hado

6F 70 69 6E 74 65 72 6E opintern

 Hadoop 序列化机制的特征

对于处理大规模数据的 Hadoop 平台,其序列化机制需要具有如下特征:

❑紧凑 :由于带宽是 Hadoop 集群中最稀缺的资源,一个紧凑的序列化机制可以充分利用数据中心的带宽。

❑快速 :在进程间通信(包括 MapReduce 过程中涉及的数据交互)时会大量使用序列化机制,因此,必须尽量减少序列化和反序列化的开销。

❑可扩展 :随着系统的发展,系统间通信的协议会升级,类的定义会发生变化,序列化机制需要支持这些升级和变化。

❑互操作 :可以支持不同开发语言间的通信,如 C++ 和 Java 间的通信。这样的通信,可以通过文件(需要精心设计文件的格式)或者后面介绍的 IPC 机制实现。

Java 的序列化机制虽然强大,却不符合上面这些要求。Java Serialization 将每个对象的类名写入输出流中,这导致了 Java 序列化对象需要占用比原对象更多的存储空间。同时,为了减少数据量,同一个类的对象的序列化结果只输出一份元数据,并通过某种形式的引用,来共享元数据。引用导致对序列化后的流进行处理的时候,需要保持一些状态。想象如下一种场景,在一个上百 G 的文件中,反序列化某个对象,需要访问文件中前面的某一个元数据,这将导致这个文件不能切割,并通过 MapReduce 来处理。同时,Java 序列化会不断地创建新的对象,对于 MapReduce 应用来说,不能重用对象,在已有对象上进行反序列化操作,而是不断创建反序列化的各种类型记录,这会带来大量的系统开销。

Hadoop Writable 机制

为了支持以上这些特性,Hadoop 引入 org.apache.hadoop.io.Writable 接口,作为所有可序列化对象必须实现的接口,其类图如图 3-2 所示。

7484db017898536c4aebe9e67f86ad39.png

Writable 机制紧凑、快速(但不容易扩展到 Java 以外的语言,如 C、Python 等)。和 java.io.Serializable 不同,Writable 接口不是一个说明性接口,它包含两个方法:

public interface Writable {
/**
* 输出(序列化)对象到流中
* @param out DataOuput 流,序列化的结果保存在流中
* @throws IOException
*/
void write(DataOutput out) throws IOException;
/**
* 从流中读取(反序列化)对象
* 为了效率,请尽可能复用现有的对象
* @param in DataInput 流,从该流中读取数据
* @throws IOException
*/
void readFields(DataInput in) throws IOException;
}Writable.write() 方法用于将对象状态写入二进制的 DataOutput 中,反序列化的过程由readFields() 从 DataInput 流中读取状态完成。下面是一个例子:public class Block implements Writable, Comparable<Block>, Serializable {
……private long blockId;
private long numBytes;
private long generationStamp;
……public void write(DataOutput out) throws IOException {
out.writeLong(blockId); out.writeLong(numBytes); out.writeLong(generationStamp); }public void readFields(DataInput in) throws IOException {
this.blockId = in.readLong();
this.numBytes = in.readLong();
this.generationStamp = in.readLong();
if (numBytes < 0) {
throw new IOException("Unexpected block size: " + numBytes);
} }……}

这个例子使用的是前面分析 Java 序列化机制的 Block 类,Block 实现了 Writable 接口,即需要实现 write() 方法和 readFields() 方法,这两个方法的实现都很简单 :Block 有三个成员变量,write() 方法简单地把这三个变量写入流中,而 readFields() 则从流中依次读入这些数据,并做必要的检查。

Hadoop 序列化机制中还包括另外几个重要接口 :WritableComparable、RawComparator和 WritableComparator。

WritableComparable,顾名思义,它提供类型比较的能力,这对 MapReduce 至关重要。该接口继承自 Writable 接口和 Comparable 接口,其中 Comparable 用于进行类型比较。

ByteWritable、IntWritable、DoubleWritable 等 Java 基本类型对应的 Writable 类型,都继承自WritableComparable。

效 率 在 Hadoop 中 非 常 重 要, 因 此 HadoopI/O 包 中 提 供 了 具 有 高 效 比 较 能 力 的

RawComparator 接口。RawComparator 和 WritableComparable 类图如图 3-3 所示。

3f6307fbb7f85399a2550a277c1e8ac8.png

RawComparator 接口允许执行者比较流中读取的未被反序列化为对象的记录,从而省去了创建对象的所有开销。其中,compare() 比较时需要的两个参数所对应的记录位于字节数组 b1 和 b2 的指定开始位置 s1 和 s1,记录长度为 l1 和 l2,代码如下:

public interface RawComparator<T>extends
Comparator<T> {
public int compare(byte[] b1, int s1, int
l1, byte[] b2, int s2, int l2);
}

以 IntWritable 为 例, 它 的 RawComparator 实 现 中(WritableComparator 是 一 个 辅 助类,实现了 RawComparator 接口),compare() 方法通过 readInt() 直接在字节数组中读入需要比较的两个整数,然后输出 Comparable 接口要求的比较结果。值得注意的是,该过程中compare() 方法避免使用 IntWritable 对象,从而避免了不必要的对象分配。相关代码如下:

public static class Comparator extends WritableComparator {
……public int compare(byte[] b1, int s1, int l1,
byte[] b2, int s2, int l2) {
int thisValue = readInt(b1, s1);
int thatValue = readInt(b2, s2);
return (thisValue1 : (thisValue==thatValue ? 0 : 1));
}……}

WritableComparator 是 RawComparator 对 WritableComparable 类的一个通用实现。它提供两个主要功能。首先,提供了一个 RawComparator 的 compare() 默认实现,该实现从数据流中反序列化要进行比较的对象,然后调用对象的 compare() 方法进行比较(这些对象都是Comparable 的)。其次,它充当了 RawComparator 实例的一个工厂方法,例如,可以通过如下代码获得 IntWritable 的 RawComparator:

RawComparatorcomparator= WritableComparator.get(IntWritable.class);

典型的 Writable 类详解

Hadoop 将很多 Writable 类归入 org.apache.hadoop.io 包中,类图如图 3-4 所示。

d6c40003704032a6f61e4e2e53c2b9f5.png

图 3-4 Writable 的子类

在这些类中,比较重要的有 Java 基本类、Text、Writable 集合、ObjectWritable 等,本节重点介绍 Java 基本类和 ObjectWritable 的实现。

1. Java 基本类型的 Writable 封装

目前 Java 基本类型对应的 Writable 封装如表 3-1 所示。所有这些 Writable 类都继承自WritableComparable。也就是说,它们是可比较的。同时,它们都有 get() 和 set() 方法,用于获得和设置封装的值。

8f2fd2e5dcc89ddf7d6ae5b9d44decb4.png

在表 3-1 中,对整型(int 和 long)进行编码的时候,有固定长度格式(IntWritable 和LongWritable)和可变长度格式(VIntWritable 和 VLongWritable)两种选择。固定长度格式的整型,序列化后的数据是定长的,而可变长度格式则使用一种比较灵活的编码方式,对于数值比较小的整型,它们往往比较节省空间。同时,由于 VIntWritable 和 VLongWritable的 编 码 规 则 是 一 样 的, 所 以 VIntWritable 的 输 出 可 以 用 VLongWritable 读 入。下 面 以VIntWritable 为例,说明 Writable 的 Java 基本类封装实现。代码如下:

public class VIntWritable implements WritableComparable {
private int value;
……// 设置 VIntWritable 的值
public void set(int value) { this.value = value; }
// 获取 VIntWritable 的值
public int get() { return value; }
public void readFields(DataInput in) throws IOException {
value = WritableUtils.read VInt(in);
}
public void write(DataOutput out) throws IOException {
WritableUtils.writeVInt(out, value);
}
……
}

首先,每个 Java 基本类型的 Writable 封装,其类的内部都包含一个对应基本类型的成员变量 value,get() 和 set() 方法就是用来对该变量进行取值 / 赋值操作的。而 Writable接口要求的 readFields() 和 write() 方法,VIntWritable 则是通过调用 Writable 工具类中提供 的 readVInt() 和 writeVInt() 读 / 写 数 据。方 法 readVInt() 和 writeVInt() 的 实 现 也 只 是 简单 调 用 了 readVLong() 和 writeVLong(), 所 以, 通 过 writeVInt() 写 的 数 据 自 然 可 以 通 过readVLong() 读入。

writeVLong () 方法实现了对整型数值的变长编码,它的编码规则如下:

如果输入的整数大于或等于 –112 同时小于或等于 127,那么编码需要 1 字节;否则,序列化结果的第一个字节,保存了输入整数的符号和后续编码的字节数。符号和后续字节数依据下面的编码规则(又一个规则):

❑如果是正数,则编码值范围落在 –113 和 –120 间(闭区间),后续字节数可以通过 –(v+112) 计算。

❑如果是负数,则编码值范围落在 –121 和 –128 间(闭区间),后续字节数可以通过 –(v+120) 计算。

后续编码将高位在前,写入输入的整数(除去前面全 0 字节)。代码如下:

public final class WritableUtils {
public stati cvoid writeVInt(DataOutput stream, int i) throws IOException
{
writeVLong(stream, i); } /**
* @param stream 保存系列化结果输出流
* @param i 被序列化的整数
* @throws java.io.IOException
*/
public static void writeVLong(DataOutput stream, long i) throws……
{
// 处于 [-112, 127] 的整数
if (i >= -112 && i <= 127) {
stream.writeByte((byte)i);
return;
}
// 计算情况 2 的第一个字节
int len = -112;
if (i < 0) {
i ^= -1L;
len = -120;
}
long tmp = i;
while (tmp != 0) {
tmp = tmp >> 8;
len--;
}
stream.writeByte((byte)len);
len = (len < -120) ? -(len + 120) : -(len + 112);
// 输出后续字节
for (int idx = len; idx != 0; idx--) {
int shiftbits = (idx - 1) * 8;
long mask = 0xFFL << shiftbits;
stream.writeByte((byte)((i & mask) >> shiftbits));
}
}
}

2. ObjectWritable 类的实现

针 对 Java 基 本 类 型、 字 符 串、 枚 举、Writable、 空 值、Writable 的 其 他 子 类,ObjectWritable 提供了一个封装,适用于字段需要使用多种类型。ObjectWritable 可应用于Hadoop 远程过程调用(将在第 4 章介绍)中参数的序列化和反序列化 ;ObjectWritable 的另一个典型应用是在需要序列化不同类型的对象到某一个字段,如在一个 SequenceFile 的值中保存不同类型的对象(如 LongWritable 值或 Text 值)时,可以将该值声明为 ObjectWritable。

ObjectWritable 的实现比较冗长,需要根据可能被封装在 ObjectWritable 中的各种对象进行不同的处理。ObjectWritable 有三个成员变量,包括被封装的对象实例 instance、该对象运行时类的 Class 对象和 Configuration 对象。

ObjectWritable 的 write 方法调用的是静态方法 ObjectWritable.writeObject(),该方法可以往 DataOutput 接口中写入各种 Java 对象。

writeObject() 方法先输出对象的类名(通过对象对应的 Class 对象的 getName() 方法获得),然后根据传入对象的类型,分情况系列化对象到输出流中,也就是说,对象通过该方法输出对象的类名,对象序列化结果对到输出流中。在 ObjectWritable.writeObject() 的逻辑中,需要分别处理 null、Java 数组、字符串 String、Java 基本类型、枚举和 Writable 的子类6 种情况,由于类的继承,处理 Writable 时,序列化的结果包含对象类名,对象实际类名和对象序列化结果三部分。

为什么需要对象实际类名呢?根据 Java 的单根继承规则,ObjectWritable 中传入的declaredClass,可以是传入 instance 对象对应的类的类对象,也可以是 instance 对象的父类的类对象。但是,在序列化和反序列化的时候,往往不能使用父类的序列化方法(如 write方法)来序列化子类对象,所以,在序列化结果中必须记住对象实际类名。相关代码如下:

public class ObjectWritable implements Writable, Configurable {
private Class declaredClass;// 保存于 ObjectWritable 的对象对应的类对象
private Object instance;// 被保留的对象
private Configuration conf;
public ObjectWritable() {}
public ObjectWritable(Object instance) {
set(instance);
}public ObjectWritable(Class declaredClass, Object instance) {
this.declaredClass = declaredClass;
this.instance = instance;
}……public void readFields(DataInput in) throws IOException {
readObject(in, this, this.conf);
}public void write(DataOutput out) throws IOException {
writeObject(out, instance, declaredClass, conf);
}……public static void writeObject(DataOutput out, Object instance,
Class declaredClass,Configuration conf) throws……{if (instance == null) {// 空
instance = new NullInstance(declaredClass, conf);
declaredClass = Writable.class;
}
// 写出 declaredClass 的规范名
UTF8.writeString(out, declaredClass.getName());
if (declaredClass.isArray()) {// 数组
……
} else if (declaredClass == String.class) {// 字符串
……
} else if (declaredClass.isPrimitive()) {// 基本类型
if (declaredClass == Boolean.TYPE) { //boolean
out.writeBoolean(((Boolean)instance).booleanValue());
} else if (declaredClass == Character.TYPE) { //char
……
}
} else if (declaredClass.isEnum()) {// 枚举类型
……
} else if (Writable.class.isAssignableFrom(declaredClass)) {
//Writable 的子类
UTF8.writeString(out, instance.getClass().getName());
((Writable)instance).write(out);
} else {
……
}
public static Object readObject(DataInput in,
ObjectWritable objectWritable, Configuration conf){
……
Class instanceClass = null;
……
Writable writable = WritableFactories.newInstance(instanceClass,
conf);
writable.readFields(in);
instance = writable;
……
}
}

和 输 出 对 应,ObjectWritable 的 readFields() 方 法 调 用 的 是 静 态 方 法 ObjectWritable.readObject (),该方法的实现和 writeObject() 类似,唯一值得研究的是 Writable 对象处理部 分,readObject () 方 法 依 赖 于 WritableFactories 类。WritableFactories 类 允 许 非 公 有 的Writable 子类定义一个对象工厂,由该工厂创建 Writable 对象,如在上面的 readObject () 代码中,通过 WritableFactories 的静态方法 newInstance(),可以创建类型为 instanceClass 的Writable 子对象。相关代码如下:

public class WritableFactories {
// 保存了类型和 WritableFactory 工厂的对应关系private static final HashMap<Class, WritableFactory>CLASS_TO_FACTORY
= new HashMap<Class, WritableFactory>();
……public static Writable newInstance(Class extends Writable> c,
Configuration conf) {
WritableFactory factory = WritableFactories.getFactory(c);
if (factory != null) {
Writable result = factory.newInstance();
if (result instanceof Configurable) {
((Configurable) result).setConf(conf);
}
return result;
} else {
// 采用传统的反射工具 ReflectionUtils,创建对象
return ReflectionUtils.newInstance(c, conf);
}
}
}

WritableFactories.newInstance() 方法根据输入的类型查找对应的 WritableFactory 工厂对象,然后调用该对象的 newInstance() 创建对象,如果该对象是可配置的,newInstance() 还会通过对象的 setConf() 方法配置对象。

WritableFactories 提供注册机制,使得这些 Writable 子类可以将该工厂登记到 WritableFactories的静态成员变量 CLASS_TO_FACTORY 中。下面是一个典型的 WritableFactory 工厂实现,来自于 HDFS 的数据块 Block。其中,WritableFactories.setFactory() 需要两个参数,分别是注册类对应的类对象和能够构造注册类的 WritableFactory 接口的实现,在下面的代码里,WritableFactory 的实现是一个匿名类,其 newInstance() 方法会创建一个新的 Block 对象。

public class Block implements Writable, Comparable<Block> {
static {
WritableFactories.setFactory (Block.class,// 类对象
new WritableFactory() {// 对应类的 WritableFactory 实现
public Writable newInstance() { return new Block(); }
});
}
……
}

ObjectWritable 作为一种通用机制,相当浪费资源,它需要为每一个输出写入封装类型的名字。如果类型的数量不是很多,而且可以事先知道,则可以使用一个静态类型数组来提高效率,并使用数组索引作为类型的序列化引用。GenericWritable 就是因为这个目的被引入 org.apache.hadoop.io 包中,由于篇幅关系,不再详细介绍,有兴趣的读者可以继续分析GenericWritable 的源代码。

 Hadoop 序列化框架

大 部 分 的 MapReduce 程 序 都 使 用 Writable 键 – 值 对 作 为 输 入 和 输 出, 但 这 并 不 是Hadoop 的 API 指定的,其他序列化机制也能和 Hadoop 配合,并应用于 MapReduce 中。

目前,除了前面介绍过的 Java 序列化机制和 Hadoop 使用的 Writable 机制,还流行其他序列化框架,如 Hadoop Avro、Apache Thrift 和 Google Protocol Buffer。

❑Avro 是一个数据序列化系统,用于支持大批量数据交换的应用。它的主要特点有:支持二进制序列化方式,可以便捷、快速地处理大量数据;动态语言友好,Avro 提供的机制使动态语言可以方便地处理 Avro 数据。

❑Thrift 是一个可伸缩的、跨语言的服务开发框架,由 Facebook 贡献给开源社区,是Facebook 的核心框架之一。基于 Thrift 的跨平台能力封装的 Hadoop 文件系统 Thrift API(参考 contrib 的 thriftfs 模块),提供了不同开发语言开发的系统访问 HDFS 的能力。

❑Google Protocol Buffer 是 Google 内部的混合语言数据标准,提供了一种轻便高效的结构化数据存储格式。目前,Protocol Buffers 提供了 C++、Java、Python 三种语言的API,广泛应用于 Google 内部的通信协议、数据存储等领域中。

Hadoop 提供了一个简单的序列化框架 API,用于集成各种序列化实现,该框架由Serialization 实现(在 org.apache.hadoop.io.serializer 包中)。

Serialization 是一个接口,使用抽象工厂的设计模式,提供了一系列和序列化相关并相互依赖对象的接口。通过 Serialization 应用可以获得类型的 Serializer 实例,即将一个对象转换为一个字节流的实现实例 ;Deserializer 实例和 Serializer 实例相反,它用于将字节流转为一个对象。很明显,Serializer 和 Deserializer 相互依赖,所以必须通过抽象工厂Serialization,才能获得对应的实现。相关代码如下:

public interface Serialization<T> {
// 客户端用于判断序列化实现是否支持该类对象
boolean accept(Class> c);
// 获得用于序列化对象的 Serializer 实现
Serializer getSerializer(Class c);
// 获得用于反序列化对象的 Deserializer 实现
Deserializer getDeserializer(Class c);
}

如 果 需 要 使 用 Serializer 来 执 行 序 列 化, 一 般 需 要 通 过 open() 方 法 打 开 Serializer,open() 方法传入一个底层的流对象,然后就可以使用 serialize() 方法序列化对象到底层的流中。最后序列化结束时,通过 close() 方法关闭 Serializer。Serializer 接口的相关代码如下:

public interface Serializer<T> {
// 为输出(序列化)对象做准备
void open(OutputStream out) throws IOException;
// 将对象序列化到底层的流中
void serialize(T t) throws IOException;
// 序列化结束,清理
void close() throws IOException;
}

Hadoop 目前支持两个 Serialization 实现,分别是支持 Writable 机制的 WritableSerialization和 支 持 Java 序 列 化 的 JavaSerialization。通 过 JavaSerialization 可 以 在 MapReduce 程 序中 方 便 地 使 用 标 准 的 Java 类 型, 如 int 或 String, 但 如 同 前 面 所 分 析 的,Java 的 ObjectSerialization 不如 Hadoop 的序列化机制有效,非特殊情况不要轻易尝试。

本篇内容给大家讲解的是Hadoop序列化机制+框架+Writable类详解~

  1. 下篇文章给大家讲解Hadoop压缩实例;

  2. 觉得文章不错的朋友可以转发此文关注小编;

  3. 感谢大家的支持!!

61e64c831535fd6570e4001b4abf6f5b.png

失眠大数据架构师,教你Hadoop配置信息处理,原来还有这种操作

大数据架构师带你深入Hadoop内幕:源代码环境准备,学这篇就够了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值