文章目录
一、序列化和反序列化流
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 + '\'' +
'}';
}
}
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文件
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会保持默认值。
二、打印流
java.io.PrintStream为其他输出添加了功能,使它们能够方便的打印各种数据值表示形式。PrintStream具有如下特点:
- 只负责数据的输出,不负责数据的读取
- 与其他输出流不同,它永远不会抛出IOException,但有可能抛出别的异常
PrintStream的父类为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):将指定的字节输出流
此外,PrintStream还有两个特有的方法:
-
print():输出任意类型的值 -
println():输出任意类型的值并换行例如我们常使用的
System.out.println()就是使用了PrintStream中的print()方法。/** * Prints a String and then terminate the line. This method behaves as * though it invokes <code>{@link #print(String)}</code> and then * <code>{@link #println()}</code>. * * @param x The <code>String</code> to be printed. */ public void println(String x) { synchronized (this) { print(x); newLine(); } }
构造方法:
PrintStream(File file):输出目的地是一个文件PrintStream(OutputStream out):输出目的地是一个字节流PrintStream(String fileName):输出目的地是一个文件路径
在使用PrintStream写数据时,如果使用继承自父类的write()写数据,那么查看数据的时候会查询编码表;如果使用自己特有的方法print()或println()方法写数据,那么写的数据原样输出。
public class PrintStreamTest {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("D:\\data\\Code\\Java_code\\src\\IOStream\\test.txt");
ps.write(97); // a
ps.println(97); // 97
ps.close();
}
}
除了写数据外,PrintStream还可以改变输出语句的目的地,即改变信息应该在哪里输出。PrintStream作为System.setOut()的参数传入,实现输出目的地的改变。
public class PrintStreamTest {
public static void main(String[] args) throws FileNotFoundException {
System.out.println("hello world"); // 在控制台输出
PrintStream ps = new PrintStream("D:\\data\\Code\\Java_code\\src\\IOStream\\test.txt");
System.setOut(ps);
System.out.println("HELLO WORLD"); // 在指定的文件中输出
}
}
除了PrintStream外,还可以使用PrintWriter实现相同的功能。PrintStream、PrintWriter的方法名是完全一致的,PrintWriter类实现了在PrintStream类中的所有print方法,因此一般使用PrintWriter即可。