序列化从头说:
在面向对象程序设计中,类是个很重要的概念。所谓“类”,可以将它想像成建筑图纸,而对象就是根据图纸盖的大楼。类,规定了对象的一切。根据建筑图纸造房子,盖出来的就是大楼,等同于将类进行实例化,得到的就是对象。一开始,在源代码里,类的定义是明确的,但对象的行为有些地方是明确的,有些地方是不明确的。对象里不明确地方,是因为对象在运行的时候,需要处理无法预测的事情,诸如用户点了下屏幕,用户点了下按钮,输入点东西,或者需要从网络发送接收数据之类的。后来,引入了泛型的概念之后,类也开始不明确了,如果使用了泛型,直到程序运行的时候,才知道究竟是哪种对象需要处理。对象可以很复杂,也可以跟时序相关。一般来说,“活的”对象只生存在内存里,关机断电就没有了。一般来说,“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机。序列化,可以存储“活的”对象,可以将“活的”对象发送到远程计算机。把“活的”对象序列化,就是把“活的”对象转化成一串字节,而“反序列化”,就是从一串字节里解析出“活的”对象。于是,如果想把“活的”对象存储到文件,存储这串字节即可,如果想把“活的”对象发送到远程主机,发送这串字节即可,需要对象的时候,做一下反序列化,就能将对象“复活”了。将对象序列化存储到文件,术语又叫“持久化”。将对象序列化发送到远程计算机,术语又叫“数据通信”。ava对序列化提供了非常方便的支持,在定义类的时候,如果想让对象可以被序列化,只要在类的定义上加上了”implements Serializable”即可,比如说,可以这么定义”public class Building implements Serializable”,其他什么都不要做,Java会自动的处理相关一切。Java的序列化机制相当复杂,能处理各种对象关系。Java的序列化机制的缺点就是计算量开销大,且序列化的结果体积大太,有时能达到对象大小的数倍乃至十倍。它的引用机制也会导致大文件不能分割的问题。
3.1 概述
——————————————————————————
java提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据,对象的类型和对象中存储的属性等信息。字节序列写出文件之后,相当于文件中初九保存了一个对象信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据,对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象,看图理解序列化:
3.2 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关键字修饰。
public class SerializeableDemo {
public static void main(String[] args) {
Employee e = new Employee("张三", "北京路", 20);
//创建序列化流对象
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\01-尚硅谷MySQL核心技术-婷姐\\资料、代码\\note\\a.txt"));
) {
//写出对象
out.writeObject(e);
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
class Employee implements Serializable {
public String name;
public String address;
public transient int age;//transient瞬态修饰成员,不会被序列化
public Employee(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public void addressCheck() {
System.out.println("Address check:" + name + "--" + address);
}
3.3 ObjectInputStream类
——————————————————————————
ObjectInputStream反序列化流,将之前使用的ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
- public ObjectInputStream(InputStream in):创建一个指定的InputStream的ObjectInputStream
反序列化操作1
如果能够找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:
- public final Object readObject():读取一个对象。
public class DeserializeDemo {
public static void main(String[] args) {
Employee emp = null;
//创建反序列化的流
try ( FileInputStream fileIn = new FileInputStream("E:\\01-尚硅谷MySQL核心技术-婷姐\\资料、代码\\note\\a.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);) {
emp = (Employee) in.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("name:" + emp.name);
System.out.println("address" + emp.address);
System.out.println("age" + emp.age);
}
}
对于JVM可以反序列化对象,它必须是能够找到class文件的二类。如果找不到该类的class文件,则抛出一个ClassNotFoundException异常。
反序列化操作2 另外,当JVM反序列化对象的时候,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参构造方法
Serializable接口给需要需要序列化的类,提供了一个序列版本号。serialVersionUID该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
class Employee implements Serializable {
//加入序列版本号
private static final long serialVersionUID = 1;
public String name;
public String address;
public transient int age;//transient瞬态修饰成员,不会被序列化
public Employee(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public void addressCheck() {
System.out.println("Address check:" + name + "--" + address);
}
}
3.4 练习:序列化集合
——————————————————————————
- 将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
- 反序列化list.txt,并遍历集合,打印对象信息。
案例分析
- 把若干学生对象,保存到集合中。
- 把集合序列化。
- 反序列化的时候,只需要读取一次,转换为集合类型
- 遍历集合,可以打印所有学生的信息
public class SerTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建学生对象
Student student1 = new Student("老王");
Student student2 = new Student("老杭");
Student student3 = new Student("老李");
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(student1);
arrayList.add(student2);
arrayList.add(student3);
//序列化操作
serializ(arrayList);
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
//读取对象,强转为ArrayList类型
ArrayList<Student> list = (ArrayList<Student>) ois.readObject();
for (Student s : list) {
System.out.println(s.getName()); }
}
public static void serializ(ArrayList<Student> arrayList) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
) {
//写出对象
oos.writeObject(arrayList);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Student implements Serializable {
private final long SerialVersionUID = 1;
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}