什么是序列化,为什么要序列化 - HbnKing/JDK GitHub Wiki

在日常项目中,我们经常都会和序列化三个字打交道。常用的各种库和语言都集成了序列化的功能。

c++ 的Boost库 java ,c# 都实现了此功能,程序设计者只需要调用接口选择要序列化的数据即可序列化 反序列化,将内存中的数据转化为二进制数据存储进本地。

那么问题就来了:

对象在内存中的数据本来就是二进制的啊。我们直接拷贝对象对象在内存中的数据写入本地磁盘即可啊。那为什么我们还需要序列化框架来做这件事情。

网上关于序列化的介绍都直说 : 序列化是为了持久化内存中的对象。但并没有介绍为什么不直接拷贝内存中的对象。所以只有自己去领会其中的精髓。

这是本人的关于为什么不直接拷贝内存中的数据来实现持久化的一些见解,如有错误,欢迎指正:

首先来对比两种实现方式功能完备性:

1

手动拷贝内存数据 框架提供的序列化机制 持久化 ok ok 筛选某些属性不存储 no ok 防止重复存储 no ok 筛选某些属性不存储:比如一个对象里你创建了一个缓存对象用于缓存某些数据,这些缓存数据在每次功能使用的时候才生效,所以存入本地并无意义,为了节省硬盘空间,应该在序列化的时候将其去掉。

防止重复储存: 相同的对象总是不希望重复存储的 。即使有改变,也应该在原来存储的地方修改对应的值就可以了。

由此可见:

    序列化所做的工作除了将数据以二进制存入本地外,还要提供筛选数据,防止重复存储等功能,而这些功能的实现就要求用户告诉功能设计者要存储的对象有哪些希望序列化的数据,基于这种设计理念,最终形成了大多数框架所采用的:用户在接口内输入需要序列化的数据及策略 ,框架将其序列化。

总结:

问:为什么要序列化。

答: 为了持久化内存数据和传输内存数据

问: 为什么要采用框架提供的序列化,直接拷贝内存数据不就行了么。

答:直接拷贝内存数据无法达到如防止重复存储,筛选数据等功能。

详解 如下

简单来说序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象传输于网络必须进行流化)!在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!
问题的引出:
如上所述,读写对象会有什么问题呢?比如:我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗?别急,其中一个最大的问题就是对象引用!举个例子来说:假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b,接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为对象b包含对对象a的引用,所以系统会自动的将a的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的!
以下序列化机制的解决方案:
1.保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等)
2.当要保存一个对象时,先检查该对象是否被保存了。
3.如果以前保存过,只需写入"与已经保存的具有序列号x的对象相同"的标记,否则,保存该对象
通过以上的步骤序列化机制解决了对象引用的问题!
序列化的实现
将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的 writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
在序列化的过程中,有些数据字段我们不想将其序列化,对于此类字段我们只需要在定义时给它加上transient关键字即可,对于transient字段序列化机制会跳过不会将其写入文件,当然也不可被恢复。但有时我们想将某一字段序列化,但它在SDK中的定义却是不可序列化的类型,这样的话我们也必须把他标注为transient,可是不能写入又怎么恢复呢?好在序列化机制为包含这种特殊问题的类提供了如下的方法定义:

private void readObject(ObjectInputStream in) throws
IOException, ClassNotFoundException;
private void writeObject(ObjectOutputStream out) throws
IOException;

(注:这些方法定义时必须是私有的,因为不需要你显示调用,序列化机制会自动调用的) 使用以上方法我们可以手动对那些你又想序列化又不可以被序列化的数据字段进行写出和读入操作。 下面是一个典型的例子,java.awt.geom包中的Point2D.Double类就是不可序列化的,因为该类没有实现Serializable接口,在我的例子中将把它当作LabeledPoint类中的一个数据字段,并演示如何将其序列化!

import java.io.*;
import java.awt.geom.*;
public class TransientTest
{
         public static void main(String[] args)
         {
                 LabeledPoint label = new LabeledPoint("Book", 5.00, 5.00);
                 try
                 {
                         System.out.println(label);// 写入前
                         ObjectOutputStream out = new ObjectOutputStream(new
                         FileOutputStream("Label.txt"));
                         out.writeObject(label);
                         out.close();
                         System.out.println(label);// 写入后
                         ObjectInputStream in = new ObjectInputStream(new
                         FileInputStream("Label.txt"));
                         LabeledPoint label1 = (LabeledPoint) in.readObject();
                         in.close();
                         System.out.println(label1);// 读出并加1.0后
                 }
                 catch (Exception e)
                 {
                         e.printStackTrace();
                 }
         }
}
class LabeledPoint implements Serializable
{
         public LabeledPoint(String str, double x, double y)
         {
                 label = str;
                 point = new Point2D.Double(x, y);
         }
         private void writeObject(ObjectOutputStream out) throws IOException
         {
                
                 out.defaultWriteObject();
                 out.writeDouble(point.getX());
                 out.writeDouble(point.getY());
         }
         private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
         {
                
                 in.defaultReadObject();
                 double x = in.readDouble() + 1.0;
                 double y = in.readDouble() + 1.0;
                 point = new Point2D.Double(x, y);
         }
         public String toString()
         {
                 return getClass().getName()+ "[label = " + label+ ", point.getX() = " + point.getX()+ ", point.getY() = " + point.getY()+ "]";
         }
         private String label;
         transient private Point2D.Double point;
}