StreamCorruptedException的基本原理

刚开始学习使用ObjectInputStream的时候经常会遇见StreamCorruptedException异常,遇到这个异常时也是非常头疼,因为不知道原因,只能通过百度和自己猜测去尝试解决,稀里糊涂解决之后第二次又遇见就抓狂了,所以这次好好看了一下这个异常,总结了一点自己的经验,和大家探讨一下。

这个异常显示如下:


首先说一下这个异常抛出的原理

这个异常信息里面重要的一句话是 invalid stream header : 00000000 ,这个信息对我们找出问题所在有帮助,具体下面解释。

我们先检查出错的代码,报错行数如下:(笔者遇到的问题都是从这个地方抛出的)


继续点击查看ObjectInputStream的构造方法:


首先关注到这里有一个bin输入流,这个bin是一个BlockDataInputStream,它是ObjectInputStream的一个内部类,这里先不用管它,我们只需知道它是被通过ObjectInputStream的构造方法传进来的流装饰的即可。重点是在readStreamHeader() 这个方法里面,


可以看到StreamCorruptedException正是在这个方法中抛出的,而抛出原因是bin读取的两个short型变量和指定的常量不符,查看这两个常量,在一个叫ObjectStreamConstants的接口里面:


如上,注释解释这两个数都是写在流的头里面,两个都是十六进制的,一个ACED,一个为5。

那通过上面的信息我们可以得出:1、异常是从ObjectInputStream中抛出的。2、每创建一个ObjectInputStream都会往一个流的头部读取两个信息。3、这个流和用来装饰ObjectInputStream的流有关。

而产生这个异常的原因正是创建ObjectInputStream时读取的信息与ObjectStreamConstants里面定义好的值不同,像上面抛出的原因是读出的数为00000000。

下面模拟一个场景验证一下:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Test {

	public static void main(String[] args) throws IOException,
			ClassNotFoundException {
		File file = new File("test.txt");
		// 文件输出流关联一个文件
		FileOutputStream fos = new FileOutputStream(file);
		// 对象流
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		A a = new A();
		oos.writeObject(a);
		FileInputStream fis = new FileInputStream(file);
		// 创建对象输入流
		ObjectInputStream ois = new ObjectInputStream(fis);
		ois.readObject();
	}

}

class A implements Serializable {
	public int a = 100;
}

用一个文件输出流与对象流结合使用,将一个自定义对象A写到test.txt文件里面,用UltraEdit打开如下:


可以看到,文件开头正是AC ED 00 05,与指定相符,所以可以正常读取对象。对于里面其他的字节是什么意思,大家可以参考在http://blog.csdn.net/e5945/article/details/6162529 这篇博客里面的解释,ObjectStreamConstants里面还定义有很多别的常量,因为对象流的传递的对象必须要实现序列化,java里面自然要定义一个协议来接收对象,比如这个流的开头必须是AC ED,它是STREAM_MAGIC,声明使用了序列化协议,第二个是STREAM_VERSION,用来标识协议的版本,里面还有TC_OBJECT = 73,代表一个对象的开始,等等等等。

那既然每次创建一个ObjectInputStream都会读取文件头,那自然而然我们就想到,每次创建一个ObjectOutputStream的时候都会写入一个文件头。和输入流相似,里面有一个writeStreamHeader()方法,方法写入和读取的值一样,下面是源代码:

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

分析完原因,那什么时候会出错呢,我们再定义一个ObjectInputStream:(部分代码)

public static void main(String[] args) throws IOException,
			ClassNotFoundException {
		File file = new File("test.txt");
		// 文件输出流关联一个文件
		FileOutputStream fos = new FileOutputStream(file);
		// 对象流
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		A a = new A();
		oos.writeObject(a);
		FileInputStream fis = new FileInputStream(file);
		// 创建对象输入流
		ObjectInputStream ois = new ObjectInputStream(fis);
		ObjectInputStream ois2 = new ObjectInputStream(fis);//再定义一个
		ois.readObject();
	}
这个时候就报错了:

看到这个信息是73 72 00 0B 正好是上面头信息完之后的信息,显然第二个对象流以为这个是头信息了。换一种形式,我们保留一个输入流,再定义一个输出流:(部分代码)

public static void main(String[] args) throws IOException,
			ClassNotFoundException {
		File file = new File("test.txt");
		// 文件输出流关联一个文件
		FileOutputStream fos = new FileOutputStream(file);
		// 对象流
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		ObjectOutputStream oos2 = new ObjectOutputStream(fos);//再定义一个输出流
		
		A a = new A();
		oos.writeObject(a);
		FileInputStream fis = new FileInputStream(file);
		// 创建对象输入流
		ObjectInputStream ois = new ObjectInputStream(fis);
		ois.readObject();
	}
这时候又报错了:


我们打开写出的文件,发现文件变成了:


也就是说,第二个ObjectOutputStream在第一个写出的内容之前又加了一个头部,所以这时候报的错是 invalid type code:AC ,注意这个错误和之前的不同,因为读完头信息后像上面说的,理应是一个TC_OBJECT = 73,代表一个对象的开始。

那为什么不同对象流里面的bin的读写会互相影响呢,这就是因为他们用了同一个装饰流,所以最直接的方法是将里面的流换成不同的就可以了。

当然在一个项目里面不会因为这么简单的几行代码暴露出问题,但是重要的是要明白出错的原因,至少要读得懂这个异常抛出的原因,下面是根据个人经验给的一点小建议:

在使用对象流的时候尽量只创建一个对象,且装饰它的流也只有一个,然后在用的时候再将对象流传过去,如果必须要有多个,就要使每个ObjectOutputStream和ObjectInputStream一一对应,当然具体情况还是具体分析。因为笔者也是初学者,对流的理解还不够深刻,这个问题到底如何解决还得再仔细研究研究。


  • 12
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值