深入浅析Java Object Serialization与 Hadoop 序列化
作者:杨jun坚
一,Java Object Serialization
1,什么是序列化(Serialization)
序列化是指将结构化对象转化为字节流以便在网络上传输或者写到磁盘永久存储的过程。反序列化指将字节流转回结构化对象的逆过程。简单的理解就是对象转换为字节流用来传输和保存,字节流转换为对象将对象恢复成原来的状态。
2,序列化(Serialization)的作用
(1)一种持久化机制,把的内存中的对象状态保存到一个文件中或者数据库。
(2)一种通信机制,用套接字在网络上传送对象。
(3)Java远程方法调用(RMI)需要调用对象时,
3,实现了Serializable接口的对象的序列化
在java.io包中,接口Serialization用来作为实现对象串行化的工具 ,只有实现了Serialization的类的对象才可以被序列化。 Serializable接口中没有任何的方法,当一个类声明要实现Serializable接口时,只是表明该类参加序列化协议,而不需要实现任何特殊的方法。
要序列化一个对象,首先要创建OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。此时,调用writeObject()方法将对象序列化并发送给OutputStream。在反序列化时,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject(),得到的结果是一个Object对象,需要进行转型得到最后所需的对象。需要注意的是,在对一个Serializable对象进行反序列化的过程中,没有调用任何构造器,包括缺省的构造器,整个对象都是通过从InputStream中取得数据恢复过来的。对象序列化是面向字节的,因此采用InputStream和OutputStream层次结构。
对Student序列化
package cn.test.serializable; /** * 序列化对象实现Serializable接口 * @author Young * created on 2017-5-25 */ import java.io.Serializable; public class Student implements Serializable { private String id; private String name; public Student (String id,String name){ this.id=id; this.name=name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
序列化
package cn.test.serializable; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; /** * 序列化 * @author Young * created on 2017-5-25 */ public class TestSerializable { public static void main(String[] args) { // TODO Auto-generated method stub Student stu=new Student("201441413110","yang"); try { FileOutputStream out=new FileOutputStream("d:\\student");//创建OutputStream对象 ObjectOutputStream ob=new ObjectOutputStream(out);//封装在一个ObjectOutputStream对象内 ob.writeObject(stu);//调用writeObject()方法将对象序列化并发送给OutputStream,保存在d:\\student中 ob.close(); out.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
反序列化
package cn.test.serializable; /** * 反序列化 * @author Young * created on 2017-5-25 */ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; public class TestDeserializable { public static void main(String[] args) { // TODO Auto-generated method stub FileInputStream in; Student stu; try { in = new FileInputStream("d:\\student"); ObjectInputStream ob=new ObjectInputStream(in);//InputStream封装在ObjectInputStream内 stu=(Student) ob.readObject();//调用readObject(),得到的结果是一个Object对象,需要进行转型得到最后所需的对象. ob.close(); in.close(); System.out.println(stu.getId()); System.out.println(stu.getName()); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
序列化机制写到流中的数据有
1,声明和标记,比如声明使用了序列化协议、序列化协议版本、声明这是一个新的对象、声明这里开始一个新Class、结束标记等。
2,对象所属的类 ,类的长度,
3,SerialVersionUID, 序列化ID,如果没有指定,则会由算法随机生成一个8byte的ID。
4,所有的非transient和非static的属性的个数以及名称。名称长度,属性的值。
这只是简单对象序列化后写到流中的数据。如果是复杂对象序列化就会包含更多的信息。比如引用其他对象作为成员变量,这是会将引用的对象也进行序列化。还有序列化时,只对对象的状态进行保存,而不管对象的方法;当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化。
二,Hadoop 序列化
hadoop在节点间的内部通讯使用的是RPC,RPC协议把消息翻译成二进制字节流发送到远程节点,远程节点再通过反序列化把二进制流转成原始的信息。
Hadoop使用自己的序列化格式Writable,它绝对紧凑、高速,但不太容易用Java以外的语言进行拓展和使用。
1,Writable接口
Writable接口定义了两个方法:一个将其状态写到DataOutput二进制流,另一个从DataInput二进制流读取其状态:
实现代码
package Hadoop.writable; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Writable; /** * hadoop 序列化与反序列化 * @author Young * created by 2017-6-9 */ public class Serialize { public static byte[] serialize(Writable writable) throws IOException{ ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream dataout = new DataOutputStream(out); try { writable.write(dataout); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ dataout.close(); } return out.toByteArray(); } public static byte[] deserialize(Writable writable ,byte[] bytes) throws IOException{ ByteArrayInputStream in = new ByteArrayInputStream(bytes); DataInputStream datain = new DataInputStream(in); try { writable.readFields(datain); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ datain.close(); } return bytes; } public static void main(String[] args) throws IOException { // TODO Auto-generated method stub IntWritable intwritable = new IntWritable(20); IntWritable newwriatble =new IntWritable(); byte[] bytes=Serialize.serialize(intwritable); deserialize(newwriatble,bytes); System.out.println(newwriatble.get()); } }
2,Hadoop序列化机制中还包含另外几个重要的接口:WritableComparable、RawComparator 和 WritableComparator
WritableComparable提供类型比较的能力,继承自Writable接口和Comparable接口,其中Comparable进行 类型比较。ByteWritable、IntWritable、DoubleWritable等java基本类型对应的Writable类型,都继承自 WritableComparable。
WritableComparable接口
package org.apache.hadoop.io; import org.apache.hadoop.classification.InterfaceAudience.Public; import org.apache.hadoop.classification.InterfaceStability.Stable; import org.apache.hadoop.io.Writable; @Public @Stable public interface WritableComparable<T> extends Writable, Comparable<T> { }
对于MapReduce来说,类型的比较非常重要,因为中间有个基于键的排序阶段。Hadoop提供了一个具有高效比较能力的RawComparator接口。
RawComparator接口
package org.apache.hadoop.io; import java.util.Comparator; import org.apache.hadoop.classification.InterfaceAudience.Public; import org.apache.hadoop.classification.InterfaceStability.Stable; @Public @Stable public interface RawComparator<T> extends Comparator<T> { int compare(byte[] arg0, int arg1, int arg2, byte[] arg3, int arg4, int arg5); }
该接口允许其实现直接比较数据流中的记录,无须先把数据流反序列化为对象,这样避免了新建对象而外的开销。
WritableComparator 是对继承自WritableComparable类的RawComparator类的一个通用实现。提供两个主要功能。1,提供对原始的compare()方法的一个默认实现,该方法能够反序列化将在流中进行比较的对象,并调用对象的compare()方法。2,它充当的是RawComparator实例的工厂,例如为了获得IntWritable的comparator,可以直接调用
RawComparator<IntWritable>comparator=WritableComparator.get(IntWritable.class);
下面是《Hadoop权威指南》的例子
package cn.serialization; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.RawComparator; import org.apache.hadoop.io.Writable; import org.apache.hadoop.io.WritableComparator; public class TestWritable { //序列化 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(); } //反序列化 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; } public static void main(String[] args) throws IOException { @SuppressWarnings("unchecked") RawComparator <IntWritable> comparator = WritableComparator.get(IntWritable.class); IntWritable w1 = new IntWritable(163); IntWritable w2 = new IntWritable(67); byte[] b1 = serialize(w1); byte[] b2 = serialize(w2); System.out.println(b1); System.out.println(b2); System.out.println(comparator.compare(w1, w2));//WritableComparator的 compare(),w1>w2 -> 1;w1<w2 -> -1 ; w1=w2 -> 0 System.out.println(comparator.compare(b1,0,b1.length,b2,0,b2.length)); } }
三,Hadoop 序列化框架
尽管大多数MapReduce程序使用Writable类型的key,value,但是不是所有MapReduce API 强制使用。事实上,可以使用任何类型,只要能有一种机制将每个类型进行类型与二进制的来回转换。为此Hadoop提供了一个序列化框架来支持,他们在org.apache.hadoop.io.serializer包中,WritableSerialization类是对Writable类型的Serialization实现。
WritableSerialization类
public class WritableSerialization extends Configured implements Serialization<Writable> {...}
Serializer接口
定义了open()接口,序列化接口,close接口
public interface Serializer<T> { void open(OutputStream arg0) throws IOException; void serialize(T arg0) throws IOException; void close() throws IOException; }
Deserializer接口
定义了open()接口,反序列化接口,close接口
public interface Deserializer<T> { void open(InputStream arg0) throws IOException; T deserialize(T arg0) throws IOException; void close() throws IOException; }
Serialization接口
定义了一组接口,判断是否支持输入的类,根据输入的类给出序列化接口和反序列化接口
public interface Serialization<T> { boolean accept(Class<?> arg0); Serializer<T> getSerializer(Class<T> arg0); Deserializer<T> getDeserializer(Class<T> arg0); }
还有几个接口,可以查看API文档
尽管这可以方便我们在MapReduce使用Java类型,比如String,Integer。但是这还是不如Writable高效。
Hadoop中不使用java Object Serialization的原因
1,Java Object Serialization不够精简,就如本文前面提到的Java Object Serialization序列化后的字节流会包含许多信息,比如类名与对象等。
2,对象在序列化中只存引用,引用可以出现的位置很随机,既可以在序列化的对象前,也可以在其后面,这样就对随机访问和排序造成影响,一旦出错,整个后面的序列化就会全部错误,Writable支持随机访问和排序。因为流中的记录是彼此独立的。
3,.Java序列化每次反序列化都要重新创建对象,内存消耗大,而Writable是可以重用的。
以上所述是小编给大家介绍的Java Object Serialization与 Hadoop 序列化,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!