java使用try-with-resources实现Serializable接口完成深拷贝例子遭遇EOFException
时间: 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