JavaEE学习日志持续更新----> 必看!JavaEE学习路线(文章总汇)
序列化
原理
序列化流:ObjectOutputStream
java.io.ObjectOutputStream extends OutputStream
对象的序列化流:把对象以流的方式写入到文件中保存
构造方法:
ObjectOutputStream(OutputStream out)
创建一个写入指定OutputStream的ObjectOutputStream。
参数:
OutputStream out:字节输出流
特有的成员方法:
void writeObject(Object obj)
将指定的对象写入ObjectOutputStream。
使用步骤:
- 创建ObjectOutputStream对象,构造方法中传递字节输出流对象
- 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
- 释放资源
注意:使用序列化和反序列化之前,需要对进行序列化或反序列化的类进行标记,进行序列化和反序列化的时候如果未进行标记,则会抛出NotSerializableException异常
:没有序列化异常。
Serializable接口:
Serializable接口也叫标记型接口
未实现Serializable接口的类将不会将其任何状态序列化或反序列化。当序列化反序列化的类实现了Serializable接口,那么就相当于给类添加了一个标记当进行序列化和反序列化时,会检查类上是否有这个标记,有则可以序列化或反序列化,没有则抛出NotSerializableException异常。
代码示例:Person类(需要实现Serializable接口)
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + 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 class Demo01 {
public static void main(String[] args) throws Exception {
//1. 创建ObjectOutputStream对象,构造方法中传递字节输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day11\\person.txt"));
//2. 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
oos.writeObject(new Person("小王",22));
//3. 释放资源
oos.close();
}
}
反序列化流:ObjectInputStream
java.io.ObjectInputStream extends InputStream
对象的反序列化流:把文件中保存的对象以流的方式读取出来
构造方法:
ObjectInputStream(InputStream in)
创建一个从指定的InputStream读取的ObjectInputStream。
参数:
InputStream in:字节输入流
特有的成员方法:
Object readObject()
从ObjectInputStream中读取一个对象。
使用步骤:
- 创建ObjectInputStream对象,构造方法中传递字节输入流
- 使用ObjectInputStream对象中的方法readObject,读取文件中保存的对象
- 释放资源
注意:
public final Object readObject() throws IOException, ClassNotFoundException
readObject方法声明了ClassNotFoundException异常:class文件找不到异常
反序列化有两个前提:
1.被反序列化的类必须实现Serializable接口
2.被反序列化的类必须有.class文件(Person.class)
代码示例:
public class Demo04ObjectInputStream {
public static void main(String[] args) throws Exception {
//1.创建ObjectInputStream对象,构造方法中传递字节输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day11\\Person.txt"));
//2.使用ObjectInputStream对象中的方法readObject,读取文件中保存的对象
Object o = ois.readObject();
System.out.println(o);//Person{name='小王', age=22}
//3.释放资源
ois.close();
}
}
transient关键字
static:静态关键字
private static int age;
oos.writeObject(new Person("小王",22));
Object o = ois.readObject(); //Person{name='小王', age=0}
静态优先于非静态加载到内存中
被static关键字修饰的成员变量不能被序列化
原因:静态的成员变量属于类,不属于某个对象,被所以的对象共享,而我们序列化的是对象,所以静态的成员变量不能被序列化。
transient:瞬态关键字
作用:阻止成员变量序列化
private transient int age;
oos.writeObject(new Person("小王",22));
Object o = ois.readObject(); Person{name='小王', age=0}
序列号冲突异常
正常的Person类:
public class Person implements Serializable {
private String name;
private int age;
}
如果对Person类进行修改,则class文件会被重写,此时的序列号也会被改变。
public class Person implements Serializable {
private String name;
public int age;
}
此时就会出现InvalidClassException异常
jdk中关于序列号的相关解释:
序列化运行时将每个可序列化类与版本号相关联,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否已加载与该序列化兼容的该对象的类。 如果接收者已经为具有与相应发送者类别不同的serialVersionUID的对象加载了类,则反序列化将导致InvalidClassException 。可序列化类可以通过声明名为"serialVersionUID"必须为static,final和long类型的字段来显式声明其自己的serialVersionUID:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
所以我们给Person类自定义一个序列号,防止序列号冲突的问题:
public class Person implements Serializable {
/*
为了防止序列号冲突的异常
可以自己定义一个序列号:
要求:
private static final long serialVersionUID = 42L;
*/
private static final long serialVersionUID = 1L;
private String name;
//private static int age;
public int age;
}
序列化集合
三种读取文件的结束标志:
read()方法结束的时候返回-1
readLine()方法结束的时候返回null
readObject()方法结束的时候返回EOFException异常
EOFException:表示在输入期间意外到达文件末尾或流末尾的信号。
所以无法使用直接的遍历来读取多个对象。想要实现序列化多个对象,反序列化多个对象,可以把多个对象存储到一个集合中,对集合进行序列化和反序列化(集合也是一个对象)
代码示例:对集合进行序列化和反序列化
public class Demo03Arrays {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建一个ArrayList集合对象,保存多个Person对象
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("小王",22));
list.add(new Person("小王2",22));
list.add(new Person("小王3",22));
list.add(new Person("小王4",22));
//创建一个序列化的流ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day11\\list.txt"));
//使用writeObject方法
oos.writeObject(list);
//释放资源
oos.close();
//创建一个反序列化流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day11\\list.txt"));
//使用readObject方法
Object o = ois.readObject();
//把Object强转为ArrayList
ArrayList<Person> list2 = (ArrayList<Person>)o;
//遍历集合
for (Person person : list2) {
System.out.println(person);
}
}
}
打印流
java.io.PrintStream extends OutputStream:打印流
打印流的特点:
- PrintStream向另一个输出流添加功能(print方法,println方法),即能够方便地打印各种数据值的表示。
- 与其他输出流不同, PrintStream永远不会抛出IOException,可能会抛出文件找不到异常。
- 不能读取文件,只能写(输出)。
构造方法:
PrintStream(File file)
输出的目的地是一个文件PrintStream(OutputStream out)
输出的目的地是一个字节输出流PrintStream(String fileName)
输出的目的地是一个文件路径
继承自父类OutputStream的共性成员方法:打开文件查看数据的时候会查询编码表
void write(int b)
写一个字节void write(byte[] b)
写一个字节数组void write(byte[] b, int off, int len)
写字节数组的一部分,off数组的开始索引,len写的字节个数void flush()
刷新此输出流并强制写出任何缓冲的输出字节。void close()
关闭此输出流并释放与此流关联的所有系统资源。
特有的成员方法:不查询编码表,原样输出
void print(任意类型的数据)
:不换行void println(任意类型的数据)
:换行,调用newLine
使用步骤:
- 创建打印流PrintStream对象,构造方法中绑定要输出的目的地
- 使用打印流PrintStream对象中的方法write/print/println,把数据写入到目的地
- 释放资源
代码示例:
public class Demo01PrintStream {
public static void main(String[] args) throws FileNotFoundException {
System.out.println();
//1.创建打印流PrintStream对象,构造方法中绑定要输出的目的地
PrintStream ps = new PrintStream("day11\\print.txt");
//2.使用打印流PrintStream对象中的方法write/print/println,把数据写入到目的地
ps.write(97);//a
ps.print(97);//97
//3.释放资源
ps.close();
}
}