天天看點

java中Serializable和Externalizable接口淺析

摘要: 本文主要要看看JDK中使用Serializable和Externalizable接口來完成Java對象序列化,并給出部分屬性序列化的幾種方式,最終做出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方法,如果有,其會委托該方法進行對象序列化。

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

java中Serializable和Externalizable接口淺析
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類的内容,如下:

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接口的實作方式一定要有預設的無參構造函數~

java中Serializable和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)
           
java中Serializable和Externalizable接口淺析
Serializable接口實作,其采用反射機制完成内容恢複,沒有一定要有無參構造函數的限制~

【4】采用Externalizable無需産生序列化ID(serialVersionUID)~而Serializable接口則需要~

【5】相比較Serializable, Externalizable序列化、反序列更加快速,占用相比較小的記憶體

在項目中,大部分的類還是推薦使用Serializable, 有些類可以使用Externalizable接口,如:

  • 完全控制序列的流程和邏輯
  • 需要大量的序列化和反序列化操作,而你比較關注資源和性能~ 當然,這種情況下,我們一般還會考慮第三方序列化/反序列化工具,如protobuf等進行序列化和反序列化操作~