Serializable和Externalizable浅析

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

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

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

序列化Serializable 

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

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

举个例子: 

 

package com.lee.test.serializable;

import lombok.Data;

import java.io.*;
import java.util.List;

/**
 * @author:Lee
 * @date: 2019/10/28
 * @company:
 * @description:
 */
@Data
public class Book implements Serializable {
    private static final long serialVersionUID = -5438448475454104576L;

    private String name;

    private String isbn;

    private List<String> authors;
    
}

序列化和反序列化:

 /**
     * 从一个给定的文件完成反序列化
     */
    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();
    }

测试下:

package com.lee.test.serializable;

import java.io.*;
import java.util.Arrays;

/**
 * @author:Lee
 * @date: 2019/10/28
 * @company:
 * @description:
 */
public class SerializableTest1{

    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 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 main(String[] args) throws IOException, ClassNotFoundException {
        Book book = new Book();
        book.setName("红楼梦");
        book.setIsbn("ABC123456789");
        book.setAuthors(Arrays.asList("John","Eric"));
        System.out.println(book);

        String path = "E:\\book.temp";
        serialize(book, path);

        Book book1 = (Book) deserialize(path);
        System.out.println(book1);
    }
}


打印如下:

Book(name=红楼梦, isbn=ABC123456789, authors=[John, Eric])
Book(name=红楼梦, isbn=ABC123456789, authors=[John, Eric])

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

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

 

部分属性序列化

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

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

使用transient关键字

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

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

package com.lee.test.serializable;

import lombok.Data;

import java.io.*;
import java.util.List;

/**
 * @author:Lee
 * @date: 2019/10/28
 * @company:
 * @description:
 */
@Data
public class Book implements Serializable {
    private static final long serialVersionUID = -5438448475454104576L;

    private String name;

    private transient String isbn;

    private transient List<String> authors;

}

 打印结果如下:

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

Book(name=红楼梦, isbn=ABC123456789, authors=[John, Eric])
Book(name=红楼梦, isbn=null, authors=null)

添加writeObject和readObject方法

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

package com.lee.test.serializable;

import lombok.Data;

import java.io.*;
import java.util.List;

/**
 * @author:Lee
 * @date: 2019/10/28
 * @company:
 * @description:
 */
@Data
public class Book implements Serializable {
    private static final long serialVersionUID = -5438448475454104576L;

    private String name;

    private String isbn;

    private List<String> authors;

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.writeObject(name);
        oos.writeObject(isbn);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        name = (String) ois.readObject();
        isbn = (String) ois.readObject();
    }
}

 打印结果如下:

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

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

Book(name=红楼梦, isbn=ABC123456789, authors=[John, Eric])
Book(name=红楼梦, isbn=ABC123456789, authors=null)

注意

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

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

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

writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
                        readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
                        readObjectNoDataMethod = getPrivateMethod(
                            cl, "readObjectNoData", null, Void.TYPE);
                        hasWriteObjectData = (writeObjectMethod != null);

使用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类的内容,如下:

package com.lee.test.serializable;

import lombok.Data;

import java.io.*;
import java.util.List;

/**
 * @author:Lee
 * @date: 2019/10/28
 * @company:
 * @description:
 */
@Data
public class Book implements Externalizable {
    
    private String name;

    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();
    }
}

如上所示,我准备只序列化name 和 isbn 两个字段,作者不序列化。

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

Book(name=红楼梦, isbn=ABC123456789, authors=[John, Eric])
Book(name=红楼梦, 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构造函数~

 

package com.lee.test.serializable;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.*;
import java.util.List;

/**
 * @author:Lee
 * @date: 2019/10/28
 * @company:
 * @description:
 */
@Data
@AllArgsConstructor
public class Book implements Externalizable {
    
    private String name;

    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();
    }
}

测试方法修改如下:

  public static void main(String[] args) throws IOException, ClassNotFoundException {
        Book book = new Book("红楼梦", "ABC123456789", Arrays.asList("John", "Eric"));
        //book.setName("红楼梦");
        //book.setIsbn("ABC123456789");
        //book.setAuthors(Arrays.asList("John", "Eric"));
        System.out.println(book);

        String path = "E:\\book.temp";
        serialize(book, path);

        Book book1 = (Book) deserialize(path);
        System.out.println(book1);
    }

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

Book(name=红楼梦, isbn=ABC123456789, authors=[John, Eric])
Exception in thread "main" java.io.InvalidClassException: com.lee.test.serializable.Book; no valid constructor
	at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169)
	at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2043)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at com.lee.test.serializable.SerializableTest1.deserialize(SerializableTest1.java:26)
	at com.lee.test.serializable.SerializableTest1.main(SerializableTest1.java:41)

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

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

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

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

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

 本文章采用https://my.oschina.net/wangmengjun/blog/1588096中的部分模板及部分文字。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术武器库

一句真诚的谢谢,胜过千言万语

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值