1.什么是序列化?

百度百科:将对象的状态信息转换成可以存储和传输的形式的过程。

网友1: 把对象转换为字节序列的过程称为对象的序列化 ; 把字节序列恢复为对象的过程称为对象的反序列化 。

网友2:序列化就是一种处理对象流的机制。

对象流又是什么呢?

对象流也就是将对象的内容流化,流(I/O流)。

可以很方便的将流化后的对象进行网络传输。

当然,对象流进行读写时会出现“对象引用”问题,先来描述一下问题:

有两个类分别是A,B,B类中包含一个指向A类对象的引用,将这两个类实例化后,在内存中实际分配两块空间,分别保存这两个对象,

下面要将这两个对象保存到文件中,在写入文件时,由于b对象包含一个a对象的引用,系统会自动将a对象数据拷贝一份到b对象中,这样,

当要将文件中的内容重新加载到内存中时,内存中分配了3块空间,而a对象在内存中就会出现两份,如果要修改a对象,还要搜索每一份拷贝对象来进行数据一致,这显然不是我们所希望看到的。

怎么解决这个问题呢?

1.保存到磁盘的所有对象分配一个序列号。

2.当保存对象时,先检查该对象是否被保存了。

3,如果保存过,只需写入“与已保存的具有相同序列号xxx的对象相同的标记”,否则,保存该对象。

序列化实现:

将需要被实现·的类实现Serializable接口,改接口没有需要实现的方法,只是为了 标记该对象是可以被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutoutStream(对象流)。接着,使用ObjectOutputStream对象的writeObject(Object object)方法将参数为obj的对象写入,要恢复则使用输入流。

在序列化过程中,有些字段不想将其序列化,对于此类字段我们只需在定义时给它加上transient关键字,这样序列化机制就会跳过该字段不将其写入文件,当然也不可被恢复。

那怎样将不可序列化类型的字段进行序列化呢?

好在序列化机制为包含这种特殊问题的类提供了如下的方法定义:

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

(注:这些方法定义时必须是私有的,因为不需要你显示调用,序列化机制会自动调用的) 


java序列化注意事项:

序列化保存的是对象的状态,而静态变量是类的状态。

1、基本类型的数据可以直接序列化

2. 对象要被序列化,它的类必须要实现Serializable接口;如果一个类中有引用类型的实例变量,这个引用类型也要实现Serializable接口。

3、可以用这个代码:ObjectOutputStream out  = new ObjectOutputStream(new FileOutputStream("seria"));

但是不能用这个代码:ObjectOutputStream out  = new ObjectOutputStream(new FileOutputStream("seria",true));

4、如果对象的引用类型没有实现序列化,但是还是想将这个对象序列化,那么可以将对象中用到的那个引用类型对象设置为transient类型。

(如果A包含了对B的引用,那么在序列化A的时候也会将B一并地序列化;如果此时A可以序列化,B无法序列化,那么在序列化A的时候就会发生异常,这时就需要将对B的引用设为transient,该关键字表示变量不会被序列化。)

5、如果用transient修饰引用类型对象,则这个引用类型的对象不会保存,那么如果希望保存下来又如何实现呢?

private transient Book book;//用transient关键字声明book
 
  
 
   
 
  
     //这个方法会在序列化的过程中被调用   
 
  
         private void writeObject(ObjectOutputStream out){  
 
  
             try {  
 
  
                 out.defaultWriteObject(); //这个方法会把这当前中非静态变量和非transient变量写到流中  
 
  
                                           //在这里我们就把name写到了流中。  
 
  
                 //因为我们要保存Book的状态,所以我们还要想办法把其状态写入流中  
 
  
                 out.writeInt(book.getIsbn());//ObjectOutputStream中提供了写基本类型数据的方法  
 
  
                 //out.close();//注意,这句千万不能有,否刚将直接导致写入失败  
 
  
             } catch (IOException e) {  
 
  
                 e.printStackTrace();  
 
  
             }   
 
  
         }  
 
  
           
 
  
         //这个方法会在反序列化的过程中被调用  
 
  
         private void readObject(ObjectInputStream in){  
 
  
             try {  
 
  
                 in.defaultReadObject(); //和defaultWriteObject()方法相对应,默认的反序列化方法,会从流中读取  
 
  
                                         //非静态变量和非transient变量  
 
  
                 int isbn  = in.readInt(); //用这个方法来读取一个int型值,这里我们是读取书号  
 
  
                 book  = new Book(isbn); //这里我们就通过读取的 保存的状态构造 了一个和原来一样的Book对象  
 
  
                 //in.close();同样的这句也不能有  
 
  
             } catch (IOException e) {  
 
  
                 e.printStackTrace();  
 
  
             } catch (ClassNotFoundException e) {  
 
  
                 e.printStackTrace();  
 
  
             }  
 
  
         }

6、如果一个类没有实现Serializable接口,但是它的父类实现了,那么这个类也可以序列化。

7、如果一个类实现了Serializable接口,但是它的父类没有实现,那么这个类可以进行序列化吗?

答案是可以的,因为超类Object类没有实现Serializable接口,所以你懂了吧,但是会有个问题,你需要在父类中是实现默认的构造方法,否则会报异常:no validconstructor。


serialVersionUID的作用

Customer类实现Serializable接口,在没有指定serialVersionUID时,正常序列化,反序列化Customer类成功。

在修改Customer类后,反序列化报错,抛出异常:

  1. Exception in thread "main" java.io.InvalidClassException: Customer; local class incompatible:  
  2. stream classdesc serialVersionUID = -88175599799432325, local class serialVersionUID = -5182532647273106745 

意思是说,文件流中的class和classpath中修改后的class不兼容了,基于安全考虑,程序会抛出异常,并拒绝载入。

那如果真的要在序列化之后修改类呢?怎么办呢?

那就自己去指定一个serialVersionUID,如果没有指定serialVersionUID,java编译器会自动给这个class进行一个类似以指纹算法的摘要算法,只要这个文件多一个空格,得到的serialVersionUID就会完全不同,保证这个类的编号是唯一的。所以修改Customer类之后,没有指定serialVersionUID,编译器就会为我们重新生成一个UID,和之前保存时的UID不一致 了,于是就会出现序列化版本号不一致的情况。因此只要手动指定了serialVersionUID,就可以在序列化之后随意修改类的字段方法,而不会影响后期的还原,还原后的对象照样可以使用,并且多了我们修改后的字段和方法。

显式地定义serialVersionUID有两种用途:

1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。


常见的序列化应用场景:

1.当想把内存中的 对象写入到硬盘中时

2.当想用套接字在网络上传送对象的时候

3.当想通过RMI传输对象的时候


序列化的常见方式:

1.java通过实现Serializable接口来实现序列化

2.Json: javaScript Object Notation,一种轻量级数据交换格式(java和json可以相互转换)

3.Xml :可扩展标记语言。

4.Soap:SOAP是基于XML的数据结构和 超文本传输协议的组合定义一个标准方法来使用Internet上操作不同环境中的分布式对象。