刚开始学习使用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一一对应,当然具体情况还是具体分析。因为笔者也是初学者,对流的理解还不够深刻,这个问题到底如何解决还得再仔细研究研究。