Java IO. 对象流
对象流: ObjectInputStream和OjbectOutputSteam。是一种用于读取和储存基本数据类型数据或对象的处理流。它可以把Java中的对象写出到数据源中,也可以从数据源中读取数据还原为java对象。
1、对象序列化与反序列化
对象序列化的目的是将对象保存到数据源(如磁盘)中,或者在网络中直接传输对象。对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流(不论从磁盘还是网络获取的) ,都可以将这种二进制流恢复成原来的 Java 对象。
序列化: 用ObjectOutputStream类保存基本类型数据或对象的机制。
反序列化: 用ObjectInputStream类读取基本类型数据或对象的机制。
序列化的好处是可将任何实现了Serializable接口的对象转化为字节数据,使其在存储和传输时能够被还原。
static和transient修饰的成员变量不能被ObjectOutputStream和ObjectInputStream序列化。
一个类如果是可序列化的,这个类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常
- Serializable
- Externalizable
并且必须让其对象所属的类及其属性也是可序列化的。(这个类的引用类型属性必须是可序列化的,否则拥有该类型的Field 的类也不能序列化)
例如对一个Person类进行序列化与反序列化:
import java.io.*;
public class ObjectStreamTest {
private static class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
//将对象写出到数据源
private static void writeObject() {
ObjectOutputStream oos = null;
try {
//1.创造流
oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
//2.造对象
oos.writeObject(new Person("张三", 25));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null){
//3.关闭流
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//从数据源读取对象
private static void readObject() {
ObjectInputStream ois = null;
try {
//1.创造流
ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
//2.读取对象
Person p = (Person) ois.readObject();
System.out.println(p.getName() + "-->" + p.getAge());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
//3.关闭流
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
writeObject();
readObject();
}
}
运行结果:
带有引用变量的对象序列化
这个类的引用类型属性必须是可序列化的,还是以Person类为例,添加了Job属性之后:
import java.io.*;
public class ObjectStreamTest {
//测试类需要序列化
private static class Person implements Serializable {
private String name;
private int age;
private Job job;
public Person(String name, int age, Job job) {
this.name = name;
this.age = age;
this.job = job;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Job getJob() {
return job;
}
}
//引用类型属性也必须序列化
private static class Job implements Serializable {
private String jobName;
private double salary;
public Job(String name, double salary) {
this.jobName = name;
this.salary = salary;
}
public String getJobName() {
return jobName;
}
public double getSalary(){
return salary;
}
}
//将对象写出到数据源
private static void writeObject() {
ObjectOutputStream oos = null;
try {
//1.创造流
oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
//2.造对象
oos.writeObject(new Person("张三", 25,new Job("医生",8000)));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null){
//3.关闭流
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//从数据源读取对象
private static void readObject() {
ObjectInputStream ois = null;
try {
//1.创造流
ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
//2.读取对象
Person p = (Person) ois.readObject();
System.out.println(p.getName() + "-->" + p.getAge() + "-->" + p.getJob().getJobName());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
//3.关闭流
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
writeObject();
readObject();
}
}
运行结果:
2、serialVersionUID 和 transient
- transient: 该关键字可以使属性不会被序列化。
例如ArrayList 中存储数据的数组 elementData 是用 transient 修饰的。
private transient Object[] elementData;
因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
- serialVersionUID:凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。
private static final long serialVersionUID
serialVersionUID用来表明类的不同版本间的兼容性。其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。如果类没有显式定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。所以最好显示声明。
总之,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则会产生序列化版本不一致InvalidCastException。
还是以之前的Person类为例,当我们想隐藏对象的年龄时,我们可以在age前添加transient:
public class ObjectStreamTest {
private static class Person implements Serializable {
private static final long serialVersionUID = -7867359117310966094L;
private String name;
private transient int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
//将对象写出到数据源
private static void writeObject() {
ObjectOutputStream oos = null;
try {
//1.创造流
oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
//2.造对象
oos.writeObject(new Person("张三", 25));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null){
//3.关闭流
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//从数据源读取对象
private static void readObject() {
ObjectInputStream ois = null;
try {
//1.创造流
ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
//2.读取对象
Person p = (Person) ois.readObject();
System.out.println(p.getName() + "-->" + p.getAge());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
//3.关闭流
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
//writeObject();
readObject();
}
}
运行结果:
age为int类型,隐藏后初始为0
然而如果我们不显式声明serialVersionUID,而加上了在原代码进行了修改(加上了transient),反序列化进行读取则会产生异常:
public class ObjectStreamTest {
private static class Person implements Serializable {
//private static final long serialVersionUID = -7867359117310966094L;
private String name;
private transient int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
//将对象写出到数据源
private static void writeObject() {
ObjectOutputStream oos = null;
try {
//1.创造流
oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
//2.造对象
oos.writeObject(new Person("张三", 25));
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null){
//3.关闭流
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//从数据源读取对象
private static void readObject() {
ObjectInputStream ois = null;
try {
//1.创造流
ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\test.txt"));
//2.读取对象
Person p = (Person) ois.readObject();
System.out.println(p.getName() + "-->" + p.getAge());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
//3.关闭流
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
//writeObject();
readObject();
}
}
运行结果: