一、需求引入
1.要求在不使用对象流的前提下实现如下需求
2.需求如下所示:
学生信息系统
(a) 假设有20个Student,每个学生有id(学号) name(姓名) age(年龄) className(班级名称)
(b) 把20个学生的信息保存到文件中
(c) 从文件中把每个学生的信息都获取处理
3.代码演示(结合ArrayList集合类+高效字符缓冲流+增强for+泛型规范)
package com.bianyiit.anli;
import java.io.Serializable;
public class Student{
private int id;
private String name;
private int age;
private String classname;
public Student() {
}
public Student(int id, String name, int age, String classname) {
this.id = id;
this.name = name;
this.age = age;
this.classname = classname;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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 String getClassname() {
return classname;
}
public void setClassname(String classname) {
this.classname = classname;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", classname='" + classname + '\'' +
'}';
}
}
package com.bianyiit.anli;
/*学生信息系统
a) 假设有20个Student,每个学生有id(学号) name(姓名) age(年龄) className(班级名称)
b) 把20个学生的信息保存到文件中
c) 从文件中把每个学生的信息都获取处理*/
import java.io.*;
import java.util.ArrayList;
public class StudentDemo {
public static void main(String[] args) throws IOException {
//创建集合对象
ArrayList<Student> arr=new ArrayList<>();
//使用循环创建20个学生对象
for (int i = 0; i < 20; i++) {
//创建学生对象
Student s = new Student();
s.setId(i+1); //设置id
s.setName("小明"+(i+1));
s.setAge(20+i);
s.setClassname("103班");
//将20个学生对象保存到集合中
arr.add(s);
}
//创建字符输出缓冲流对象---写数据
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\重要代码Demo\\文件IO对象\\打印流\\student.txt"));
//遍历集合
for (Student student : arr) {
//System.out.println(student);
//将每一个学生的信息写入a.txt中
bw.write("ID:"+student.getId()+" Name:"+student.getName()+" Age:"+student.getAge()+" ClassName:"+student.getClassname());
bw.newLine();
}
bw.close();
//创建字符输入缓冲流对象---读数据
BufferedReader br = new BufferedReader(new FileReader("D:\\重要代码Demo\\文件IO对象\\打印流\\student.txt"));
String line;
while((line=br.readLine())!=null){
System.out.println(line);
}
br.close();
}
}
//输出结果:
ID:1 Name:小明1 Age:20 ClassName:103班
ID:2 Name:小明2 Age:21 ClassName:103班
ID:3 Name:小明3 Age:22 ClassName:103班
ID:4 Name:小明4 Age:23 ClassName:103班
ID:5 Name:小明5 Age:24 ClassName:103班
ID:6 Name:小明6 Age:25 ClassName:103班
ID:7 Name:小明7 Age:26 ClassName:103班
ID:8 Name:小明8 Age:27 ClassName:103班
ID:9 Name:小明9 Age:28 ClassName:103班
ID:10 Name:小明10 Age:29 ClassName:103班
ID:11 Name:小明11 Age:30 ClassName:103班
ID:12 Name:小明12 Age:31 ClassName:103班
ID:13 Name:小明13 Age:32 ClassName:103班
ID:14 Name:小明14 Age:33 ClassName:103班
ID:15 Name:小明15 Age:34 ClassName:103班
ID:16 Name:小明16 Age:35 ClassName:103班
ID:17 Name:小明17 Age:36 ClassName:103班
ID:18 Name:小明18 Age:37 ClassName:103班
ID:19 Name:小明19 Age:38 ClassName:103班
ID:20 Name:小明20 Age:39 ClassName:103班
4.这里写入记事本的还是Student对象的各个属性的String类型的数据(并不是面向对象的编程),并不是写入真正的Student对象
二、如何将真正的Student对象写入txt文本中
1.对象流概念的引入
对象输出流 序列化--将对象写入到文件中 ObjectOutputStream
对象输入流 反序列化--将对象读到内存中 ObjectInputStream
2.使用对象流将Student对象写入txt文本中---这里需要序列化
package com.bianyiit.cast;
import com.bianyiit.anli.Student;
import java.io.*;
public class DuiXiangIO{
public static void main(String[] args) throws IOException, ClassNotFoundException {
//对象输出流 序列化--将对象写入到文件中 ObjectOutputStream
//对象输入流 反序列化--将对象读到内存中 ObjectInputStream
//创建对象输出流对象
ObjectOutputStream ojos = new ObjectOutputStream(new FileOutputStream("D:\\重要代码Demo\\文件IO对象\\对象流\\student.txt"));
//创建学生对象--writeObject()
Student s1 = new Student(1, "张三", 20, "103班");
ojos.writeObject(s1);
}
}
//输出结果:
Exception in thread "main" java.io.NotSerializableException: com.bianyiit.anli.Student
报错原因分析:NotSerializableException(Student类没有实现序列化)
3.Student类实现序列化接口
package com.bianyiit.yanshi;
import java.io.Serializable;
//对实体类Student进行序列化
public class Student implements Serializable {
private int id;
private String name;
private int age;
private String classname;
public Student() {
}
public Student(int id, String name, int age, String classname) {
this.id = id;
this.name = name;
this.age = age;
this.classname = classname;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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 String getClassname() {
return classname;
}
public void setClassname(String classname) {
this.classname = classname;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", classname='" + classname + '\'' +
'}';
}
}
分析:使用ctrl+点击Serializable,进入Serializable类的源代码中发现,它里面的序列化接口方法中没有代码,所以我们实现了序列化之后不需要重写它的方法
4.再次运行测试代码
package com.bianyiit.yanshi;
import com.bianyiit.anli.Student;
import java.io.*;
public class DuiXiangIO{
public static void main(String[] args) throws IOException, ClassNotFoundException {
//对象输出流 序列化--将对象写入到文件中 ObjectOutputStream
//对象输入流 反序列化--将对象读到内存中 ObjectInputStream
//创建对象输出流对象
ObjectOutputStream ojos = new ObjectOutputStream(new FileOutputStream("D:\\重要代码Demo\\文件IO对象\\对象流\\s1.txt"));
//创建学生对象--writeObject()
Student s1 = new Student(1, "张三", 20, "103班");
ojos.writeObject(s1);
}
}
//输出结果:运行成功
Process finished with exit code 0
打开写入对象的txt文本,如下所示:
结果分析:打开txt文本出现乱码,这是怎么回事呢??博客的最后解释原因!
5.同样使用对象输入流将对象读入内存,并在控制台打印---这里需要反序列化
package com.bianyiit.cast;
import com.bianyiit.anli.Student;
import java.io.*;
public class DuiXiangIO{
public static void main(String[] args) throws IOException, ClassNotFoundException {
//对象输出流 序列化--将对象写入到文件中 ObjectOutputStream
//对象输入流 反序列化--将对象读到内存中 ObjectInputStream
//读取文件中的学生对象
ObjectInputStream obis = new ObjectInputStream(new FileInputStream("D:\\重要代码Demo\\文件IO对象\\对象流\\student.txt"));
//读取对象----readObject();
Student s1 = (Student)obis.readObject();
System.out.println(s1);
}
}
//输出结果:打印成功
Student{id=1, name='张三', age=20, classname='103班'}
6.假如存只存入一个对象,但是使用对象输入流将两个对象读入内存,并在控制台打印
package com.bianyiit.yanshi;
import com.bianyiit.anli.Student;
import java.io.*;
public class DuiXiangIO{
public static void main(String[] args) throws IOException, ClassNotFoundException {
//对象输出流 序列化--将对象写入到文件中 ObjectOutputStream
//对象输入流 反序列化--将对象读到内存中 ObjectInputStream
//读取文件中的学生对象
ObjectInputStream obis = new ObjectInputStream(new FileInputStream("D:\\重要代码Demo\\文件IO对象\\对象流\\s1.txt"));
//读取对象----readObject();
Student s1 = (Student)obis.readObject();
System.out.println(s1);
Student s2 = (Student)obis.readObject();
System.out.println(s2);
}
}
//输出结果:
Exception in thread "main" java.io.EOFException
7.怎么做才能使用对象输出流只存入一个对象,但是使用对象输入流将两个对象读入内存,并在控制台打印的时候不报错
//利用一个while(true)死循环集合上述情况出现的EOFException,用它来判断已经读到了文本的末尾,然后使用break跳出循环
//格式:while(true){ } catch (EOFException e) { break;}
package com.bianyiit.yanshi;
import com.bianyiit.anli.Student;
import java.io.*;
public class DuiXiangIO{
public static void main(String[] args) throws IOException, ClassNotFoundException {
//对象输出流 序列化--将对象写入到文件中 ObjectOutputStream
//对象输入流 反序列化--将对象读到内存中 ObjectInputStream
//读取文件中的学生对象
ObjectInputStream obis = new ObjectInputStream(new FileInputStream("D:\\重要代码Demo\\文件IO对象\\对象流\\s1.txt"));
//读取对象----readObject();
while(true){
try {
Student s1 = (Student)obis.readObject();
System.out.println(s1);
Student s2 = (Student)obis.readObject();
System.out.println(s2);
} catch (EOFException e) {
//当出现异常的时候,代表读到末尾了,这是可以将整个读的步骤干掉
//System.out.println("用异常来标识读取对象的结尾:"+e.getClass());
break;
}
}
}
}
//输出结果:
Student{id=1, name='张三', age=20, classname='103班'}
8.当我们在Student学生类写入txt文本文件之前不添加private char sex,但是从txt文本文件中读数据的时候添加private char sex,也就是写的时候Student只有四个属性,但是读的时候Student有五个属性了,会出现什么问题呢??
package com.bianyiit.yanshi;
import java.io.Serializable;
//对实体类进行序列化
public class Student implements Serializable {
private int id;
private String name;
private int age;
private String classname;
public Student() {
}
public Student(int id, String name, int age, String classname) {
this.id = id;
this.name = name;
this.age = age;
this.classname = classname;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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 String getClassname() {
return classname;
}
public void setClassname(String classname) {
this.classname = classname;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", classname='" + classname + '\'' +
'}';
}
}
package com.bianyiit.cast;
import com.bianyiit.anli.Student;
import java.io.*;
public class DuiXiangIO{
public static void main(String[] args) throws IOException, ClassNotFoundException {
//对象输出流 序列化--将对象写入到文件中 ObjectOutputStream
//对象输入流 反序列化--将对象读到内存中 ObjectInputStream
//读取文件中的学生对象
ObjectInputStream obis = new ObjectInputStream(new FileInputStream("D:\\重要代码Demo\\文件IO对象\\对象流\\student.txt"));
//读取对象----readObject();
Student s1 = (Student)obis.readObject();
System.out.println(s1);
}
}
运行代码,将拥有四个属性的Student对象存入txt文本文件中
package com.bianyiit.yanshi;
import java.io.Serializable;
//对实体类进行序列化
public class Student implements Serializable {
private int id;
private String name;
private int age;
private String classname;
private char sex; //写之前没有,读的时候有
public Student() {
}
public Student(int id, String name, int age, String classname) {
this.id = id;
this.name = name;
this.age = age;
this.classname = classname;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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 String getClassname() {
return classname;
}
public void setClassname(String classname) {
this.classname = classname;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", classname='" + classname + '\'' +
'}';
}
}
package com.bianyiit.yanshi;
import com.bianyiit.anli.Student;
import java.io.*;
public class DuiXiangIO{
public static void main(String[] args) throws IOException, ClassNotFoundException {
//对象输出流 序列化--将对象写入到文件中 ObjectOutputStream
//对象输入流 反序列化--将对象读到内存中 ObjectInputStream
//读取文件中的学生对象
ObjectInputStream obis = new ObjectInputStream(new FileInputStream("D:\\重要代码Demo\\文件IO对象\\对象流\\s1.txt"));
//读取对象----readObject();
while(true){
try {
Student s1 = (Student)obis.readObject();
System.out.println(s1);
Student s2 = (Student)obis.readObject();
System.out.println(s2);
//之前保存的对象中不包含sex 而取出来的时候包含sex 所以会报java.io.InvalidClassException
} catch (EOFException e) {
//当出现异常的时候,代表读到末尾了,这是可以将整个读的步骤干掉
//System.out.println("用异常来标识读取对象的结尾:"+e.getClass());
break;
}
}
}
}
接着运行读的代码,将拥有五个属性的Student类读入内存,并在控制台打印
输出结果截图:
原因解释:
//Exception in thread "main" java.io.InvalidClassException: com.bianyiit.anli.Student; local class incompatible:
//stream classdesc serialVersionUID = -3422614124499666693, local class serialVersionUID = -2568366293181036619
序列化和反序列化的serialVersionUID不再匹配了,原先的类四个属性会在内存中自动生成序列化代码,更改之后五个属性的类在内存中又会自动生成新的序列化代码,两个序列化代码不匹配就会报错
由于UID前后不匹配,所以要自己定义序列化编码private static final long serialVersionUID =1L;
package com.bianyiit.anli;
import java.io.Serializable;
//对实体类进行序列化
public class Student implements Serializable {
//由于UID前后不匹配,所以要自己定义序列化编码
//private static final long serialVersionUID =1L;
private int id;
private String name;
private int age;
private String classname;
private char sex;
//在创建对象之前注释掉private char sex,然后写入拥有四个属性的对象进入文件中
//在之后打开private char sex,然后从文件中读取拥有五个属性的对象
//会报异常java.io.InvalidClassException(序列化前的UID和序列化后的UID不匹配)
public Student() {
}
public Student(int id, String name, int age, String classname) {
this.id = id;
this.name = name;
this.age = age;
this.classname = classname;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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 String getClassname() {
return classname;
}
public void setClassname(String classname) {
this.classname = classname;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", classname='" + classname + '\'' +
'}';
}
}
这个时候如果按照之前的步骤,先添加四个属性的Student对象,然后写入txt文本中。接着读出拥有五个属性的Student对象进入内存,并显示在控制台中
输出结果:Student{id=1, name='张三', age=20, classname='103班'} 输出成功
总结
1.如果要实现将对象存入txt文本文件中,创建的对象类必须要实现序列化接口,也就是implements Serializable
public class Student implements Serializable { }
2.如果想要写入一个对象,然后读取多个对象不报异常需要实现如下代码
/*while(true){
try {
Student s1 = (Student)obis.readObject();
System.out.println(s1);
Student s2 = (Student)obis.readObject();
System.out.println(s2);
} catch (EOFException e) {
//当出现异常的时候,代表读到末尾了,这是可以将整个读的步骤干掉
//System.out.println("用异常来标识读取对象的结尾:"+e.getClass());
break;
}*/
3.当我们在Student学生类写入txt文本文件之前不添加private char sex,但是从txt文本文件中读数据的时候添加private char sex,也就是写的时候Student只有四个属性,但是读的时候Student有五个属性了会报异常 java.io.InvalidClassException
原因解释
:序列化和反序列化的serialVersionUID不再匹配了,原先的类四个属性会在内存中自动生成序列化代码,更改之后五个属性的类在内存中又会自动生成新的序列化代码,两个序列化代码不匹配就会报错
解决方法
:由于UID前后不匹配,所以要自己定义序列化编码private static final long serialVersionUID =1L
4.当我们将序列化对象写入文本时,打开记事本发现是乱码
原因解释
:我们如果不序列化对象,那么就会报NotSerializableException异常,当我们implements Serializable之后,发现打开记事本,内容是乱码,是不是编码格式不对呢??其实不是的,序列化对象是将对象编码成可永久存储的二进制文件,而不是我们一开始使用的那种存储字符串的方式,它是没有对应的编码表的,所以无法用txt、word等软件打开的。而反序列化对象是将已经序列化的二进制文本文件重新解码成可读取的对象,能够在控制台打印输出的。所以千万别想着能够编写代码能够在txt中能够看到不是乱码的对象内容。
5.千万千万要记住序列化和反序列化的serialVersionUID必须保持一致,才能保证编码和解码的格式一致,不然会出现java.io.InvalidClassException异常