Hadoop Serialization hadoop序列化详解(最新版) (1)【java和hadoop序列化比较和writable接口】

初学java的人确定对java序列化记忆犹新。最开始不少人并不会一会儿理解序列化的意义所在。这样子是由于不少人仍是对java最底层的特性不是特别理解,当你经验丰富,对java理解更加深入以后,你就会发现序列化这种东西的精髓。

谈hadoop序列化以前,咱们再来回顾一下java的序列化,也是最底层的序列化:

在面向对象程序设计中,类是个很重要的概念。所谓“类”,能够将它想像成建筑图纸,而对象就是根据图纸盖的大楼。类,规定了对象的一切。根据建筑图纸造房子,盖出来的就是大楼,等同于将类进行实例化,获得的就是对象。
   
 一开始,在源代码里,类的定义是明确的,但对象的行为有些地方是明确的,有些地方是不明确的。对象里不明确地方,是由于对象在运行的时候,须要处理没法预测的事情,诸如用户点了下屏幕,用户点了下按钮,输入点东西,或者须要从网络发送接收数据之类的。后来,引入了泛型的概念以后,类也开始不明确了,若是使用了泛型,直到程序运行的时候,才知道到底是哪一种对象须要处理。
对象能够很复杂,也能够跟时序相关。通常来讲,“活的”对象只生存在内存里,关机断电就没有了。通常来讲,“活的”对象只能由本地的进程使用,不能被发送到网络上的另一台计算机。想象一下,对象怎么出现的,通常是new出来的,new出来的对象在内存里面,另外的计算机怎么可能使用我这台机器上的对象呢?若是要的话,序列化就是你必需要使用的东西。
 序列化,能够存储“活的”对象,能够将“活的”对象发送到远程计算机。
 把“活的”对象序列化,就是把“活的”对象转化成一串字节,而“反序列化”,就是从一串字节里解析出“活的”对象。因而,若是想把“活的”对象存储到文件,存储这串字节便可,若是想把“活的”对象发送到远程主机,发送这串字节便可,须要对象的时候,作一下反序列化,就能将对象“复活”了。
有例子为证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public  class  Person  implements  Serializable {
 
     private  String name =  null ;
 
     private  Integer age =  null ;
 
     private  Gender gender =  null ;
 
     public  Person() {
         System.out.println( "none-arg constructor" );
     }
 
     public  Person(String name, Integer age, Gender gender) {
         System.out.println( "arg constructor" );
         this .name = name;
         this .age = age;
         this .gender = gender;
     }
 
     public  String getName() {
         return  name;
     }
 
     public  void  setName(String name) {
         this .name = name;
     }
 
     public  Integer getAge() {
         return  age;
     }
 
     public  void  setAge(Integer age) {
         this .age = age;
     }
 
     public  Gender getGender() {
         return  gender;
     }
 
     public  void  setGender(Gender gender) {
         this .gender = gender;
     }
 
     @Override
     public  String toString() {
         return  "["  + name +  ", "  + age +  ", "  + gender +  "]" ;
     }
}
   SimpleSerial,是一个简单的序列化程序,它先将一个Person对象保存到文件person.out中,而后再从该文件中读出被存储的Person对象,并打印该对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public   class   SimpleSerial {
 
     public  static  void  main(String[] args)  throws  Exception {
         File file =  new  File( "person.out" );
 
         ObjectOutputStream oout =  new  ObjectOutputStream( new  FileOutputStream(file));
         Person person =  new  Person( "John" 101 , Gender.MALE);
         oout.writeObject(person);
         oout.close();
 
         ObjectInputStream oin =  new  ObjectInputStream( new  FileInputStream(file));
         Object newPerson = oin.readObject();  // 没有强制转换到Person类型
         oin.close();
         System.out.println(newPerson);
     }
}


上述程序的输出的结果为:
arg constructor
[John, 31, MALE]
这就是序列化。序列化的对象,他们超越了JVM的生死,不顾生他们的母亲,化做永恒。

 将对象序列化存储到文件,术语又叫“持久化”。将对象序列化发送到远程计算机,术语又叫“数据通讯”。
 Java对序列化提供了很是方便的支持,在定义类的时候,若是想让对象能够被序列化,只要在类的定义上加上了”implements Serializable”便可,好比说,能够这么定义”public class Building implements Serializable”,其余什么都不要作,Java会自动的处理相关一切。Java的序列化机制至关复杂,能处理各类对象关系。

那么序列化这种操做咱们怎么衡量他好很差呢?主要是压缩,速度,扩张,兼容四方面考虑。

hadoop通信格式需求

hadoop在节点间的内部通信使用的是RPC,RPC协议把消息翻译成二进制字节流发送到远程节点,远程节点再经过反序列化把二进制流转成原始的信息。RPC的序列化须要实现如下几点:
1.压缩,能够起到压缩的效果,占用的宽带资源要小。
2.快速,内部进程为分布式系统构建了高速链路,所以在序列化和反序列化间必须是快速的,不能让传输速度成为瓶颈。
3.可扩展的,新的服务端为新的客户端增长了一个参数,老客户端照样可使用。
4.兼容性好,能够支持多个语言的客户端

hadoop存储格式需求

表面上看来序列化框架在持久化存储方面可能须要其余的一些特性,但事实上依然是那四点:
1.压缩,占用的空间更小
2.快速,能够快速读写
3.可扩展,能够以老格式读取老数据
4.兼容性好,能够支持多种语言的读写



 Java的序列化机制的缺点就是计算量开销大,且序列化的结果体积大太,有时能达到对象大小的数倍乃至十倍。它的引用机制也会致使大文件不能分割的问题。这些缺点使得Java的序列化机制对Hadoop来讲是不合适的。因而Hadoop根据本身上门的需求设计了本身的序列化机制。

Hadoop 使用本身的序列化格式Writables ,它紧凑、快速(但不容易扩展或lava 之外的语言)。因为Writables 是Hadoop 的核心(MapReduce 程序使用它来序列化键/值对),因此在转而简要讨论其余几个有名的序列化框架(好比Apache 的Thrift 和谷歌的Google Protocol Buffers)以前.咱们先深刻探讨一下.

Hadoop经过Writable接口实现的序列化机制,不过没有提供比较功能,因此和java中的Comparable接口合并,提供一个接口WritableComparable。

Writable接口提供两个方法(write和readFields)。
1
2
3
4
5
package  org.apache.hadoop.io;
public  interface  Writable {
   void  write(DataOutput out)  throws  IOException;
   void  readFields(DataInput in)  throws  IOException;
}

WritableComparable实现Writable,Comparable接口

  1. package org.apache.hadoop.io;  
  2.   
  3. public interface WritableComparable <T>  extends org.apache.hadoop.io.Writable, java.lang.Comparable<T> {  
  4. }  

MapReduce在排序部分要根据key值的大小进行排序,所以类型的比较至关重要,RawComparator是Comparator的加强版。
hadoop中使用的key类型都要事先比较的接口。而且hashcode在hadoop区分keyd 时候会频繁使用,所以确保实现hashcode的输出在不一样jvm同样很重要。

  1. package org.apache.hadoop.io;  
  2.   
  3. public interface RawComparator <T>  extends java.util.Comparator<T> {  
  4.     int compare(byte[] bytes, int i, int i1, byte[] bytes1, int i2, int i3);  
  5. }  
s1 s2时开始位置,l1,l2是长度,读取以后比较。这个就不须要反序列化。

它能够作到,不先反序列化就能够直接比较二进制字节流的大小:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public  class  TestComparator {
 
     RawComparator<IntWritable> comparator;
     IntWritable w1;
     IntWritable w2;
 
     /**
      * 得到IntWritable的comparator,并初始化两个IntWritable
      */
     @Before
     public  void  init() {
         comparator = WritableComparator.get(IntWritable. class );
         w1 =  new  IntWritable( 163 );
         w2 =  new  IntWritable( 76 );
     }
 
     /**
      * 比较两个对象大小
      */
     @Test
     public  void  testComparator() {
         Assert.assertEquals(comparator.compare(w1, w2) >  0 true );
     }
 
     /**
      * 序列号后进行直接比较
      *
      * @throws IOException
      */
     @Test
     public  void  testcompare()  throws  IOException {
         byte [] b1 = serialize(w1);
         byte [] b2 = serialize(w2);
         Assert.assertTrue(comparator.compare(b1,  0 , b1.length, b2,  0 , b2.length) >  0 );
     }
 
     /**
      * 将一个实现了Writable接口的对象序列化成字节流
      *
      * @param writable
      * @return
      * @throws java.io.IOException
      */
     public  static  byte [] serialize(Writable writable)  throws  IOException {
         ByteArrayOutputStream out =  new  ByteArrayOutputStream();
         DataOutputStream dataOut =  new  DataOutputStream(out);
         writable.write(dataOut);
         dataOut.close();
         return  out.toByteArray();
     }
}

额外说一句,从git上下载的hadoop项目自己自带不少test类,你们能够多多尝试一下。



权威指南原文用intwritable 举例说明hadoop如何序列化:

1
2
3
4
5
6
7
  public  static  byte [] serialize(Writable writable)  throws  IOException {
         ByteArrayOutputStream out =  new  ByteArrayOutputStream();
         DataOutputStream dataOut =  new  DataOutputStream(out);
         writable.write(dataOut);
         dataOut.close();
         return  out.toByteArray();
     }
以及反序列化:
1
2
3
4
5
6
7
8
public  static  byte [] deserialize(Writable writable,  byte [] bytes)
throws  IOException {
ByteArrayInputStream in =  new  ByteArrayInputStream(bytes);
DataInputStream dataIn =  new  DataInputStream(in);
writable.readFields(dataIn);
dataIn.close();
return  bytes;
}

这是目前全部Writeable 的Java 基本类的封装(见表4-哟,此外还有short 和char 类型(二者都可存储在Int W rita ble 中)。它们都有用于检索和存储封装值的get () 和set ( )方撞.


类结构:


在对整数进行编码时,在固定长度格式。intWritable 和LongWritable)和可变长度格式(VlntWritable 和vLongWritable ) 之闹,有一个选择.若是值足够小(-112 和127 之问.包含这两个值) ,可变长度格式就只用一个字节来对值进行编码,不然,使用第一字节来表示值为正仍是负,以及后面还有多少字节。

如何在固定长度和可变长度编码之间进行选择?固定长度编码的好处在于值比较均匀地分布在整个值空间中,就像(精心设计)的散列函数。大多数数字变量每每分布, 因此可变长度编码每每更节省空间。可变长度编码的另外一个好处是能够将VintWritable 变为VLongWritable ,由于它们的编码其实是相同的. 所以,通过选择可变长度的编码方式,使空间能够增加,而不是一开始就占用8字节的空间。

权威指南对这些类都有一一介绍,在此再也不赘述。




Charles 2015-12-23 于P.P 



版权说明:
本文由Charles Dong原创,本人支持开源以及免费有益的传播,反对商业化谋利。
CSDN博客:http://blog.csdn.net/mrcharles
我的站:http://blog.xingbod.cn
EMAIL:charles@xingbod.cn
相关文章
相关标签/搜索