Java IO流之序列化流【六】
1.概述
Java语言 提供了一种对象序列化的机制。用一个字节序列表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。
字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。
对象的数据、类型和存储的数据信息,都可以用来在内存中创建对象。
2.ObjectOutputStream类
java.io.ObjectOutputStream
类是对象的序列化流,可以将 Java 对象的基本数据类型和图形写入 OutputStream。
可以使用 ObjectInputStream
读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。
如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
只能将支持java.io.Serializable
接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。
writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。
2.1 重要方法
public ObjectOutputStream(OutputStream out) :创建写入指定 OutputStream 的 ObjectOutputStream对象。
public final void writeObject(Object obj):将对象写入 ObjectOutputStream。对象的类、类的签名,以及类及其所有超类型的非瞬态和非静态字段的值都将被写入。
2.2序列化操作
一个对象要想序列化,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口,该接口是一个标记接口,不实现此接口的类将不能进行序列化或反序列化,会抛出NotSerializableException 。 - 如果类中有属性不需要可序列化,则该属性必须注明是瞬态的,即使用
transient
关键字修饰。
补充:
- static:静态关键字,优先于对象进入内存,而序列化的都是对象,所以被其修饰的变量或方法不能被序列化。
- transient:瞬态关键字,被其修饰的成员变量不能被序列化
2.3 简单代码演示
案例:将自定义的Hero类的示例进行序列化,并保存再项目路径下的hero.txt中。
代码演示:
package com.hanyxx.io;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @author layman
*/
public class Demo10 {
public static void main(String[] args) throws IOException {
Hero hero = new Hero("葫芦娃",3);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hero.txt"));
// 写出对象
oos.writeObject(hero);
oos.close();
}
}
class Hero implements Serializable{
public String name;
public Integer age;
// 被transient修饰的变量不能被序列化transient
public transient String address;
// 被static修饰的变量不能被序列化
public static String mail;
public Hero(String name,Integer age){
this.name = name;
this.age = age;
this.address = "中国香港";
this.mail = "layman@111.com";
}
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
", mail='" + mail + '\'' +
'}';
}
}
3.ObjectOutputStream类
java.io.ObjectInputStream
类可以对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
ObjectOutputStream 和 ObjectInputStream 分别与FileOutputStream
和 FileInputStream
一起使用时,可以为应用程序提供对对象图形的持久存储。ObjectInputStream 用于恢复那些以前序列化的对象。其他用途包括使用套接字流在主机之间传递对象,或者用于编组和解组远程通信系统中的实参和形参。
只有支持 java.io.Serializable
或 java.io.Externalizable
接口的对象才能从流读取。
readObject
方法用于从流读取对象。应该使用 Java 的安全强制转换来获取所需的类型。在 Java 中,字符串和数组都是对象,所以在序列化期间将其视为对象。读取时,需要将其强制转换为期望的类型。
3.1 常用方法
public ObjectInputStream(InputStream in) :创建从指定 InputStream 读取的 ObjectInputStream。
public final Object readObject():从 ObjectInputStream 读取对象。
3.2 简单代码演示
案例:将前文序列化保存的对象文件,hero.txt进行反序列化。
代码演示:
package com.hanyxx.io;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* @author layman
*/
public class Demo11 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hero.txt"));
// 读取对象
Object object = ois.readObject();
Hero hero = null;
if(object instanceof Hero){
hero = (Hero)object;
}
System.out.println(hero);
}
}
运行结果:
Hero{name='葫芦娃', age=3, address='null', mail='null'}
4.序列化失败
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常。
另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。
发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
4.1 serialVersionUID
serialVersionUID
:Serializable 接口提供的序列版本号,目的在于验证序列化的对象和对应类是否版本匹配。
API文档说明
序列化运行时使用 serialVersionUID (版本号)与可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException
。
可序列化类可以通过声明名为 “serialVersionUID” 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:
private static final long serialVersionUID = 1L; //指定类的版本序列号
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java™ 对象序列化规范”中所述。
不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。
因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 – serialVersionUID 字段作为继承成员没有用处。
总结
序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。
为什么要把Java对象序列化呢?
因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,
这样,就相当于把Java对象存储到文件或者通过网络传输出去了。
反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。
有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象。