Java 和 Hadoop 序列化机制浅讲

1.序列化

序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程(字节流)。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

通常来说有三个用途:

  • 持久化:对象可以被存储到磁盘上
  • 通信:对象可以通过网络进行传输
  • 拷贝、克隆:可以通过将某一对象序列化到内存的缓冲区,然后通过反序列化生成该对象的一个深拷贝(破解单例模式的一种方法)

2.Java序列化机制

在Java中要实现序列化,只需要实现Serializable即可(说是实现,其实不需要实现任何成员方法)。

   
   
  1. public interface Serializable {
  2. }
如果想对某个对象进行序列化的操作,只需要在OutputStream对象上创建一个输入流 ObjectOutputStream 对象,然后调用 writeObject()。 在序列化过程中,对象的类、类签名、雷瑟所有非暂态和非静态成员变量的值,以及它所有的父类都会被写入。

   
   
  1. Date d = new Date();
  2. OutputStream out = new ByteArrayOutputStream();
  3. ObjectOutputStream objOut = new ObjectOutputStream(out);
  4. objOut.writeObject(d);
如果想对某个基本类型进行序列化,ObjectOutputStream 还提供了多种 writeBoolean、writeByte等方法
反序列过程类似,只需要调用 ObjectInputStream 的 readObject() ,并向下转型,就可以得到正确结果。
优点:实现简便,对于循环引用和重复引用的情况也能处理,允许一定程度上的类成员改变。支持加密,验证。
缺点:序列化后的对象占用空间过大,数据膨胀。反序列会不断创建新的对象。同一个类的对象的序列化结果只输出一份元数据(描述类关系),导致了文件不能分割。

3.Hadoop序列化机制

对于需要保存和处理大规模数据的Hadoop来说,其序列化机制要达到以下目的:
  • 排列紧凑:尽量减少带宽,加快数据交换速度
  • 处理快速:进程间通信需要大量的数据交互,使用大量的序列化机制,必须减少序列化和反序列的开支
  • 跨语言:可以支持不同语言间的数据交互啊,如C++
  • 可扩展:当系统协议升级,类定义发生变化,序列化机制需要支持这些升级和变化
为了支持以上特性,引用了Writable接口。和说明性Serializable接口不一样,它要求实现两个方法。

   
   
  1. public interface Writable {
  2. void write(DataOutput out) throws IOException;
  3. void readFields(DataInput in) throws IOException;
  4. }
比如,我们需要实现一个表示某一时间段的类,就可以这样写

   
   
  1. public class StartEndDate implements Writable{
  2. private Date startDate;
  3. private Date endDate;
  4. @Override
  5. public void write(DataOutput out) throws IOException {
  6. out.writeLong(startDate.getTime());
  7. out.writeLong(endDate.getTime());
  8. }
  9. @Override
  10. public void readFields(DataInput in) throws IOException {
  11. startDate = new Date(in.readLong());
  12. endDate = new Date(in.readLong());
  13. }
  14. public Date getStartDate() {
  15. return startDate;
  16. }
  17. public void setStartDate(Date startDate) {
  18. this.startDate = startDate;
  19. }
  20. }

Hadoop 还提供了另外几个重要的接口:
WritableComparable:它不仅提供序列化功能,而且还提供比较的功能。这种比较式基于反序列后的对象成员的值,速度较慢。
RawComparator:由于MapReduce十分依赖基于键的比较排序(自定义键还需要重写hashCode和equals方法),因此提供了一个优化接口 RawComparator。该接口允许直接比较数据流中的记录,无需把数据流反序列化为对象,这样避免了新建对象的额外开销。RawComparator定义如下,compare方法可以从每个字节数组b1和b2中读取给定起始位置(s1和s2)以及长度l1和l2的一个整数直接进行比较。

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

WritableComparator: 是 RawComparator 的一个通用实现,提供两个功能:提供了一个 RawComparator的comparea()的默认实现,该默认实现只是反序列化了键然后再比较,没有什么性能优势。其次、充当了 RawComaprator 实例的一个工厂方法。
当我们要实现自定key排序时(自定义分组),需要指定自己的排序规则。
如需要以StartEndDate为键且以开始时间分组,则需要自定义分组器:

   
   
  1. class MyGrouper implements RawComparator<StartEndDate> {
  2. @Override
  3. public int compare(StartEndDate o1, StartEndDate o2) {
  4. return ( int)(o1.getStartDate().getTime()- o2.getEndDate().getTime());
  5. }
  6. @Override
  7. public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
  8. int compareBytes = WritableComparator.compareBytes(b1, s1, 8, b2, s2, 8);
  9. return compareBytes;
  10. }
  11. }
然后在job中设置 
job.setGroupingComparatorClass(MyGrouper.class);
最好将equals和hashcode也进行重写:

   
   
  1. @Override
  2. public boolean equals(Object obj) {
  3. if(!(obj instanceof StartEndDate))
  4. return false;
  5. StartEndDate s = (StartEndDate)obj;
  6. return startDate.getTime()== s.startDate.getTime()&&endDate.getTime() == s.endDate.getTime();
  7. }
  8. @Override
  9. public int hashCode() {
  10. int result = 17; //任意素数
  11. result = 31*result +startDate.hashCode();
  12. result = 31*result +endDate.hashCode();
  13. return result;
  14. };

ps: equal 和 hashcode 方法中应该还要对成员变量判空,以后还需要修改。

参考资料:
《Hadoop权威指南》
《Hadoop技术内幕》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值