处理流之三/四:数据流、对象流

1. 数据流与对象流说明

如果需要将内存中定义的变量(包括基本数据类型或引用数据类型)保存在文件中,那怎么办呢?

int age = 300;  
char gender = '男';  
int energy = 5000;  
double price = 75.5;  
boolean relive = true;  
​  
String name = "巫师";  
Student stu = new Student("张三",23,89);

Java提供了数据流和对象流来处理这些类型的数据:

  • 数据流:DataOutputStream、DataInputStream

    • DataOutputStream:允许应用程序将基本数据类型、String类型的变量写入输出流中

    • DataInputStream:允许应用程序以与机器无关的方式从底层输入流中读取基本数据类型、String类型的变量。

  • 对象流DataInputStream中的方法:

  byte readByte()                short readShort()  
  int readInt()                  long readLong()  
  float readFloat()              double readDouble()  
  char readChar()                boolean readBoolean()                    
  String readUTF()               void readFully(byte[] b)
  • 对象流DataOutputStream中的方法:将上述的方法的read改为相应的write即可。

  • 数据流的弊端:只支持Java基本数据类型和字符串的读写,而不支持其它Java对象的类型。而ObjectOutputStream和ObjectInputStream既支持Java基本数据类型的数据读写,又支持Java对象的读写,所以重点介绍对象流ObjectOutputStream和ObjectInputStream。

  • 对象流:ObjectOutputStream、ObjectInputStream

    • ObjectOutputStream:将 Java 基本数据类型和对象写入字节输出流中。通过在流中使用文件可以实现Java各种基本数据类型的数据以及对象的持久存储。

    • ObjectInputStream:ObjectInputStream 对以前使用 ObjectOutputStream 写出的基本数据类型的数据和对象进行读入操作,保存在内存中。

说明:对象流的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

2. 对象流API

ObjectOutputStream中的构造器:

public ObjectOutputStream(OutputStream out): 创建一个指定的ObjectOutputStream。

FileOutputStream fos = new FileOutputStream("game.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);

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):写出一个32位的 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):将表示长度信息的两个字节写入输出流,后跟字符串 s 中每个字符的 UTF-8 修改版表示形式。根据字符的值,将字符串 s 中每个字符转换成一个字节、两个字节或三个字节的字节组。注意,将 String 作为基本数据写入流中与将它作为 Object 写入流中明显不同。 如果 s 为 null,则抛出 NullPointerException。

  • public void writeObject(Object obj):写出一个obj对象

  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源

ObjectInputStream中的构造器:

public ObjectInputStream(InputStream in): 创建一个指定的ObjectInputStream。

FileInputStream fis = new FileInputStream("game.dat");
ObjectInputStream ois = new ObjectInputStream(fis);

ObjectInputStream中的方法:

  • 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() :关闭此输入流并释放与此流相关联的任何系统资源

3. 认识对象序列化机制

1、何为对象序列化机制?

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

  • 序列化过程:用一个字节序列可以表示一个对象,该字节序列包含该对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

  • 反序列化过程:该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。

image-20220503123328452

2、序列化机制的重要性

序列化是 RMI(Remote Method Invoke、远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础。

序列化的好处,在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。

3、实现原理

  • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制。方法为:

    • public final void writeObject (Object obj) : 将指定的对象写出。
  • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制。方法为:

    • public final Object readObject () : 读取一个对象。

处理流之三/四:数据流、对象流

4. 如何实现序列化机制

如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现java.io.Serializable 接口。Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException

  • 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable 接口

  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。

  • 静态(static)变量的值不会序列化。因为静态变量的值不属于某个对象。

举例1:

package com.atguigu.object;  
​  
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();  
    }  
}  

举例2:

package com.atguigu.object;  
​  
import java.io.Serializable;  
​  
public class Employee implements Serializable {  
    //static final long serialVersionUID = 23234234234L;  
    public static String company; //static修饰的类变量,不会被序列化  
    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 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 +  
                ", company=" + company +  
                '}';  
    }  
}  
​
package com.atguigu.object;  
​  
import org.junit.Test;  
​  
import java.io.*;  
​  
public class ReadWriteObject {  
    @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 reload() 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);  
    }  
}

举例3:如果有多个对象需要序列化,则可以将对象放到集合中,再序列化集合对象即可。

package com.atguigu.object;  
​  
import org.junit.Test;  
​  
import java.io.*;  
import java.util.ArrayList;  
​  
public class ReadWriteCollection {  
    @Test  
    public void save() throws IOException {  
        ArrayList<Employee> list = new ArrayList<>();  
        list.add(new Employee("张三", "宏福苑", 23));  
        list.add(new Employee("李四", "白庙", 24));  
        list.add(new Employee("王五", "平西府", 25));  
        // 创建序列化流对象  
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employees.dat"));  
        // 写出对象  
        oos.writeObject(list);  
        // 释放资源  
        oos.close();  
    }  
​  
    @Test  
    public void reload() 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);  
    }  
}  
​

5. 反序列化失败问题

问题1:

对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。

问题2:

当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配

  • 该类包含未知数据类型

解决办法:

Serializable 接口给需要序列化的类,提供了一个序列版本号:serialVersionUID 。凡是实现 Serializable接口的类都应该有一个表示序列化版本标识符的静态变量:

static final long serialVersionUID = 234242343243L; //它的值由程序员随意指定即可。

  • serialVersionUID用来表明类的不同版本间的兼容性。简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。

  • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。因此,建议显式声明。

  • 如果声明了serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已。

package com.atguigu.object;  
​  
import java.io.Serializable;  
​  
public class Employee implements Serializable {  
    private static final long serialVersionUID = 1324234L; //增加serialVersionUID  
      
    //其它结构:略  
}
Java基础

处理流之二:转换流

2025-8-23 15:00:00

Java基础

其他流的使用

2025-8-25 15:00:00

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索