zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    mysql联合主键自增、主键最大长度小记
    针对list集合进行分页展示
    初识javascript变量和基本数据类型
    12.19如何定义正则表达式
    人民币符号在html的显示方法
    windows下的NodeJS安装
    css 实现未知图片垂直居中
    IE678下placeholder效果,支持文本框和密码框
    jvm004 解析与分派
    jvm003 类加载的过程
  • 原文地址:https://www.cnblogs.com/xfk1999/p/java-try-with-resources-EOFException.html
Copyright © 2011-2022 走看看