不能靠捕获 eofexception 判断“读完了”,因为它标识的是异常中断(如流截断、网络断连、序列化失败),而非正常结束;真正可靠的方式是应用层显式协议,如长度前缀、对象计数或定界符。

直接用 EOFException 判断“读完了”是危险的惯性操作。它不是流正常结束的信号,而是读取行为与数据实际长度不匹配时抛出的异常——说明程序预期还能读到更多内容,但底层字节流已提前终止。
为什么不能靠捕获 EOFException 来控制循环
ObjectInputStream、DataInputStream 等类在设计上就不承诺流末尾一定对应业务逻辑的“自然结束”。比如:
- 写端只写了一个对象后关闭流,读端调用 readObject() 就会抛 EOFException —— 这不是错误,但也不代表“该读的都读完了”,只是协议没约定清楚;
- 网络传输中 socket 被对端静默关闭,或中间链路断开,readInt() 或 readUTF() 同样触发 EOFException,此时数据已损坏,不是“读完”,而是“读崩了”;
- 文件被截断、磁盘写入失败、序列化版本不一致,都可能让反序列化中途卡在结构体某字段,误判为“EOF”。
真正可靠的边界识别方式
把“流是否还有数据”这个判断权从 JVM 底层移到应用层,用显式协议代替异常试探:
- 长度前缀法:写端先 writeInt(len),再 write(byte[], 0, len);读端先 readInt() 得长度,再循环 read() 直到凑够字节数 —— read() 返回 -1 才是真 EOF,且只应在预期长度未满时出现;
- 计数头法:适合对象流,写端 out.writeInt(count),再 for 循环 writeObject();读端先读 count,再严格循环 count 次 —— 即使流意外中断,也能在第 i 次读取失败时明确知道缺第几个对象;
- 定界符法:二进制场景可用特殊字节序列(如 0xFF 0xFE)作消息尾,每次读满缓冲区后扫描边界,避免粘包/半包干扰解析上下文。
如果必须捕获 EOFException,该怎么收场
仅限无法修改协议的遗留对接场景,且必须满足三个前提:
- 只 catch EOFException,绝不 catch IOException 或 Exception —— 其他异常(如 SocketException、InvalidClassException)代表真实故障,需单独处理;
- catch 块内不做重试、不继续调用 readXXX(),因为流内部状态已失效;
- 确保外层流(FileInputStream / SocketInputStream)确实已由对端关闭,而非异常断连 —— 可通过检查异常堆栈中是否含 “Connection reset” 或 “Broken pipe” 来区分。
关键提醒:available() 不可靠
DataInputStream.available() 在文件流中可能返回准确值,但在网络流中常返回 0 即使数据还在路上。它不能替代长度协议,也不能作为 while 循环的退出条件。真正可信赖的只有:你写入时定义的边界,而不是流自己“说没数据了”。










