java.io.StreamCorruptedException: invalid type code: AC

一般,可以使用ObjectInputStream把对象写出到文件,再使用ObjectOutputStream把对象读取出来。我以为这样做完美无缺,就像使用FileInputStream和FileOutputStream一样。

1.问题
直到我遇到这个问题:我以追加的形式写出,每次只写出一个对象,然后就关闭写出流。接着再重复写出一个对象。在读取时这个文件时,每次读一个对象,直到读到EOF。结果让我很意外,我只读取了一个对象就报错了。

大体意思的代码如下:

/* 以追加形式写入一个对象到文件中*/
ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("test.txt", true));
String str1 = "a";
oos1.writeObject(str1);
oos1.flush();
oos1.close();
		
/* 再次以相同方式写入一个对象 */
ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream("test.txt", true));
String str2 = "b";
oos2.writeObject(str2);
oos2.flush();
oos2.close();
		
		
		
/* 读取刚才写入的两个对象 */
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
String str3 = (String) ois.readObject();
String str4 = (String) ois.readObject();
System.out.println(str3 + str4);
ois.close();

控制台提示:

Exception in thread "main" java.io.StreamCorruptedException: invalid type code: AC
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1601)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at Test.main(Test.java:28)

问题处在了第二次调用readObject()时:

String str4 = (String) ois.readObject();

2.分析

要解决这个错误,首先要了解一个事实:

用对象流写到文件中时,首先会将流的头部信息写出,之后才开始写具体对象数据。

我们可以从ObjectOutputStream的源码(jdk1.8)中看到:

/* ObjectOutputStream的构造器 */
	public ObjectOutputStream(OutputStream out) throws IOException {
        verifySubclass();
        bout = new BlockDataOutputStream(out);
        handles = new HandleTable(10, (float) 3.00);
        subs = new ReplaceTable(10, (float) 3.00);
        enableOverride = false;
        writeStreamHeader();		//注意这一行,对象流构造时,趁我们不注意偷偷写入头部信息
        bout.setBlockDataMode(true);
        if (extendedDebugInfo) {
            debugInfoStack = new DebugTraceInfoStack();
        } else {
            debugInfoStack = null;
        }
	}

 writeStreamHeader这个方法写入了两个short类型的数据:

protected void writeStreamHeader() throws IOException {
        bout.writeShort(STREAM_MAGIC);
        bout.writeShort(STREAM_VERSION);
}

 两个short类型的意义:

/**
     * Magic number that is written to the stream header.(标志流头部信息的魔数)
     */
    final static short STREAM_MAGIC = (short)0xaced;

    /**
     * Version number that is written to the stream header.(流信息的版本号)
     */
    final static short STREAM_VERSION = 5;

在使用十六进制打开之前对象写入的文件test.txt时,发现里面确实包含有这两个数:

 官方文档也说明了输出流的信息是由头部信息和数据组成的:

Primitive data, excluding serializable fields and externalizable data, is written to the ObjectOutputStream in block-data records. A block data record is composed of a header and data. The block data header consists of a marker and the number of bytes to follow the header.

总结一下错误的原因:

ObjectOutputStream在构造时,首先会将流的头部信息(“AC ED 00 05”)写入到文件中。然后在调用write时,写入其他数据,直到关闭。当再次使用ObjectOutputStream追加写入对象时,头部信息又会再次写入。所以当你用ObjectInputStream来读取对象时,流虽然能够将第一个头部信息(“AC ED 00 05”)跳过,但是其他头部信息会当做数据来处理,造成无法解析,所以读取会出现StreamCorruptedException: invalid type code: AC错误。

3.解决
了解了上述原理后,也就大概有了两种解决思路:

  1. 不读取头部信息
  2. 不写入头部信息

第一种方法比较笨,容易出错,但是实现较为简单。

		/* 读取刚才写入的两个对象 */
		FileInputStream fs = new FileInputStream("test.txt");
		ObjectInputStream ois = new ObjectInputStream(fs);
		String str3 = (String) ois.readObject();
		fs.skip(4);			//跳过两次写入过程中间的头部信息
//		ois.skip(4);		//如果使用ois的方法依然会有错误,希望有大神解惑
		String str4 = (String) ois.readObject();
		System.out.println(str3 + str4);
		ois.close();

 第二种方法需要继承ObjectOutputStream和ObjectInputStream来自定义实现类,但是要特别注意JDK的版本信息。

class MyObjectOutputStream extends ObjectOutputStream {

	public MyObjectOutputStream(OutputStream out) throws IOException {
		super(out);
	}
	
	@Override
	protected void writeStreamHeader() throws IOException {
        //重写读取头部信息方法:不写入头部信息
		super.reset();
	}
}

class MyObjectInputStream extends ObjectInputStream {
	public MyObjectInputStream(InputStream in) throws IOException {
		super(in);
	}
	
	@Override
	protected void readStreamHeader() throws IOException {
    	//重写读取头部信息方法:什么也不做
	}
}

 测试:

/* 以追加形式写入一个对象到文件中*/
MyObjectOutputStream oos1 = new MyObjectOutputStream(new FileOutputStream("test.txt", true));
String str1 = "a";
oos1.writeObject(str1);
oos1.flush();
oos1.close();
		
/* 再次以相同方式写入一个对象 */
MyObjectOutputStream oos2 = new MyObjectOutputStream(new FileOutputStream("test.txt", true));
String str2 = "b";
oos2.writeObject(str2);
oos2.flush();
oos2.close();
		
		
		
/* 读取刚才写入的两个对象 */
MyObjectInputStream ois = new MyObjectInputStream(new FileInputStream("test.txt"));
String str3 = (String) ois.readObject();
String str4 = (String) ois.readObject();
System.out.println(str3 + str4);
ois.close();

 问题解决了,网上查找前辈的记录,独立解决问题。

————————————————
版权声明:本文为CSDN博主「假装四处看风景」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Marco___/article/details/98380665

 

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值