1. 序列化
在前面IO流的学习中,我们使用流对象读取和写入的数据类型都是数字、字符串等类型。那么如果想将对象保存到文件中,以及使用流来读取文件中保存的对象时,就需要使用Java中的序列化和反序列化机制:
- 序列化:用一个字节序列表示一个对象,该字节序列包含该对象的数据、类型和对象中存储的属性等信息,通过序列化可以持久化的保存对象
- 反序列化:根据文件中保存的字节序列,重构对象,将保存的对象的数据、类型和对象中存储的属性等信息读取出来
Java中使用ObjectOutputStream和ObjectInputStream来实现对象的序列化和反序列化。
2. ObjectOutputStream
ObjectOutputStream将Java对象的原始数据类型写出到文件中,从而实现对象的持久化存储。它是java.io.OutputStream的子类,因此,可以使用父类中共性的成员方法:
public void close()
:关闭此输出流并释放与此相关的任何系统资源public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写入public void write(byte[] b)
: 将b.length的字节从指定的字符数组写入此输出流public void write(byte[] b, int off, int len)
: 从指定的字节数组写入len长度的字节,从偏移量off开始输出到此输出流中public abstract void write(int b
):将指定的字节输出流
它的构造方法有:
ObjectOutputStream(OutputStream out)
:创建写入指定OutputStream的ObjectOutputStream
它有一个特有的成员方法:
void writeObject(Object obj)
:将指定的对象写入ObjectOutputStream
使用步骤:
- 创建ObjectOutputStream对象,构造方法中传递字节输出流
- 使用ObjectOutputStream对象的
writeObject()
将对象写入到文件中 - 释放资源
public class Person implements Serializable {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ObjectOutputStreamTest {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\data\\Code\\Java_code\\src\\IOStream\\test.txt"));
oos.writeObject(new Person(10, "Forlogen"));
oos.close();
}
}
Serializable接口,也叫标记型接口,要进行序列化和反序列化的类必须实现Serializable接口,它会给类添加一个标记。
public interface Serializable {
}
当进行序列化和反序列化时,就会检测类上是否有此标记
- 有则可正常序列化和反序列化
- 没有则抛出NotSerializableException异常
3. ObjectInputStream
ObjectInputStream是对象的反序列化流,它是java.io.InputStream的子类,因此可以使用父类中共性成员方法:
int read()
:从输入流中读取数据的下一个字节int read(byte[] b)
:从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中void close():
关闭此输入流并释放与该留相关的所有系统资源
构造方法:
ObjectInputStream(InputStream in)
:创建从指定InputStream读取的ObjectInputStream
特有的成员方法:
Object readObject()
:从ObjectInputStream中读取对象
使用步骤:
- 创建ObjectInputStream对象,构造方法中传递字节输入流
- 使用ObjectInputStream对象中的
readObject()
读取保存对象的文件 - 释放资源
- 使用读取出的对象
反序列化的前提:
- 类必须实现Serializable接口接口
- 必须存在类对应的.class文件
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectInoutStreamTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream( "D:\\data\\Code\\Java_code\\src\\IOStream\\test.txt"));
Object o = ois.readObject();
ois.close();
System.out.println(o); // Person{age=10, name='Forlogen'}
}
}
ClassNotFoundException会出现在反序列化的过程中,示意图如下所示:
为了避免异常的抛出,额可以显式的声明serialVersionUID,格式为:
static final long serialVersionUID = 42L
4. transient关键字
transient关键字也称为瞬态关键字,它用于序列化和反序列化是进行关键字的屏蔽,被此关键字修饰的成员变量就不进行序列化操作;同时在反序列化时也会忽视被它修饰的存变量。例如,如果上面的Person类中的age属性被transient关键字修饰,那么就行序列化->反序列化的操作后,输出的信息就是Person{age=0, name="Forlogen"}
,此时age会保持默认值。