解决 java.io.StreamCorruptedException: invalid type code: AC 异常

Spring-_-Bear 的 CSDN 博客导航

一、问题描述

对象输出流 (ObjectOutputStream) 以追加方式向文件序列化保存对象时,可以使用文件输出流 (FileOutputStream) 作为构造器参数,通过 writeObject() 方法向文件中写入对象信息,如下示例代码所示:

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("people.txt", true));
oos.writeObject(new People("Tom", 'M', 21));

第一次通过对象输出流序列化保存对象,而后反序列化读取数据时正常读取不会报错。当第二次追加写入对象信息,再次通过对象输入流(ObjectInputStream)反序列化读取第二次追加写入的对象信息时,就会抛出 java.io.StreamCorruptedException: invalid type code: AC 的异常。详细问题如下示例代码所示:

  1. People.java
import java.io.Serializable;

/**
 * People.java
 *
 * @author Spring-_-Bear
 * @version 2021-10-30 22:45
 */
public class People implements Serializable {

    private static final long serialVersionUID = -2047975638001087437L;

    private final String name;
    private final Character sex;
    private final Integer age;

    public People(String name, Character sex, Integer age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                ", age=" + age +
                '}';
    }

}
  1. AppendSaveObject.java
import java.io.*;

/**
 * AppendSaveObject.java
 *
 * @author Spring-_-Bear
 * @version 2021-10-30 22:45
 */
public class AppendSaveObject {

    private static final String FILE_PATH = "people.txt";

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        File file = new File(FILE_PATH);
        if (file.exists() && file.isFile()) {
            if (file.delete()) {
                System.out.println("------------------ " + FILE_PATH + " 文件删除成功 ------------------\n");
            }
        }

        // 初次序列化保存 2 个对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH, true));
        oos.writeObject(new People("Tom", 'M', 21));
        oos.writeObject(new People("Smith", 'M', 22));
        oos.close();

        // 初次反序列化读取已保存的对象信息,正常读取
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
        System.out.println("第一次读取到的的对象信息:" + ois.readObject());
        System.out.println("第一次读取到的的对象信息:" + ois.readObject());
        ois.close();

        // 第二次追加写入 2 个对象信息
        oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH, true));
        oos.writeObject(new People("Mary", 'M', 21));
        oos.writeObject(new People("Jack", 'M', 20));
        oos.close();

        // 再次创建对象输入流读取对象信息
        ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
        // 以下两行代码读取第一次添加的对象信息,正常输出
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        // 当试图读取第二次添加的两个新对象信息时,会抛异常 java.io.StreamCorruptedException: invalid type code: AC
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        ois.close();
    }

}

示例代码运行情况如下图所示:

在这里插入图片描述

二、原因分析

调试程序跟踪源码,找出异常抛出位置为 ObjectInputStream 类的 readObject0(boolean unshared) 方法的 1721 行代码,如下图:

在这里插入图片描述

查看 ObjectOutputStream 的构造器源码可知,每次序列化创建 ObjectOutputStream 类的对象时都会在构造器中调用 writeStreamHeader() 方法写入流头的信息。

在这里插入图片描述

而反序列化创建 ObjectInputStream 类的对象的时候会在构造器中读取流头的信息。

在这里插入图片描述

invalid type code: AC 问题出现在哪呢?分析问题描述中给出的源码执行流程可以发现:当第一次创建输出流写入对象信息前,写入了一次流头信息。再次创建输出流写入对象前,又写入了一次流头信息,一共写入了两个流头信息。而创建对象输入流时只能读取到第一次写入的流头信息,因而在尝试读取第二次追加保存的对象信息时,由于未读取文件中第二次写入的流头信息而直接读取对象,从而抛出了异常。

三、解决方案

如此看来似乎找到了问题关键所在,即每创建一个对象输出流对象都会写入一次流头信息,而创建对象输入流读取文件中的对象信息时只能读取到第一次添加的流头信息,当尝试读取后续追加的对象而读取到流头信息时就会抛出异常。对象读取流程描述如下所示:

StreamHeader(对象输入流只能读到这个 StreamHeader)
Tom 对象信息
Smith 对象信息
StreamHeader(当对象输入流读到这个 StreamHeader 时就会抛出异常)
Mary 对象信息
Jack 对象信息
······

从上面的分析自然想到了问题的解决方案:

  1. 不写入流信息
  2. 只写入一次流信息

本文采用后者作为问题解决方案。通过自定义类 AppendObjectOutputStream 继承 ObjectOutputStream 类,并重写父类的 writeStreamHeader() 方法,当创建 AppendObjectOutputStream 类的对象时进行流程控制,以达到文件中只存在一个 StreamHeader 的目的。AppendObjectOutputStream 类的源码如下:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * AppendObjectOutputStream.java: 解决 java.io.StreamCorruptedException: invalid type code: AC 异常
 *
 * @author Spring-_-Bear
 * @version 2021-10-30 21:48
 */
public class AppendObjectOutputStream extends ObjectOutputStream {

    private static File file;

    public static void initFile(File file) {
        AppendObjectOutputStream.file = file;
    }

    public AppendObjectOutputStream(File file) throws IOException {
        super(new FileOutputStream(file, true));
    }

    @Override
    public void writeStreamHeader() throws IOException {
        // 如果文件为空直接写入 StreamHeader
        if (file == null || file.length() == 0) {
            super.writeStreamHeader();
        } else {
            // 文件中存在内容,则说明文件中已经存在了一个 StreamHeader,调用父类的 reset() 方法保证文件中只存在一个 StreamHeader
            this.reset();
        }
    }

}

修改问题描述中 AppendSaveObject.java 文件代码,将原来自己创建对象输出流的代码修改为创建 AppendObjectOutputStream 对象,从而保证文件中只存在一个 StreamHeader,进而解决 invalid type code: AC 异常问题。

需要注意的问题是在创建 AppendObjectOutputStream 类的实例之前需要调用其 initFile() 静态方法初始化 file 对象,避免创建其实例动态绑定到 writeStreamHeader() 方法时 file 对象始终为 null。

修改后的代码如下:

import java.io.*;

/**
 * AppendSaveObject.java
 *
 * @author Spring-_-Bear
 * @version 2021-10-30 22:45
 */
public class AppendSaveObject {

    private static final String FILE_PATH = "people.txt";

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        File file = new File(FILE_PATH);
        if (file.exists() && file.isFile()) {
            if (file.delete()) {
                System.out.println("------------------ " + FILE_PATH + " 文件删除成功 ------------------\n");
            }
        }

        // Important: 在创建 ObjectOutputStream 类的实例前先初始化该类的文件信息
        AppendObjectOutputStream.initFile(file);

        // new ObjectOutputStream(new FileOutputStream(FILE_PATH, true)) => new AppendObjectOutputStream(file)
        ObjectOutputStream oos = new AppendObjectOutputStream(file);
        oos.writeObject(new People("Tom", 'M', 21));
        oos.writeObject(new People("Smith", 'M', 22));
        oos.close();

        // 初次反序列化读取已保存的对象信息,正常读取
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
        System.out.println("第一次读取到的的对象信息:" + ois.readObject());
        System.out.println("第一次读取到的的对象信息:" + ois.readObject());
        ois.close();

        // new ObjectOutputStream(new FileOutputStream(FILE_PATH, true)) => new AppendObjectOutputStream(file)
        oos = new AppendObjectOutputStream(file);
        oos.writeObject(new People("Mary", 'M', 21));
        oos.writeObject(new People("Jack", 'M', 20));
        oos.close();

        // 再次创建对象输入流读取对象信息
        ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
        // 以下两行代码读取第一次添加的对象信息,正常输出
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        // 当试图读取第二次添加的两个新对象信息时,会抛异常 java.io.StreamCorruptedException: invalid type code: AC
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        ois.close();
    }

}

修改后的代码运行结果如下图所示,可见异常问题得到了完美的解决。

在这里插入图片描述

综上所述,通过自定义的类 AppendOutputStream 继承自 ObjectOutputStream, 并且重写了父类的 writeStreamHeader() 方法以保证文件中只存在一个 StreamHeader,从而 java.io.StreamCorruptedException: invalid type code: AC 异常抛出问题得到解决。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春天熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值