这里描述的java.io.EOFException异常是在对象流(也就是ObjectInputStream,ObjectOutputStream)的使用过程中,抛出的。

什么是对象流?

对象流指的是可以直接把一个对象以流的形式传输给其他的介质,比如硬盘
一个对象以流的形式进行传输,叫做序列化。该对象所对应的类,必须是实现Serializable接口。详细学习

如果你使用使用对象流那么必须配套使用,因为用对象流的方式写人文件,文件的开头会有序列头,就像协议一样,是规定好的。如下是用java写人的Hero对象:

1
2
3

序列头 对象 数值
序列头? Hero  I hpL namet Ljava/lang/String;xp ht garen

对象流不同于普通的字节流,当对象流中没有数据时,程序却尝试读取异常,会报EOF错误;而字节流就不会出现这种情况,字节流会返回-1 。

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 初始化Object流语句
FileInputStream fis = new FileInputStream(file);
FileOututStream fos = new FileOutputStream(file);
dis = new ObjectInputStream(fis); // 报错的就是这一行,第xx行
dos = new ObjectOutputStream(fos);

java.io.EOFException
at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2681)
at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3156)
at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:862)
at java.io.ObjectInputStream.<init>(ObjectInputStream.java:358)
at Test.main(Test.java:xx行) // 第xx行报错

因为ObjectInputStream构造函数会从传入的InputStream来读取数据。首先会读取序列化流的头部(serialization stream header)并验证头部。此构造器会一直地”阻塞”,直到与之对应的ObjectOutputStream写入了序列化头部。(阻塞并不完全正确,详细见Canliture的文章),

所以我们先实例化ObjectOutputStream,再实例化 ObjectInputStream,保证在在同一资源的对象流ObjectInputStream能够及时读取到序列化头而不至于阻塞或者引发EOF异常(阻塞对应于Socket IO,EOF异常对应于文件IO)

1
2
dos = new ObjectOutputStream(fos);
dis = new ObjectInputStream(fis);

ObjectInputStream写入的数据,在ObjectOutputStream上读取时,应该按照相同的数据类型依次读取,否则数据类型不等会抛出EOFException

下文的错误把dis.readInt() 改为 dis.readObject()就能解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static void main(String[] args) throws IOException {
File f0 = new File("kkk.out");
FileInputStream fis = null;
FileOutputStream fos = null;
ObjectInputStream dis = null;
ObjectOutputStream dos = null;
try{
if(!f0.exists())f0.createNewFile();

fos = new FileOutputStream(f0);
fis = new FileInputStream(f0);

// 1. 初始化Object流语句
dis = new ObjectInputStream(fis);
dos = new ObjectOutputStream(fos);

// 2. 写"对象"语句
dos.writeInt(1); //异常处
dos.writeObject(new Integer(3));

// 3. 读取,输出语句
System.out.println(dis.readInt() + ","+ dis.readInt());
} catch (Exception e){
e.printStackTrace();
if(fos != null) fos.close();
if(fis != null) fis.close();
if(dos != null) dos.close();
if(dis != null) dis.close();
}
}

总结:

1.对象流不同于普通的字节流,当对象流中没有数据时,程序却尝试读取数据,会报EOFException;而字节流就不会出现这种情况,字节流会返回-1

2.ObjectInputStream写入的数据,在ObjectOutputStream上读取时,应该按照相同的数据类型依次读取,否则数据类型不等会抛出EOFException

3.最好在实际使用的过程中,我们先实例化ObjectOutputStream,再实例化 ObjectInputStream,这是由这两个类的设计思想所决定的。如此能保证在同一资源的对象流ObjectInputStream能够及时读取到序列化头而不至于阻塞或者引发EOF异常(阻塞对应于Socket IO,EOF异常对应于文件IO)

如果想从源码上了解问题请点击下放的原文链接

原文链接:Canliture