java使用try-with-resources实现Serializable接口完成深拷贝例子遭遇EOFException
作者:@caseyfu
本文为作者原创,转载请注明出处:https://www.cnblogs.com/caseor/p/java-try-with-resources-EOFException.html
目录
问题
解决
try-catch-finally
try-with-resources
参考
时间: 2020年08月26日
问题
在写实现Serializable接口完成深拷贝时想用java7的特性try-with-resources优雅的关闭流, 结果遇到了EOFException问题
解决
try-catch-finally
import java.io.*; /** * @ClassName Student * @Author Casey Fu * @Version v1.0.0 * @Description 深拷贝传统写法 * @Date 2020/8/22 */ public class Student implements Serializable { private String name; private Subject subject; public Student(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } public Student deepSerialize() { Student student = null; ByteArrayOutputStream os = null; ObjectOutputStream oos = null; ByteArrayInputStream is = null; ObjectInputStream ois = null; try { os = new ByteArrayOutputStream(); oos = new ObjectOutputStream(os); oos.writeObject(this); is = new ByteArrayInputStream(os.toByteArray()); ois = new ObjectInputStream(is); student = (Student) ois.readObject(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (oos != null) { oos.close(); } if (ois != null) { ois.close(); } } catch (Exception e) { e.printStackTrace(); } } return student; } public static void main(String[] args) { Student ming = new Student("小明"); ming.setSubject(new Subject("语文")); System.out.println(" 克隆前 ming hashcode:" + ming.hashCode() + " - Subject hashcode: " + ming.getSubject().hashCode()); System.out.println(" 把小明克隆成cai徐坤......"); Student kun = ming.deepSerialize(); System.out.println(" 克隆后 kun hashcode:" + kun.hashCode() + " - Subject hashcode: " + kun.getSubject().hashCode()); System.out.println(" 克隆后 Subject的hashcode不同, 说明ming和kun是两个不同对象, 是深拷贝"); } } class Subject implements Serializable { private String name; public Subject(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
克隆前 ming hashcode:2133927002 - Subject hashcode: 1836019240 把小明克隆成cai徐坤...... 克隆后 kun hashcode:1531448569 - Subject hashcode: 1867083167 克隆后 Subject的hashcode不同, 说明ming和kun是两个不同对象, 是深拷贝 Process finished with exit code 0
这里的ByteArrayOutputStream
和ByteArrayInputStream
不是基于外部流, 可以不使用close进行关闭, java8手册上有明确说明


至于为什么要关闭流, 因为GC不能监管全部的输入流和输入流, 如果不关闭就会一直占用内存, 最终导致outOfMemoryError
try-with-resources
先上错误写法:
import java.io.*; /** * @ClassName Student * @Author Casey Fu * @Version v1.0.0 * @Description 深拷贝 * @Date 2020/8/22 */ public class Student implements Serializable { private String name; private Subject subject; public Student(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } public Student deepSerialize() { Student student = null; try ( ByteArrayOutputStream os = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(os); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); ObjectInputStream ois = new ObjectInputStream(is) ) { oos.writeObject(this); student = (Student) ois.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return student; } public static void main(String[] args) { Student ming = new Student("小明"); ming.setSubject(new Subject("语文")); System.out.println(" 克隆前 ming hashcode:" + ming.hashCode() + " - Subject hashcode: " + ming.getSubject().hashCode()); System.out.println(" 把小明克隆成cai徐坤......"); Student kun = ming.deepSerialize(); System.out.println(" 克隆后 kun hashcode:" + kun.hashCode() + " - Subject hashcode: " + kun.getSubject().hashCode()); System.out.println(" 克隆后 Subject的hashcode不同, 说明ming和kun是两个不同对象, 是深拷贝"); } } class Subject implements Serializable { private String name; public Subject(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
克隆前 ming hashcode:2133927002 - Subject hashcode: 1836019240 把小明克隆成cai徐坤...... java.io.EOFException at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:2960) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1540) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) at org.casey.designpattern.prototype.threeleveldeepserializable.Student.deepSerialize(Student.java:79) at org.casey.designpattern.prototype.threeleveldeepserializable.Student.main(Student.java:91) Exception in thread "main" java.lang.NullPointerException at org.casey.designpattern.prototype.threeleveldeepserializable.Student.main(Student.java:92) Process finished with exit code 1
自定义的deepSerialize()方法, 把需要关闭的流写在一堆(错误的关键点)
EOFException介绍[1]:
在输入流把数据按批次输入到内存时会判断流中是否还有数据, 如果有数据就返回数据, 没数据就返回一个特殊值-1或null
如InputStream的read()方法, 在读到流的末尾时返回-1
如BufferedReader的readLine()方法, 在读到流末尾时返回null


ObjectInputStream的readObject()方法主要由底层的一个readObject0()实现, 会调用peekByte()查看当前流还有多少, 如果到了流的末尾就抛出EOFException而不是像其它输入流返回一个特殊值, 以下是peekByte()的原码
/** * Peeks at (but does not consume) and returns the next byte value in * the stream, or throws EOFException if end of stream/block data has * been reached. */ byte peekByte() throws IOException { int val = peek(); if (val < 0) { throw new EOFException(); } return (byte) val; }
我把上面的弄懂了之后还是不知道怎么解决EOF问题, 我就和上面try-catch-finally的代码做对比, 分析出可能原因是由于代码的调用次序问题
当ByteArrayOutputStream和ObjectOutputStream还没有把对象序列化成流的时候, 通过 ByteArrayInputStream 构造方法将os.toByteArray()作为了参数传入了输入流, 以致于输入流的数据一直为空, 所以抛出EOFException
下面上正确的deepSerialize()方法
// 正确的用try-with-resources深拷贝的方法 public Student deepSerialize() { Student student = null; ByteArrayOutputStream os = new ByteArrayOutputStream(); try (ObjectOutputStream oos = new ObjectOutputStream(os)) { // 第一步 oos.writeObject(this); } catch (IOException e) { e.printStackTrace(); } // 第二步 ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); try (ObjectInputStream ois = new ObjectInputStream(is)) { student = (Student) ois.readObject(); } catch (ClassNotFoundException | IOException e) { e.printStackTrace(); } return student; }
克隆前 ming hashcode:2133927002 - Subject hashcode: 1836019240 把小明克隆成cai徐坤...... 克隆后 kun hashcode:1531448569 - Subject hashcode: 1867083167 克隆后 Subject的hashcode不同, 说明ming和kun是两个不同对象, 是深拷贝 Process finished with exit code 0
运行结果正确, 分两批次, 先把对象序列化为流, 然后以os.toByteArray()作为数据载体放进到输入流, 最后从输入流中读取对象
前面也说了, 不用关闭ByteArrayOutputStream与ObjectInputStream的输入输出流, 所以就不写进try里面
以上就是关于 java使用try-with-resources实现Serializable接口完成深拷贝例子遭遇EOFException 问题的描述与解决方案, 如有错误请指正, 欢迎交流
参考
[1] 【疑难杂症04】EOFException异常详解. https://www.cnblogs.com/yiwangzhibujian/p/7107084.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了