1.序列化
序列化
(Serialization)将对象的状态信息转换为可以存储或传输的形式的过程(字节流)。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
通常来说有三个用途:
- 持久化:对象可以被存储到磁盘上
- 通信:对象可以通过网络进行传输
- 拷贝、克隆:可以通过将某一对象序列化到内存的缓冲区,然后通过反序列化生成该对象的一个深拷贝(破解单例模式的一种方法)
2.Java序列化机制
在Java中要实现序列化,只需要实现Serializable即可(说是实现,其实不需要实现任何成员方法)。
public interface Serializable { }
如果想对某个对象进行序列化的操作,只需要在OutputStream对象上创建一个输入流 ObjectOutputStream 对象,然后调用 writeObject()。在序列化过程中,对象的类、类签名、雷瑟所有非暂态和非静态成员变量的值,以及它所有的父类都会被写入。
Date d = new Date(); OutputStream out = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(out); objOut.writeObject(d);
如果想对某个基本类型进行序列化,ObjectOutputStream 还提供了多种 writeBoolean、writeByte等方法
反序列过程类似,只需要调用 ObjectInputStream 的 readObject() ,并向下转型,就可以得到正确结果。
优点:实现简便,对于循环引用和重复引用的情况也能处理,允许一定程度上的类成员改变。支持加密,验证。
缺点:序列化后的对象占用空间过大,数据膨胀。反序列会不断创建新的对象。同一个类的对象的序列化结果只输出一份元数据(描述类关系),导致了文件不能分割。
3.Hadoop序列化机制
对于需要保存和处理大规模数据的Hadoop来说,其序列化机制要达到以下目的:
- 排列紧凑:尽量减少带宽,加快数据交换速度
- 处理快速:进程间通信需要大量的数据交互,使用大量的序列化机制,必须减少序列化和反序列的开支
- 跨语言:可以支持不同语言间的数据交互啊,如C++
- 可扩展:当系统协议升级,类定义发生变化,序列化机制需要支持这些升级和变化
为了支持以上特性,引用了Writable接口。和说明性Serializable接口不一样,它要求实现两个方法。
public interface Writable { void write(DataOutput out) throws IOException; void readFields(DataInput in) throws IOException; }
比如,我们需要实现一个表示某一时间段的类,就可以这样写
public class StartEndDate implements Writable{ private Date startDate; private Date endDate; @Override public void write(DataOutput out) throws IOException { out.writeLong(startDate.getTime()); out.writeLong(endDate.getTime()); } @Override public void readFields(DataInput in) throws IOException { startDate = new Date(in.readLong()); endDate = new Date(in.readLong()); } public Date getStartDate() { return startDate; } public void setStartDate(Date startDate) { this.startDate = startDate; } }
Hadoop 还提供了另外几个重要的接口:
WritableComparable:它不仅提供序列化功能,而且还提供比较的功能。这种比较式基于反序列后的对象成员的值,速度较慢。
RawComparator:由于MapReduce十分依赖基于键的比较排序(自定义键还需要重写hashCode和equals方法),因此提供了一个优化接口
RawComparator。该接口允许直接比较数据流中的记录,无需把数据流反序列化为对象,这样避免了新建对象的额外开销。RawComparator定义如下,compare方法可以从每个字节数组b1和b2中读取给定起始位置(s1和s2)以及长度l1和l2的一个整数直接进行比较。
public interface RawComparator<T> extends Comparator<T> { public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2); }
WritableComparator: 是 RawComparator 的一个通用实现,提供两个功能:提供了一个 RawComparator的comparea()的默认实现,该默认实现只是反序列化了键然后再比较,没有什么性能优势。其次、充当了
RawComaprator 实例的一个工厂方法。
当我们要实现自定key排序时(自定义分组),需要指定自己的排序规则。
如需要以StartEndDate为键且以开始时间分组,则需要自定义分组器:
class MyGrouper implements RawComparator<StartEndDate> { @Override public int compare(StartEndDate o1, StartEndDate o2) { return (int)(o1.getStartDate().getTime()- o2.getEndDate().getTime()); } @Override public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) { int compareBytes = WritableComparator.compareBytes(b1, s1, 8, b2, s2, 8); return compareBytes; } }
然后在job中设置
job.setGroupingComparatorClass(MyGrouper.class);
最好将equals和hashcode也进行重写:
@Override public boolean equals(Object obj) { if(!(obj instanceof StartEndDate)) return false; StartEndDate s = (StartEndDate)obj; return startDate.getTime()== s.startDate.getTime()&&endDate.getTime() == s.endDate.getTime(); } @Override public int hashCode() { int result = 17; //任意素数 result = 31*result +startDate.hashCode(); result = 31*result +endDate.hashCode(); return result; };
ps: equal 和 hashcode 方法中应该还要对成员变量判空,以后还需要修改。
参考资料:
《Hadoop权威指南》
《Hadoop技术内幕》
正确重写hashCode的方法 -http://blog.sina.com.cn/s/blog_700aa8830101jtlf.html
MapReduce自定义分组 -http://www.luoliang.me/index.php/archives/ProgrammingLanguage/56.html