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

问题描述

ObjectOutputStream 以追加的方式向文件中序列化对象时,每次想向文件末尾追加对象,而不是覆盖,可以使用 FileOutputStream(文件名,true)作为构造器参数,用 writeObject() 方法写入对象。

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("peopleInfo.txt",true));

在读取数据的时候第一次会正常读取,不会报错。当关闭流后再打开,追加写入第二个对象,再用ObjectInputStream 反序列化读取对象时,就会抛出 java.io.StreamCorruptedException: invalid type code: AC 的错误。

代码如下

ObjectOutputInputStreamEx 类

import java.io.*;

/**
 * @author Spring-_-Bear
 * @version 2021-10-30 22:45
 */
public class ObjectOutputInputStreamEx {
    
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
    
        // 第一次写入两个对象,正常读取
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("peopleInfo.txt", true));
        oos.writeObject(new People("Tom", 'M', 21));
        oos.writeObject(new People("Smith", 'M', 22));
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("peopleInfo.txt"));
        System.out.println("第一次读取到的的对象信息:" + ois.readObject());
        System.out.println("第一次读取到的的对象信息:" + ois.readObject());
        ois.close();

        // 在第一次成功写入的文件尾追加写入两个新的对象
        oos = new ObjectOutputStream(new FileOutputStream("peopleInfo.txt", true));
        oos.writeObject(new People("Mary", 'M', 21));
        oos.writeObject(new People("Jack", 'M', 20));
        oos.close();

        // 再次创建对象输入流读取对象信息
        ois = new ObjectInputStream(new FileInputStream("peopleInfo.txt"));
        // 以下两行代码读取第一次添加的对象信息,正常输出
        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();
    }
}

People类

public class People implements Serializable {
    
    
    private String name;
    private char sex;
    int age;

    public People(String name, char sex, int age) {
    
    
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

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

异常信息

在这里插入图片描述
异常抛出位置:在 ObjectInputStream 类的 readObject0(boolean unshared) 方法中:

在这里插入图片描述

原因分析

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

在这里插入图片描述

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

在这里插入图片描述

问题出在哪呢?分析问题描述中给出的源码执行流程,我们发现,当第一次创建输出流写入对象信息前,写入了一次流信息。而再次创建输出流写入对象前,又写入了一次流信息,一共写入了两个流信息。而创建对象输入流时只能读取到第一次写入的流信息。

解决方案

如此看来似乎找到了问题所在,即每创建一个对象输出流对象时都会写入一个流信息,创建几个 ObjectOutputStream 的对象就写入几个 StreamHeader。而创建对象输入流读取文件中的对象信息时只能读取到第一次添加的流信息,当需要读取对象而读取到流信息时就会抛出异常。

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

扫描二维码关注公众号,回复: 13365904 查看本文章

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

(1)不写入流信息;
(2)只写入一次流信息;

我们采用了方案二,新建一个自定义的 AppendObjectOutputStream 类,让其继承自 ObjectOutputStream 类,重写父类的 writeStreamHeader() 方法,当创建 AppendObjectOutputStream 类的对象的时候进行判断,以达到文件中只存在一个 StreamHeader 的目的。

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

/**
 * 解决 java.io.StreamCorruptedException: invalid type code: AC 异常
 * 原因分析参考博客:https://blog.csdn.net/weixin_51008866/article/details/121061149
 *
 * @author Spring-_-Bear
 * @version 2021-10-30 21:48
 */
public class AppendObjectOutputStream extends ObjectOutputStream {
    
    
    /**
     * The path of the file
     */
    private static File file = null;

    public static File getFile() {
    
    
        return file;
    }

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

    /**
     * 调用父类的构造器,初始化父类
     *
     * @param file
     * @throws IOException
     */
    public AppendObjectOutputStream(File file) throws IOException {
    
    
        super(new FileOutputStream(file, true));
    }

    /**
     * 重写父类的 writeSteamHeader() 方法以实现文件中只存在一个StreamHeader
     *
     * @throws IOException I/O 异常
     */
    @Override
    public void writeStreamHeader() throws IOException {
    
    
        // 如果文件为空直接写入 StreamHeader
        if (file == null) {
    
    
            super.writeStreamHeader();
        } else {
    
    
            // 文件长度为0即文件中没有内容时也写入 StreamHeader
            if (file.length() == 0) {
    
    
                super.writeStreamHeader();
            } else {
    
    
                // 文件存在且文件中存在内容,则说明文件中已经存在了一个 StreamHeader
                // 则调用父类的 reset() 方法保证文件中只存在一个 StreamHeader
                this.reset();
            }
        }
    }
}

实现效果

测试代码

import java.io.*;

/**
 * @author Spring-_-Bear
 * @version 2021-10-31 11:45
 */

public class AppendObjectOutputStreamEx {
    
    

    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
    
        File file = new File("people.txt");
        // 初始化 AppendObjectOutStream 类中的文件路径
        AppendObjectOutputStream.file = file;

        // 第一次保存对象信息,添加一个了 StreamHeader,正常读取
        AppendObjectOutputStream appendObjectOutputStream = new AppendObjectOutputStream(file);
        appendObjectOutputStream.writeObject(new People("Tom", 'M', 21));
        appendObjectOutputStream.writeObject(new People("Smith", 'M', 22));
        appendObjectOutputStream.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        System.out.println("第一次读取到的的对象信息:" + ois.readObject());
        System.out.println("第一次读取到的的对象信息:" + ois.readObject());
        ois.close();

        // 追加保存对象
        appendObjectOutputStream = new AppendObjectOutputStream(file);
        appendObjectOutputStream.writeObject(new People("Mary", 'M', 21));
        appendObjectOutputStream.writeObject(new People("Jack", 'M', 20));
        appendObjectOutputStream.close();

        // 再次创建对象输入流读取对象信息
        ois = new ObjectInputStream(new FileInputStream(file));
        // 以下两行代码读取第一次添加的对象信息,正常输出
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        // 由于重写了 writeStreamHeader() 方法,则文件中只会存在一个 StreamHeader,故正常读取
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        System.out.println("第二次读取到的的对象信息:" + ois.readObject());
        ois.close();
    }
}

输出结果

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

猜你喜欢

转载自blog.csdn.net/weixin_51008866/article/details/121061149