1、介绍
1.1 什么是java序列化?
java序列化就是一种可以将java对象转换成可用于持久化的流的技术。我们可以利用序列化技术奖java对象直接保存在文件中,等要
用的时候在从文件里面取出来,然后反序列化一下,这样就可以将类还原了。
1.2 序列化的条件
1.2.1 实现Serializable接口
所有要用到序列化技术的类都必须实现Serializable这个接口,很奇怪的是这个接口没有任何方法,API中有描述:序列化接
口没有方法或字段,仅用于标识可序列化的语义。如果一个子类的父类实现了该接口,那么子类也同样拥有序列化的能力。
1.2.2 serialVersionUID
下面是API中对于这个字段的描述,我觉得很详细而且易懂:
序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过, 强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。
|
1.2.3 对于子类的序列化(这点是我自己试出来的)
假设B继承自A,那么对B进行序列化的时候,所有继承自A的属性的值会丢失。
2、使用
在使用序列化技术的时候一定会用到两个特殊的类:ObjectOutputStream和ObjectInputStream,这两个类可以将类/流转换成
流
/类进行输出/输入。
2.1 对象持久化
可以将一个java对象保存在文件里,等到要用的时候在反序列化一下。
2.1.1 序列化代码 (这里先不使用
serialVersionUID,请注意看注解)
public class Dog implements Serializable{
String name ;
public Dog(String name){
this.name = name ;
}
public static void main(String args[]){
Dog dog = new Dog("crazy");
//建立保存对象的文件
File file = new File("C:\\Users\\Administrator\\Desktop\\test.obj");
if(file.exists()){
file.delete();
}
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//使用ObjectOutputStream
ObjectOutputStream out = null ;
try {
/*
ObjectOutputStream将对象变成流,然后利用FileOutputStream写到文件里面。
因此,很容易想到,也可以利用socket的inputStream进行进程间的通信
*/
out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(dog);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
|
2.1.2 反序列化代码
public class Dog implements Serializable{
String name ;
public Dog(String name){
this.name = name ;
}
public static void main(String args[]){
//取得保存序列化的文件
File file = new File("C:\\Users\\Administrator\\Desktop\\test.obj");
//使用ObjectInputStream流来进行读入
ObjectInputStream in = null ;
try {
//从FileInputStream流里面读出数据,然后将其转换成对象
in = new ObjectInputStream(new FileInputStream(file));
Dog dog = (Dog) in.readObject();
System.out.println(dog.name);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
|
在上面的例子中我们没有使用
serialVersionUID,现在我利用之前保存的序列化对象,来看看serialVersionUID的作用
修改读取序列化对象的代码(加上serialVersionUID):
public class Dog implements Serializable{
static final long serialVersionUID = 2016041901 ;
String name ;
public Dog(String name){
this.name = name ;
}
public static void main(String args[]){
//取得保存序列化的文件
File file = new File("C:\\Users\\Administrator\\Desktop\\test.obj");
//使用ObjectInputStream流来进行读入
ObjectInputStream in = null ;
try {
//从FileInputStream流里面读出数据,然后将其转换成对象
in = new ObjectInputStream(new FileInputStream(file));
Dog dog = (Dog) in.readObject();
System.out.println(dog.name);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
|
java.io.InvalidClassException: com.example.BaseKnowledge.Dog; local class incompatible: stream classdesc serialVersionUID = 3079980272438232802, local class serialVersionUID = 2016041901
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1580)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1493)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1729)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1326)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:348)
at com.example.BaseKnowledge.Dog.main(Dog.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
|
象serialVersionUID 和现在对象的serialVersionUID 不同,就报错了。
2.2 进程间通信
2.2.1 ip+port
在网络中,利用ip地址可以定位一台主机,而利用ip地址加一个端口号可以定位一个程序,进程间就可以利用ip+port进行
socket通信。
2.2.2 利用socket传输序列化的对象
socket的使用方式这里就不赘述了,直接上代码
服务端代码:
public class Main {
public static void main(String args[]){
try {
ServerSocket serverSocket = new ServerSocket(10000);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Dog dog = null ;
dog = (Dog) objectInputStream.readObject();
System.out.println(dog.name);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
发送端代码:
public class Dog implements Serializable{
static final long serialVersionUID = 2016041901 ;
String name ;
public Dog(String name){
this.name = name ;
}
public static void main(String args[]){
ObjectOutputStream out = null ;
try {
Socket socket = new Socket("127.0.0.1",10000);
out = new ObjectOutputStream(socket.getOutputStream());
out.writeObject(new Dog("hello"));
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
|
其实和序列化对象持久化的原理一样,只是使用了socket进行流的传输而已。
NOTE:在反序列化的时候你必须保证在自己的工程中有相应的class文件才行。