本文简单的介绍了序列化和反序列化的作用、实现方式以及实际的业务场景中如何选择。
Java 提供了一种对象序列化的机制。
该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
java中的序列化时transient变量(这个关键字的作用就是告知JAVA不可以被序列化)和静态变量不会被序列化
如果先序列化对象A后序列化B,那么在反序列化的时候一定记着JAVA规定先读到的对象是先被序列化的对象,不要先接收对象B,否则会报错。尤其在使用上面的Externalizable的时候一定要注意读取的先后顺序
实现序列化接口的对象并不强制声明唯一的serialVersionUID,是否声明serialVersionUID对于对象序列化的向上向下的兼容性有很大的影响
序列化算法一般会按步骤做如下事情:
对于序列化接口Serializable接口是一个标识接口,它的主要作用就是标识这个对象是可序列化的,jre对象在传输对象的时候会进行相关的封装。
类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流,它们包含序列化和反序列化对象的方法。ObjectOutputStream 类包含很多写方法来写各种数据类型,但是一个特别的方法例外:
public final void writeObject(Object x) throws IOException
上面的方法序列化一个对象,并将它发送到输出流。相似ObjectInputStream 类包含如下反序列化一个对象的方法:
public final Object readObject() throws IOException, ClassNotFoundException
该方法从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。
public class User implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private static String val = "1234";
private Integer id;
private String name;
public transient int SSN;
public int number;
public User() {
}
public void mailCheck() {
System.out.println("Mailing a check to " + name+ " " + name);
}
public class SerializeDemo {
public static void main(String[] args) {
SerializeDemo serializeDemo = new SerializeDemo();
serializeDemo.serialize();
serializeDemo.deSerialize();
}
public void serialize() {
User e = new User();
e.setName("Reyan");
e.SSN = 11122333;
e.number = 101;
try {
FileOutputStream fileOut = new FileOutputStream("user.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
System.out.println("序列化后的文件保存在:user.ser");
} catch (Exception i) {
i.printStackTrace();
}
}
public void deSerialize() {
FileInputStream fileIn = null;
try {
fileIn = new FileInputStream("user.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
User user = (User) in.readObject();
System.out.println(user.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author alanchan
*
*/
public class SerializeDemo2 {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Operate operate = new Operate();
User person = new User("alan", "123456", "20");
System.out.println("为序列化之前的相关数据如下:\n" + person.toString());
operate.serializable(person);
User newPerson = operate.deSerializable();
System.out.println("-------------------------------------------------------");
System.out.println("序列化之后的相关数据如下:\n" + newPerson.toString());
}
static class Operate {
/**
* 序列化方法
*
* @throws IOException
* @throws FileNotFoundException
*/
public void serializable(User person) throws FileNotFoundException, IOException {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("user.ser"));
outputStream.writeObject(person);
}
/**
* 反序列化的方法
*
* @throws IOException
* @throws FileNotFoundException
* @throws ClassNotFoundException
*/
public User deSerializable() throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
return (User) ois.readObject();
}
}
static class User implements Externalizable {
private static final long serialVersionUID = 1L;
String password;
String age;
String userName;
/**
* 需要有一个默认构造器,否则会报no valid constructor异常
*/
public User() {
}
public User(String password, String age, String userName) {
this.password = password;
this.age = age;
this.userName = userName;
}
@Override
public String toString() {
return "User{" + "password='" + password + '\'' + ", age='" + age + '\'' + ", userName='" + userName + '\'' + '}';
}
/**
* 在writeExternal()方法里定义了哪些属性可以序列化,哪些不可以序列化,
* 所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理
*
* @param out
* @throws IOException
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 增加一个新的对象
Date date = new Date();
out.writeObject(userName);
out.writeObject(password);
out.writeObject(date);
}
/**
* 在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列
*
* @param in
* @throws IOException
* @throws ClassNotFoundException
*/
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 注意这里的接受顺序是有限制的,否则的话会出错的
// 例如上面先write的是A对象的话,那么下面先接受的也一定是A对象...
userName = (String) in.readObject();
password = (String) in.readObject();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = (Date) in.readObject();
System.out.println("反序列化后的日期为:" + sdf.format(date));
}
}
}
这里采用JSON格式同时使用Google的gson进行转义
Gson gson = new Gson();
//转成json
String json = gson.toJson(obj);
//解析成对象,可以强制转换成需要的对象
Object obj= gson.toJson(json);
Protobuf是一个纯粹的展示层协议,可以和各种传输层协议一起使用;Protobuf的文档也非常完善。 但是由于Protobuf产生于Google,所以目前其仅仅支持Java、C#典型应用场景和非应用场景 Protobuf具有广泛的用户基础,空间开销小以及高解析性能是其亮点,非常适合于公司内部的对性能要求高的RPC调用。
由于Protobuf提供了标准的IDL以及对应的编译器,其IDL文件是参与各方的非常强的业务约束,另外,Protobuf与传输层无关,采用HTTP具有良好的跨防火墙的访问属性,所以Protobuf也适用于公司间对性能要求比较高的场景。
由于其解析性能高,序列化后数据量相对少,非常适合应用层对象的持久化场景。
三种方式对比传输同样的数据,google protobuf只有53个字节是最少的。
XML是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。
SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于XML为序列化和反序列化协议的结构化消息传递协议。
Thrift是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。 Thrift并不仅仅是序列化协议,而是一个RPC框架。相对于JSON和XML而言,Thrift在空间开销和解析性能上有了比较大的提升,对于对性能要求比较高的分布式系统,它是一个优秀的RPC解决方案;但是由于Thrift的序列化被嵌入到Thrift框架里面,Thrift框架本身并没有透出序列化和反序列化接口,这导致其很难和其他传输层协议共同使用(例如HTTP)。
Avro提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面可以和Protobuf媲美,JSON格式方便测试阶段的调试。
hadoop有自己的序列化实现方式。
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.mapreduce.lib.db.DBWritable;
import lombok.Data;
/**
* @author alanchan
* 实现Hadoop序列化接口Writable 从数据库读取/写入数据库的对象应实现DBWritable
*/
@Data
public class User implements Writable, DBWritable {
private int id;
private String userName;
private String password;
private String phone;
private String email;
private String createDay;
@Override
public void write(PreparedStatement ps) throws SQLException {
ps.setInt(1, id);
ps.setString(2, userName);
ps.setString(3, password);
ps.setString(4, phone);
ps.setString(5, email);
ps.setString(6, createDay);
}
@Override
public void readFields(ResultSet rs) throws SQLException {
this.id = rs.getInt(1);
this.userName = rs.getString(2);
this.password = rs.getString(3);
this.phone = rs.getString(4);
this.email = rs.getString(5);
// this.createDay = rs.getDate(6);
this.createDay = rs.getString(6);
}
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(id);
out.writeUTF(userName);
out.writeUTF(password);
out.writeUTF(phone);
out.writeUTF(email);
out.writeUTF(createDay);
}
@Override
public void readFields(DataInput in) throws IOException {
id = in.readInt();
userName = in.readUTF();
password = in.readUTF();
phone = in.readUTF();
email = in.readUTF();
createDay = in.readUTF();
}
}
以上描述的几种序列化和反序列化协议都各自具有相应的特点,适用于不同的场景。
以上,简单的介绍了序列化和反序列化的作用、实现方式以及实际的业务场景中如何选择。