序列化是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列 的过程称为对象的序列化 反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程 成为对象的反序列化
public class User implements Serializable {
private static final long serialVersionUID = -434539422310062943L;
private String name;
private int age;
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeObject(name);
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
name=(String)s.readObject();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Server {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ServerSocket serverSocket=null;
serverSocket=new ServerSocket(8080);
Socket socket=serverSocket.accept(); //建立好连接
ObjectInputStream objectInputStream=
new ObjectInputStream(socket.getInputStream());
User user=(User)objectInputStream.readObject();
System.out.println(user);
}
}
public class Client {
public static void main(String[] args) throws IOException {
Socket socket=null;
socket=new Socket("localhost",8080);
User user=new User();
user.setAge(18);
user.setName("Mic");
ObjectOutputStream out=new ObjectOutputStream
(socket.getOutputStream());
out.writeObject(user);
socket.close();
}
}
java.io.ObjectOutputStream:表示对象输出流 , 它的 writeObject(Object obj)方法可以对参 数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream:表示对象输入流 ,它的 readObject()方法源输入流中读取字节序 列,再把它们反序列化成为一个对象,并将其返回
一是默认的 1L,比如:private static final long serialVersionUID = 1L;
二是根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段
当实现 java.io.Serializable 接口的类没有显式地定义一个 serialVersionUID 变量时候,Java 序 列化机制会根据编译的 Class 自动生成一个 serialVersionUID 作序列化版本比较用,这种情况 下,如果 Class 文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算 再编译多次,serialVersionUID 也不会变化的。
public interface ISerializer {
<T> byte[] serialize(T obj);
<T> T deserialize(byte[] data,Class<T> clazz);
}
public class JavaSerializer implements ISerializer{
@Override
public <T> byte[] serialize(T obj) {
ByteArrayOutputStream byteArrayOutputStream=
new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream=
new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(obj);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(data);
try {
ObjectInputStream objectInputStream=
new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
public class JavaSerializerWithFile implements ISerializer{
@Override
public <T> byte[] serialize(T obj) {
try {
ObjectOutputStream outputStream=
new ObjectOutputStream(new FileOutputStream
(new File("user")));
outputStream.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
try {
ObjectInputStream objectInputStream=
new ObjectInputStream(new FileInputStream
(new File("user")));
return (T) objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变 量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
虽然 name 被 transient 修饰,但是通过我们写的这两个方法依然能够使得 name 字段正确 被序列化和反序列化
writeObject 和 readObject 是两个私有的方法,他们是什么时候被调用的呢?从运行结果来 看,它确实被调用。而且他们并不存在于 Java.lang.Object,也没有在 Serializable 中去声明。 我们唯一的猜想应该还是和 ObjectInputStream 和 ObjectOutputStream 有关系,所以基于 这个入口去看看在哪个地方有调用
1.Java 序列化只是针对对象的状态进行保存,至于对象中的方法,序列化不关心
2.当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口
3.当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进 行序列化(实现深度克隆)
4.当某个字段被申明为 transient 后,默认的序列化机制会忽略这个字段
5.被申明为 transient 的字段,如果需要序列化,可以添加两个私有方法:writeObject 和 readObject
1.序列化的计算性能
2.数据包的大小
3.跨语言
1.序列化的数据比较大,传输效率低
2.其他语言无法识别和对接
public class XStreamSerializer implements ISerializer{
XStream xStream=new XStream(new DomDriver());
@Override
public <T> byte[] serialize(T obj) {
return xStream.toXML(obj).getBytes();
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
return (T)xStream.fromXML(new String(data));
}
}
public class SerialDemo {
public static void main(String[] args) {
// ISerializer iSerializer=new JavaSerializer();
// ISerializer iSerializer=new JavaSerializerWithFile();
// ISerializer iSerializer=new XStreamSerializer();
ISerializer iSerializer=new FastJsonSeriliazer();
// ISerializer iSerializer=new HessianSerializer();
User user=new User();// JVM内存中. 如何把内存中的对象进行网络传输.(实体)->字节序列
user.setAge(18);
user.setName("Mic");
//原生实现
byte[] bytes=iSerializer.serialize(user);
System.out.println("byte.length:"+bytes.length);
/**
* json: byte.leng 23
* xmlstream: byte.leng 198
* hessian: byte.length: 50
* java原生: byte.length: 92
* protobuf: byte.length:7
*/
System.out.println(new String(bytes));// byte长度 198 -> 23
User user1=iSerializer.deserialize(bytes,User.class);
System.out.println(user1);
}
}
public class FastJsonSeriliazer implements ISerializer{
@Override
public <T> byte[] serialize(T obj) {
return JSON.toJSONString(obj).getBytes();
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
return (T)JSON.parseObject(new String(data),clazz);
}
}
Hessian 是一个支持跨语言传输的二进制序列化协议,相对于 Java 默认的序列化机制来说,
Hessian 具有更好的性能和易用性,而且支持多种不同的语言 实际上 Dubbo 采用的就是 Hessian 序列化来实现,只不过 Dubbo 对 Hessian 进行了重构, 性能更高
public class HessianSerializer implements ISerializer{
@Override
public <T> byte[] serialize(T obj) {
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
HessianOutput hessianOutput=new HessianOutput(outputStream);
try {
hessianOutput.writeObject(obj);
return outputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
ByteArrayInputStream inputStream=new ByteArrayInputStream(data);
HessianInput hessianInput=new HessianInput(inputStream);
try {
return (T)hessianInput.readObject();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
public class ProtobufDemo {
public static void main(String[] args) {
UserProtos.User user=UserProtos.User.newBuilder().
setAge(300).setName("Mic").build();
byte[] bytes=user.toByteArray();
System.out.println(bytes.length);
for (int i = 0; i < bytes.length; i++) {
System.out.print(bytes[i]+" ");
}
//10 3 77 105 99 16 18
}
}
使用 protobuf 开发的一般步骤是
1.配置开发环境,安装 protocol compiler 代码编译器
2.编写.proto 文件,定义序列化对象的数据结构
3.基于编写的.proto 文件,使用 protocol compiler 编译器生成对应的序列化/反序列化工具 类
4.基于自动生成的代码,编写自己的序列化应用
字符如何转化为编码 “Mic”这个字符,需要根据 ASCII 对照表转化为数字。 M =77、i=105、c=99 所以结果为 77 105 99 大家肯定有个疑问,这里的结果为什么直接就是 ASCII 编码的值呢?怎么没有做压缩呢?有 没有同学能够回答出来 原因是,varint 是对字节码做压缩,但是如果这个数字的二进制只需要一个字节表示的时候, 其实最终编码出来的结果是不会变化的
Protocol Buffer 的性能好,主要体现在 序列化后的数据体积小 & 序列化速度快,最终使得 传输效率高,其原因如下:
序列化速度快的原因:
a.编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等)
b.采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成
序列化后的数据量体积小(即数据压缩效果好)的原因:
a. 采用了独特的编码方式,如 Varint、Zigzag 编码方式等等
b. 采用 T - L - V 的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑
技术层面
1.序列化空间开销,也就是序列化产生的结果大小,这个影响到传输的性能
2.序列化过程中消耗的时长,序列化消耗时间过长影响到业务的响应时间
3.序列化协议是否支持跨平台,跨语言。因为现在的架构更加灵活,如果存在异构系统通信 需求,那么这个是必须要考虑的
4.可扩展性/兼容性,在实际业务开发中,系统往往需要随着需求的快速迭代来实现快速更新, 这就要求我们采用的序列化协议基于良好的可扩展性/兼容性,比如在现有的序列化数据结 构中新增一个业务字段,不会影响到现有的服务
5.技术的流行程度,越流行的技术意味着使用的公司多,那么很多坑都已经淌过并且得到了解决,技术解决方案也相对成熟
6.学习难度和易用性
选型建议
1.对性能要求不高的场景,可以采用基于 XML 的 SOAP 协议
2.对性能和间接性有比较高要求的场景,那么 Hessian、Protobuf、Thrift、Avro 都可以。
3.基于前后端分离,或者独立的对外的 api 服务,选用 JSON 是比较好的,对于调试、可读 性都很不错
4.Avro 设计理念偏于动态类型语言,那么这类的场景使用 Avro 是可以的
各个序列化技术的性能比较 这个地址有针对不同序列化技术进行性能比较: https://github.com/eishay/jvm-serializers/wiki