最近線上的系統被檢測出有錯誤日志,領導讓我檢查下問題,我就順便了解了下這個異常。
了解一個類,當然是先去看他的API,EOFException的API如下:
通過這個API,我們可以得出以下信息:
- 這是一個IO異常的子類,名字也是END OF FILE的縮寫,當然也表示流的末尾
- 它在表明一個信息,流已經到末尾了,而大部分做法是以特殊值的形式返回給我們,而不是拋異常
也就是說這個異常是被主動拋出來的,而不是底層或者編譯器返回給我的,就像NullPointerException或IndexOutOfBoundsException一樣。
我們先來看InputStream,這個輸入流,當讀到了結尾會怎么樣,看看API介紹:
可以看到如果到達流的末尾,那么會返回-1,也就是說我們可以根據這個-1來判斷是否到達流的末尾。
同樣的我們看一下輸入流的包裝類BufferedReader,它有一個讀一行的方法:
也可以發現當讀到流的末尾,通過返回值null來告訴我們到達流的末尾了,也就是說通過返回一個不可能的值來表示到達流的末尾。
那我們找一個EOFException的例子,在jdk類中就有一個,那就是ObjectInputStream,我寫了一個測試代碼,如下:
package yiwangzhibujian.objectstream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class ObjectStream {
public static void main(String[] args) throws Exception {
User user1=new User("yiwangzhibujian",27);
User user2=new User("laizhezhikezhui",24);ByteArrayOutputStream bos</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> ByteArrayOutputStream();</br> ObjectOutputStream oos</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> ObjectOutputStream(bos);</br> oos.writeObject(user1);</br> oos.writeObject(user2);</br> oos.writeObject(</span><span style="color: #0000ff;">null</span><span style="color: #000000;">);</br> </span><span style="color: #0000ff;">byte</span>[] data =<span style="color: #000000;"> bos.toByteArray();</br> ByteArrayInputStream bis</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> ByteArrayInputStream(data);</br> ObjectInputStream ois</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> ObjectInputStream(bis);</br></br> System.out.println(ois.readObject());</br> System.out.println(ois.readObject());</br> System.out.println(ois.readObject());</br> System.out.println(ois.readObject());</br>
}
}
class User implements Serializable{
private static final long serialVersionUID = 1L;
public String name;
public int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
控制台輸出結果為:
User [name=yiwangzhibujian, age=27] User [name=laizhezhikezhui, age=24] null Exception in thread "main" java.io.EOFException
at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:2608)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1319)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at yiwangzhibujian.objectstream.ObjectStream.main(ObjectStream.java:28)
可以感覺到EOFException的用意,因為我們可以往流中放入null值,所以我們沒法找到一個不可能的值來表示到達流的末尾,所以只能通過拋異常的方式來告訴用戶到達末尾了,相應的拋異常部分的源碼如下:
byte peekByte() throws IOException {
int val = peek();
if (val < 0) {
throw new EOFException();
}
return (byte) val;
}
也就是說,ObjectInputStream在讀取具體的對象之前會優先讀取一個標識符,它通過是否能讀到符號來判斷是否到達流的末尾,因為再底層的流會通過返回-1來表明,然后ObjectInputStream會根據標識符來判斷讀到的是什么類型,因為ObjectOutputStream 在寫入內容的時候會這么做:
所以說ObjectInputStream可以自己判斷流是否到達末尾,但是它無法告訴我們,我們不能替代他們讀取這個標記,不然ObjectInputStream將識別不了下一個內容的實際類型。
所以呢,對於這種異常的一般解決方法就是,捕獲,可以記錄日志,也可以不做處理,捕獲異常以后,把之前讀到的數據進行后續的處理就可以了,因為那就是所以的數據。還有就是如果打算記錄日志,不要把它的堆棧信息打印出來,容易給人以錯覺。畢竟EOFException實質上只是一個消息而已。
當然拋異常的做法還是有一些偏激,但是當ObjectInputStream在不知道讀取對象數量的情況下,確實無法判斷是否讀完,除非你把之前寫入對象流的數量記錄下來。所以說出現這個異常時就認真分析一下,這個異常是不是代表一個信息。
希望我對這個問題的理解,能幫助到遇到同樣問題的人。