一:基本概述
1、数据流
- DataOutputStream
基本概述:允许应用程序将基本数据类型、String类型的变量写入输出流中;
对应方法:
byte writeByte() | short writeShort() |
int writeInt() | long writeLong() |
float writeFloat() | double writeDouble() |
char writeChar() | boolean writeBoolean() |
String writeUTF() | void writeFully(byte[] b) |
- DataInputStream
基本概述:允许应用程序以与机器无关的方式从底层输入流中读取基本数据类型、String类型的变量。
对应方法:
byte readByte() | short readShort() |
int readInt() | long readLong() |
float readFloat() | double readDouble() |
char readChar() | boolean readBoolean() |
String readUTF() | void readFully(byte[] b) |
2、对象流
- ObjectOutputStream
将java基本数据类型和对象写入字节输出流中。通过在流中使用文件可以实现java各种基本数据类型的数据以及对象的持久存储。
- ObjectInputStream
ObjectInputStream写出的基本数据类型的数据和对象进行读入操作,保存在内存中。
二:对象流API
1、ObjectOutputStream中的构造器:
public ObjectOutputStream(OutputStream out): 创建一个指定的ObjectOutputStream。
FileOutputStream fos = new FileOutputStream("game.dat")
ObjectOutputStream oos = new ObjectOutputStream(fos)
2、ObjectOutputStream中的方法:
public void writeBoolean(boolean val) | 写出一个boolean值 |
public void writeByte(int val) | 写出一个8位字节 |
public void writeShort(int val) | 写出一个16位short值 |
public void writeChar(int val) | 写出一个16位char值 |
public void writeInt(int val) | 写出一个16位Int值 |
public void writeLong(long val) | 写出一个64位的long值 |
public void writeFloat(float val) | 写出一个32位的float值 |
public void writeDouble(double val) | 写出一个64位的double值 |
public void writeUTF(String str) | 将表示长度信息两个字节写入输出流 |
public void writeObject(Object obj) | 写出一个obj对象 |
public void close() | 关闭输出流,并释放相关资源 |
3、ObjectOutputStream中的构造器
public ObjectInputStream(InputStream in): 创建一个指定的ObjectInputStream。
FileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
4、ObjectOutputStream中的方法
public boolean readBoolean() | 读取一个boolean值 |
public byte readByte() | 读取一个8位的字节 |
public short readShort() | 读取一个16位的short值 |
public char readChar() | 读取一个16位的char值 |
public int readInt() | 读取一个32位的int值 |
public long readLong() | 读取一个64位的long值 |
public float readFloat() | 读取一个32位的float值 |
public double readDouble() | 读取一个64位的double值 |
public String readUTF() | 读取UTF-8修改版格式的String |
public void readObject(Object obj) | 读入一个obj对象 |
public void close() | 关闭输入流并释放相关资源 |
三:认识对象序列化机制
对象序列化机制:把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
- 序列化过程:用一个字节序列可以表示一个对象,该字节序列包含该对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
- 反序列化过程:该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。
四:如何实现序列化机制
1、基本概念
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现java.io.Serializable
接口。Serializable
是一个标记接口
,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。
(note:实现序列化机制的前提是该类是否可以序列化,也就是说该类需要实现java.io.Serializable接口。没有实现此类的接口不会序列化和反序列化,会抛出NotSerializableException错误。)
- 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable
接口。(note:对象的某个属性是引用数据类型,那么其实现序列化的前提也是实现Serializable接口。)
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient
关键字修饰。(note: 对象序列化的前提是,该对象内的所有属性都需要可以序列化,如果不可以序列化,需要使用transient关键字修饰,注明其是瞬态的。)
- 静态(static)变量
的值不会序列化。因为静态变量的值不属于某个对象。
2、测试代码01
package IO.Serializable;
import org.junit.Test;
import java.io.*;
public class ReadWriteDataOfAnyType {
@Test
public void save() throws IOException{
String name = "巫师";
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("game.dat"));
oos.writeUTF(name);
oos.writeInt(age);
oos.writeChar(gender);
oos.writeInt(energy);
oos.writeDouble(price);
oos.writeBoolean(relive);
oos.close();
}
@Test
public void reload()throws IOException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("game.dat"));
String name = ois.readUTF();
int age = ois.readInt();
char gender = ois.readChar();
int energy = ois.readInt();
double price = ois.readDouble();
boolean relive = ois.readBoolean();
System.out.println(name + "," + age + "," + gender + "," + energy + "," + price + "," + relive);
ois.close();
}
}
3、测试代码02
transient修饰的成员变量没有被序列化;
package IO.Serializable;
import org.junit.Test;
import java.io.*;
public class EmployeeTest {
@Test
public void save() throws IOException{
Employee.setCompany("人生圆满");
Employee e = new Employee("小马宝莉","完美世界",23);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
oos.writeObject(e);
oos.close();
System.out.println("Serialized data is saved");
}
@Test
public void reaload() throws IOException,ClassNotFoundException{
FileInputStream fis = new FileInputStream("employee.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
Employee e = (Employee) ois.readObject();
ois.close();
fis.close();
System.out.println(e);//Employee{name='小马宝莉', address='完美世界', age=0}
//姓名地址被序列化,年龄没有序列化
}
}
class Employee implements Serializable{
public static String company;
public String name;
public String address;
public transient int age;
public Employee(String name,String address,int age){
this.name = name;
this.address = address;
this.age = age;
}
public static String getCompany() {
return company;
}
public static void setCompany(String company) {
Employee.company = company;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
'}';
}
}
4、测试代码03
如果多个对象需要被序列化,则可以将对象放到集合中,在序列化集合对象即可。
@Test
public void test3() throws IOException{
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三","王府井",20));
list.add(new Employee("李四","碧桂园",10));
list.add(new Employee("王五","动心玩",19));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employees.dat"));
oos.writeObject(list);
oos.close();
}
@Test
public void test4() throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("employees.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
ArrayList<Employee> list = (ArrayList<Employee>) ois.readObject();
ois.close();
fis.close();
System.out.println(list);
}
五:反序列化失败的问题
1、找到对应的类
对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException
异常。
(note:反序列化的对象,必须找到该class文件的类,找不到会抛出ClassNotFoundException。)
2、序列化后,class文件发生了修改
当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常。发生这个异常的原因如下:
-
该类的序列版本号与从流中读取的类描述符的版本号不匹配
-
该类包含未知数据类型