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

这里的ByteArrayOutputStreamByteArrayInputStream不是基于外部流, 可以不使用close进行关闭, java8手册上有明确说明



图组1 ByteArrayOutputStream与ByteArrayInputStream

至于为什么要关闭流, 因为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



图2 InputStream与BufferedReader

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

原文地址:https://www.cnblogs.com/xfk1999/p/java-try-with-resources-EOFException.html