序列化
ObjectOutputStream类
java.io.ObjectOutputStream类,将java对象的原始数据类型写入到文件,实现对象的持久存储。
构造方法:
public ObjectOutputStream(OutputStream out):创建一个OutputStream的ObjectOutputStream。构造方法代码如下:
FileOutputStream fileout=new FileOutputStream("employee.txt");
ObjectOutputStream out=new ObjectOutputStream(fileout);
序列化操作
- 一个对象想要序列化,必须满足两个条件:
- 该类必须实现java.io.Serializable接口,Serializable是一个标记接口,不实现此接口的类将不会使任何状态的序列化或反序列化,会抛出NotSerializableException
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性 必须注明是瞬态的,使用transient关键字修饰。
import java.io.Serializable;
/**
* 序列化和反序列化的时候,会抛出NotSerializableException没有序列化异常。
* 类通过实现 java.io.Serializable 接口以启用器序列化功能,未实现此接口的类将无法使其任何状态序列化和反序列化。
* Serializable也叫标记型接口
* 要序列化和反序列化的类必须实现Serializable接口,就会给他添加一个标记
* 当我们进行序列化和反序列化的时候,就会检测这个类上是否有Serializable标记,有的话就可以序列化和反序列化。没有就会报错会抛出NotSerializableException异常。
*/
public class Student implements Serializable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
import org.junit.Test;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* java.io.ObjectOutputStream extends OutputStream
* ObjectOutputStream:对象的序列化流
* 作用:把对象以流的方式写入到文件中保存
*
* 构造方法:
* ObjectOutputStream(OutputStream out):创建写入指定OutputStream的ObjectOutputStream
* 参数:
* OutputStream out:字节输出流。
* 特有的成员方法:
* void writeObject(Object obj):将指定的对象写入ObjectOutputStream
* 使用步骤:
* 1.创建ObjectOutputStream对象,构造方法中传递字节输入流
* 2.使用ObjectOutputStream对象中的writeObject,把对象写入到文件中
* 3.释放资源
*/
public class DemoObjectStream01 {
@Test
public void testObjectOutputStream() throws IOException {
//创建ObjectOutputStream对象,构造方法中传递字节输入流
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("student.txt"));
//使用ObjectOutputStream对象中的writeObject,把对象写入到文件中
objectOutputStream.writeObject(new Student("橙子",20));
//释放资源
objectOutputStream.close();
}
}
ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复对象。
构造方法:
public ObjectInputStream(InputStream in):创建一个指定的InputStream的ObjectInputStream。
反序列化操作1
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectinputStream读取对象的方法。
- public final Object readObject():读取一个对象
package com.ft.first.objectStream;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* java.io.ObjectInputStream extends InputStream
* ObjectInputStream:对象反序列化流
* 作用:把文件保存的对象,以流的方式读取出来使用
*
* 构造方法:
* ObjectInputStream(InputStream in):创建一个指定InputStream读取的ObjectInputStream
* 参数:
* InputStream in:字节输入流。
* 特有的方法:
* Object readObject():从ObjectInputStream读取对象
* 使用步骤:
* 1.创建ObjectInputStream对象,构造方法中传递字节输入流。
* 2.使用ObjectInputStream对象的方法readObject读取保存对象的文件
* 3.释放资源
* 4.使用读取出来的对象(打印)
* readObject()声明抛出了ClassNotFoundException(class文件找不到异常)
* 当不存在对象的class文件时抛出此异常
* 反序列化的前期:
* 1.类必须实现Serializable
* 2.必须存在类对应的class文件
*/
public class DemoObjectInputStream01 {
@Test
public void TestObjectInputStream() throws IOException, ClassNotFoundException {
//创建ObjectInputStream对象,构造方法中传递字节输入流。
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("student.txt"));
//使用ObjectInputStream对象的方法readObject读取保存对象的文件
Object obj=objectInputStream.readObject();
//释放资源
objectInputStream.close();
//使用读取出来的对象(打印)
System.out.println(obj);
Student student=(Student) obj;
System.out.println(student.getName());
System.out.println(student.getAge());
}
}
关键字
static关键字:静态关键字。静态优先于非静态加载到内存中。被static修饰的成员变量不能被序列化的,序列化的都是对象。
transient关键字:瞬态关键字。
指定成员变量不想被序列化可以使用transient和static,transient跟static差不多只是没有static的含义。
package com.ft.first.objectStream;
import java.io.Serializable;
/**
* 序列化和反序列化的时候,会抛出NotSerializableException没有序列化异常。
* 类通过实现 java.io.Serializable 接口以启用器序列化功能,未实现此接口的类将无法使其任何状态序列化和反序列化。
* Serializable也叫标记型接口
* 要序列化和反序列化的类必须实现Serializable接口,就会给他添加一个标记
* 当我们进行序列化和反序列化的时候,就会检测这个类上是否有Serializable标记,有的话就可以序列化和反序列化。没有就会报错会抛出NotSerializableException异常。
*
* static关键字:静态关键字。静态优先于非静态加载到内存中。被static修饰的成员变量不能被序列化的,序列化的都是对象。
* private static int age;
* 序列化写入:
* objectOutputStream.writeObject(new Student("橙子",20));
* 反序列化读取:
* Object obj=objectInputStream.readObject();
* 结果为:Student{name='橙子', age=0}
*
* transient关键字:瞬态关键字。
* 被transient修饰的成员变量,不能被序列化
* private transient int age;
* 序列化写入:
* objectOutputStream.writeObject(new Student("橙子",20));
* 反序列化读取:
* Object obj=objectInputStream.readObject();
* 结果为:Student{name='橙子', age=0}
*/
public class Student implements Serializable {
private String name;
private transient int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
反序列化操作2
另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:
- 该类的序列化版本号与从流中读取的类描述符的版本号不配匹。
- 该类包含未知数据类型。
- 该类没有可访问的无参数构造方法
Serializable接口给需要序列化的类,提供了一个序列版本号。serialVersionUID该版本号的目的在于验证序列化的对象和对应类是否版本配匹。
问题:
每次修改类的定义,都会给class文件生成一个新的序列号。
解决方案:
无论是否对类的定义是否进行修改,都不重新生成新的序列号。可以手动给类添加一个序列号。
格式在Serialzable接口规定:
可序列号类可以通过声明为"serialVersionUID"的字段(该字段必须是静态(static)、最终(final)的long型字段)显示声明其自己的serialVersionUID。
public class Student implements Serializable {
//定义这个类的serialVersionUID=42L; 不会出现序列号冲突
static final long serialVersionUID=42L; //常量不能改变
private String name;
//private transient int age;
private int age;
序列化集合
- 将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
- 反序列化list.txt,并遍历集合,打印对象信息。
案例分析
- 把若干个学生对象,保存到集合中。
- 把集合序列化。
- 反序列化读取时,只需要读取一次,转换为集合类型。
- 遍历集合,可以打印所有学生信息。
案例实现
import org.junit.Test;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 练习:序列化集合
* 当我们想在文件中保存多个对象的时候,可以把多个对象存储到一个集合中,对集合进行序列化和反序列化
* 分析:
* 1.定义一个存储Student对象的ArrayList集合
* 2.往ArrayList集合中存储Student对象
* 3.创建一个序列化流ObjectOutputStream对象
* 4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
* 5.创建一个反序列化ObjectInputStream对象
* 6.使用ObjectInputStream对象中的方法readObject,读取文件中保存的集合
* 7.把Object类型的集合转换为ArrayList类型
* 8.遍历ArrayList集合
* 9.释放资源
*/
public class DemoObjectInputStream02 {
@Test
public void testListStudent() throws IOException, ClassNotFoundException {
//定义一个存储Student对象的ArrayList集合
List list = new ArrayList();
//往ArrayList集合中存储Student对象
list.add(new Student("张三", 18));
list.add(new Student("李四", 19));
list.add(new Student("王五", 20));
//创建一个序列化流ObjectOutputStream对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("listStudent.txt"));
//使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
objectOutputStream.writeObject(list);
//创建一个反序列化ObjectInputStream对象
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("listStudent.txt"));
//使用ObjectInputStream对象中的方法readObject,读取文件中保存的集合
Object obj = objectInputStream.readObject();
//把Object类型的集合转换为ArrayList类型
ArrayList<Student> list1=(ArrayList<Student>)obj;
//遍历ArrayList集合
for(Student s:list1){
System.out.println(s);
}
//释放资源
objectInputStream.close();
objectOutputStream.close();
}
}
打印流
概述
平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都是来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
最常见的:
System.out.println(" ");
PrintStream类
构造方法:
- public PrintStream(String fileName):使用指定的文件名创建一个新的打印流。
构造举例,代码如下:
PrintStream ps=new PrintStream("ps.txt");
改变打印流方向
System.out就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个"小把戏",改变它的流向。
import org.junit.Test;
import java.io.FileNotFoundException;
import java.io.PrintStream;
/**
* java.io.PrintStream:打印流
* PrintStream 为其他输出流添加了功能,使他们能够方便地打印各种数据值表示形式。
* PrintStream特点:
* 1.只负责数据的输出,不负责数据的读取
* 2.与其他输出流不同,PrintStream永远不会抛出IOException,但会抛出文件找不到的异常
* 3.有特有的方法,print、println
* print:(输出任意的数据格式)
* println:(输出任意的数据格式带换行)
* 构造方法:
* PrintStream(File file):输出的目的地是文件
* PrintStream(OutputStream out):输出的目的地是一个输出流
* PrintStream(String FileName):输出的目的地是一个文件路径
* PrintStream extends OutputStream
* 继承自父类的成员方法:
* void close():关闭此输出流并释放与此流有关的所有系统资源。
* void flush():刷新此输出流并强制写出所有缓冲的输出字节。
* void write(byte[] b):将 b.length 个字节从指定的 byte 数组写入此输出流。
* void write(byte[] b, int off, int len):将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
* abstract void write(int b):将指定的字节写入此输出流。
* 注意:
* 如果使用继承自父类的write方法写数据,那么查询数据的时候会查询编码表。例:97->a
* 如果使用它自己的方法print、println写数据,写的数据会原样输出.例:97->97
*/
public class OutStream {
@Test
public void testOut() throws FileNotFoundException {
//创建打印流PrintStream对象,构造方法中绑定要输出的目的地
PrintStream printStream=new PrintStream("print.txt");
//如果使用继承自父类的write方法写数据,那么查询数据的时候会查询编码表。例:97->a
printStream.write(97);
//如果使用它自己的方法print、println写数据,写的数据会原样输出.例:97->97
printStream.println(97);
//释放资源
printStream.close();
}
/**
* 可以改变输出语句的目的地(打印的流向)
* 输出语句,默认在控制台输出
* 使用:System.setOut方法改变输出语句的目的地改为参数中传递的打印流的目的地。
* static void setOut(PrintStream out):重新分配"标准"输出流。
*/
@Test
public void testOut01() throws FileNotFoundException {
System.out.println("我是在控制台打印输出");
PrintStream printStream=new PrintStream("打印流.txt");
//把输出语句的目的地改变为打印流的目的地
System.setOut(printStream);
System.out.println("我在打印流的目的地中输出");
printStream.close();
}
}