• Serializable和Externalizable浅析


    Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。从而达到网络传输、本地存储的效果。

    本文主要要看看JDK中使用Serializable和Externalizable接口来完成Java对象序列化,并给出部分属性序列化的几种方式,最终做出Serializable和Externalizable接口的几个方面的对比。

    注:本文不讨论为什么不用第三方工具包完成序列化等~

    序列化Serializable

     要实现Java对象的序列化,只要将类实现Serializable或Externalizable接口即可。

    采用类实现Serializable接口的序列化很简单,Java自动会将非transient修饰属性序列化到指定文件中去。

    举个例子:

    import java.io.Serializable;
    import java.util.List;
    
    /**
     * @Type Book.java
     * @Desc 
     * @author wangmengjun
     * @date 2017年12月1日 下午7:16:29
     * @version 
     */
    public class Book implements Serializable {
    
        private static final long serialVersionUID = -6212470156629515269L;
    
        /**书名*/
        private String name;
    
        /**ISBN*/
        private String isbn;
    
        /**作者*/
        private List<String> authors;
    
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
    
        /**
         * @param name the name to set
         */
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * @return the isbn
         */
        public String getIsbn() {
            return isbn;
        }
    
        /**
         * @param isbn the isbn to set
         */
        public void setIsbn(String isbn) {
            this.isbn = isbn;
        }
    
        /**
         * @return the authors
         */
        public List<String> getAuthors() {
            return authors;
        }
    
        /**
         * @param authors the authors to set
         */
        public void setAuthors(List<String> authors) {
            this.authors = authors;
        }
    
        /* (non-Javadoc)
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return "Book [name=" + name + ", isbn=" + isbn + ", authors=" + authors + "]";
        }
    
    }

    然后编写一个用于序列化和反序列的小工具类,

    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    
    /**
     * @Type SerializationUtil.java
     * @Desc 
     * @author wangmengjun
     * @date 2017年12月1日 下午7:23:04
     * @version 
     */
    public class SerializationUtil {
     
        /**
         * 从一个给定的文件完成反序列化
         */
        public static Object deserialize(String fileName) throws IOException,
                ClassNotFoundException {
            FileInputStream fis = new FileInputStream(fileName);
            BufferedInputStream bis = new BufferedInputStream(fis);
            ObjectInputStream ois = new ObjectInputStream(bis);
            Object obj = ois.readObject();
            ois.close();
            return obj;
        }
     
        /**
         * 将给定的对象序列化到指定的文件中去
         */
        public static void serialize(Object obj, String fileName)
                throws IOException {
     
            FileOutputStream fos = new FileOutputStream(fileName);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            oos.close();
        }
    } 

    写个测试类,测试一下:

    public class SerializableTest {
    
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            
            Book book = new  Book();
            book.setIsbn("ABC123456789");
            book.setName("Hello Java");
            book.setAuthors(Arrays.asList("John","Eric"));
            //book==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]]
    
            System.out.println("book==>" + book);
            
            /**
             * 将book对象序列化到book.temp文件中去
             */
            String fileName = "book.temp";
            SerializationUtil.serialize(book, fileName);
            
            /**
             * 从book.temp文件中,反序列化一个Book对象
             */
            Book deserializedBook = (Book) SerializationUtil.deserialize(fileName);
            //deserializedBook==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]]
            System.out.println("deserializedBook==>" + deserializedBook);
        }
    } 

    一个简单的示例,就完成了Book对象的序列化和反序列化。

    在上述示例中,Book对象中的所有的属性都被序列化。如果里面存在部分属性,我们不想要被序列化,该如何做呢?

    部分属性序列化

    如果只想将部分属性进行序列化,可以采用如下几种方法:

    1. 使用transient关键字
    2. 添加writeObject和readObject方法
    3. 使用Externalizable实现

    使用transient关键字

    对属性添加transient关键字,可以防止该属性序列化~

    如下示例中,我们不想isbn和authors属性被序列,添加上transient关键字实现一下~

    public class Book implements Serializable {
    
    	private static final long serialVersionUID = -6212470156629515269L;
    
    	/** 书名 */
    	private String name;
    
    	/** ISBN */
    	private transient String isbn;
    
    	/** 作者 */
    	private transient List<String> authors;
    
     ... ... 
    
    }

    运行上述提到的SerializableTest.java程序,输出如下结果,我们可以看出isbn和authors的值都为null,表明这两个属性没有被序列化~

    book==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]]
    deserializedBook==>Book [name=Hello Java, isbn=null, authors=null]

    添加writeObject和readObject方法

    另外,我们也可以采用编写私有方法writeObject和readObject,完成部分属性的序列化。修改Book类,增加writeObject 和 readObject方法,如:

    public class Book implements Serializable {
    
    	private static final long serialVersionUID = -6212470156629515269L;
    
    	/** 书名 */
    	private String name;
    
    	/** ISBN */
    	private String isbn;
    
    	/** 作者 */
    	private List<String> authors;
    
    	private void writeObject(ObjectOutputStream oos) throws IOException {
    		// oos.defaultWriteObject();
    		oos.writeObject(name);
    		oos.writeObject(isbn);
    	}
    
    	private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    		// ois.defaultReadObject();
    		name = (String) ois.readObject();
    		isbn = (String) ois.readObject();
    	}
    
    ... ...
    
    } 

    在上述示例中,我们选择序列化的属性为name和isbn,书本作者authors没有序列化~

    同样,使用SerializableTest.java类测试一下,结果如下:

    book==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]]
    deserializedBook==>Book [name=Hello Java, isbn=ABC123456789, authors=null]

    注意:

    这里的writeObject和readObject是private的且是void的~

    Java调用ObjectOutputStream类检查其是否有私有的,无返回值的writeObject方法,如果有,其会委托该方法进行对象序列化。

    检查是否有合适的方法如下:

    使用Externalizable实现

    还有一种方式,就是使用Externalizable完成部分属性的序列化

    Externalizable继承自Serializable,使用Externalizable接口需要实现writeExternal以及readExternal方法~在writeExternal方法中,写入想要外部序列化的元素~

    public interface Externalizable extends java.io.Serializable {
        /**
         * The object implements the writeExternal method to save its contents
         * by calling the methods of DataOutput for its primitive values or
         * calling the writeObject method of ObjectOutput for objects, strings,
         * and arrays.
         *
         * @serialData Overriding methods should use this tag to describe
         *             the data layout of this Externalizable object.
         *             List the sequence of element types and, if possible,
         *             relate the element to a public/protected field and/or
         *             method of this Externalizable class.
         *
         * @param out the stream to write the object to
         * @exception IOException Includes any I/O exceptions that may occur
         */
        void writeExternal(ObjectOutput out) throws IOException;
    
        /**
         * The object implements the readExternal method to restore its
         * contents by calling the methods of DataInput for primitive
         * types and readObject for objects, strings and arrays.  The
         * readExternal method must read the values in the same sequence
         * and with the same types as were written by writeExternal.
         *
         * @param in the stream to read data from in order to restore the object
         * @exception IOException if I/O errors occur
         * @exception ClassNotFoundException If the class for an object being
         *              restored cannot be found.
         */
        void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
    }

    修改Book类的内容,如下:

    import java.io.Externalizable;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectOutput;
    import java.util.List;
    
    /**
     * @Type Book.java
     * @Desc
     * @author wangmengjun
     * @date 2017年12月1日 下午7:16:29
     * @version
     */
    public class Book implements Externalizable {
    
    	/** 书名 */
    	private String name;
    
    	/** ISBN */
    	private String isbn;
    
    	/** 作者 */
    	private List<String> authors;
    
    	@Override
    	public void writeExternal(ObjectOutput out) throws IOException {
    		out.writeObject(name);
    		out.writeObject(isbn);
    	}
    
    	@Override
    	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    		name = (String) in.readObject();
    		isbn = (String) in.readObject();
    	}
    
    	/**
    	 * @return the name
    	 */
    	public String getName() {
    		return name;
    	}
    
    	/**
    	 * @param name
    	 *            the name to set
    	 */
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	/**
    	 * @return the isbn
    	 */
    	public String getIsbn() {
    		return isbn;
    	}
    
    	/**
    	 * @param isbn
    	 *            the isbn to set
    	 */
    	public void setIsbn(String isbn) {
    		this.isbn = isbn;
    	}
    
    	/**
    	 * @return the authors
    	 */
    	public List<String> getAuthors() {
    		return authors;
    	}
    
    	/**
    	 * @param authors
    	 *            the authors to set
    	 */
    	public void setAuthors(List<String> authors) {
    		this.authors = authors;
    	}
    
    	/*
    	 * (non-Javadoc)
    	 * 
    	 * @see java.lang.Object#toString()
    	 */
    	@Override
    	public String toString() {
    		return "Book [name=" + name + ", isbn=" + isbn + ", authors=" + authors + "]";
    	}
    
    }

    同样,使用SerializableTest.java类测试一下,同样获得如下结果:

    book==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]]
    deserializedBook==>Book [name=Hello Java, isbn=ABC123456789, authors=null]
    

    Externalizable vs Serializable

    Externalizable和Serializable的一些比较点,如下:

    【1】 Serializable 是标识接口

    public interface Serializable {
    }

    实现该接口,无需重写任何方法;

    public interface Externalizable extends java.io.Serializable {
        
        void writeExternal(ObjectOutput out) throws IOException;
    
       
        void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
    }
    

      

    Externalizable 接口继承于Serializable,实现该接口,需要重写readExternal和writeExternal方法~

    【2】Serializable提供了两种方式进行对象的序列化,

    • 采用默认序列化方式,将非transatient和非static的属性进行序列化
    • 编写readObject和writeObject完成部分属性的序列化

    Externalizable 接口的序列化,需要重写writeExternal和readExternal方法,并且在方法中编写相关的逻辑完成序列化和反序列化。

    【3】Externalizable接口的实现方式一定要有默认的无参构造函数~

    如果,没有无参构造函数,反序列化会报错~ 验证一下~ Book添加一个有参数的Book构造函数~

    public class Book implements Externalizable {
    
    	/** 书名 */
    	private String name;
    
    	/** ISBN */
    	private String isbn;
    
    	/** 作者 */
    	private List<String> authors;
    	
    	public Book(String name) {
    		this.name = name;
    	}
    
    ... ... 
    }

    这样,序列化、反序列化之后,报no valid constructor的异常~

    Exception in thread "main" java.io.InvalidClassException: Book; no valid constructor
    	at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
    	at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
    	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1772)
    	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
    	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
    	at SerializationUtil.deserialize(SerializationUtil.java:27)
    	at SerializableTest.main(SerializableTest.java:33)
    

    Serializable接口实现,其采用反射机制完成内容恢复,没有一定要有无参构造函数的限制~

    【4】采用Externalizable无需产生序列化ID(serialVersionUID)~而Serializable接口则需要~

    【5】相比较Serializable, Externalizable序列化、反序列更加快速,占用相比较小的内存

    在项目中,大部分的类还是推荐使用Serializable, 有些类可以使用Externalizable接口,如:

    • 完全控制序列的流程和逻辑
    • 需要大量的序列化和反序列化操作,而你比较关注资源和性能~ 当然,这种情况下,我们一般还会考虑第三方序列化/反序列化工具,如protobuf等进行序列化和反序列化操作~

    转载:Serializable和Externalizable浅析 - 王孟君的个人空间 - OSCHINA - 中文开源技术交流社区

    拓展资料1:Java transient关键字使用小记

  • 相关阅读:
    中标麒麟QT5编译出现:cannot find -lGL 和 collect2:error:ld returned 1 exit status 错误
    虚拟机设置静态ip【实测中标麒麟】
    selenium webdriver如何拿到页面的加载时间
    有感于去哪儿的一道笔试题
    python selenium 常见问题列表
    乙醇的webdriver实用指南java版本
    乙醇的webdriver实用指南ruby版本
    作死的自动化测试
    从龙门镖局看自动化测试
    无用的自动化测试
  • 原文地址:https://www.cnblogs.com/fanbi/p/15248100.html
Copyright © 2020-2023  润新知