java - serialization中文 - 序列化




Java中的Serializable和Externalizable有什麼區別? (7)

Java中的SerializableExternalizable什麼區別?


Externalizable接口實際上並未提供用於優化序列化過程的性能! 而是提供實現自己的自定義處理的方法,並提供對對象及其超類型的流的格式和內容的完全控制!

這方面的例子是AMF(動作腳本消息格式)遠程處理的實現,通過網絡傳輸原生動作腳本對象。


Serializable和Externalizable之間存在如此之多的差異,但是當我們比較自定義Serializable(重寫writeObject()和readObject())和Externalizable之間的區別時,我們發現自定義實現與ObjectOutputStream類緊密結合,在Externalizable case中,我們自己提供可能是ObjectOutputStream類的ObjectOutput的實現,或者可以是其他類似org.apache.mina.filter.codec.serialization.ObjectSerializationOutputStream

在Externalizable接口的情況下

@Override
public void writeExternal(ObjectOutput out) throws IOException {
    out.writeUTF(key);
    out.writeUTF(value);
    out.writeObject(emp);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    this.key = in.readUTF();
    this.value = in.readUTF();
    this.emp = (Employee) in.readObject();
}





**In case of Serializable interface**


        /* 
             We can comment below two method and use default serialization process as well
             Sequence of class attributes in read and write methods MUST BE same.
        // below will not work it will not work . 
        // Exception = java.io.StreamCorruptedException: invalid type code: 00\
              private void writeObject(java.io.ObjectOutput stream) 
              */
            private void writeObject(java.io.ObjectOutputStream Outstream)
                    throws IOException {

                System.out.println("from writeObject()");
                /*     We can define custom validation or business rules inside read/write methods.
 This way our validation methods will be automatically 
    called by JVM, immediately after default serialization 
    and deserialization process 
    happens.
                 checkTestInfo();
                */

                stream.writeUTF(name);
                stream.writeInt(age);
                stream.writeObject(salary);
                stream.writeObject(address);
            }

            private void readObject(java.io.ObjectInputStream Instream)
                    throws IOException, ClassNotFoundException {
                System.out.println("from readObject()");
                name = (String) stream.readUTF();
                age = stream.readInt();
                salary = (BigDecimal) stream.readObject();
                address = (Address) stream.readObject();
                // validateTestInfo();
            }

我已經添加了示例代碼來更好地解釋。 請檢查Externalizable的對象情況。 這些不直接綁定到任何實現。
Outstream / Instream與類緊密相連。 我們可以擴展ObjectOutputStream / ObjectInputStream,但它有點難以使用。


SerializableExternalizable之間的主要區別

  1. 標記接口 :可Serializable是沒有任何方法的標記接口。 Externalizable接口包含兩個方法: writeExternal()readExternal()
  2. 序列化過程 :對於實現Serializable接口的類,默認序列化過程將被踢入。 程序員定義的序列化過程將被踢入實現Externalizable接口的類。
  3. 維護不兼容的更改可能會破壞序列化。
  4. 向後兼容和控制 :如果您必須支持多個版本,則可以通過Externalizable接口完全控制。 你可以支持你的對象的不同版本。 如果你實現了Externalizable ,那麼你有責任序列化super
  5. public No-arg構造函數Serializable使用反射來構造對象,並且不需要任何參數構造函數。 但是Externalizable需要公有無參構造函數。

有關更多詳細信息,請參閱Hitesh Garg blog


對象序列化使用Serializable和Externalizable接口。 一個Java對像只能被序列化。 如果一個類或其任何超類實現了java.io.Serializable接口或其子接口java.io.Externalizable。 大部分的java類都是可序列化的

  • NotSerializableExceptionpackageName.ClassName要在序列化過程中參與一個類對象,該類必須實現Serializable或Externalizable接口。

可串行化接口

對象序列化會生成一個流,其中包含有關正在保存的對象的Java類的信息。 對於可序列化的對象,即使存在該類的實現的不同(但兼容)版本,也會保留足夠的信息以恢復這些對象。 Serializable接口被定義為標識實現可序列化協議的類:

package java.io;

public interface Serializable {};
  • 序列化接口沒有方法或字段,僅用於識別可序列化的語義。 對於一個類的序列化/反序列化,我們可以使用默認的writeObject和readObject方法(或)來覆蓋類中的writeObject和readObject方法。
  • JVM將在序列化對象時擁有完全的控制權。 使用transient關鍵字來防止數據成員被序列化。
  • 這裡可串行化的對象直接從流中重構而不執行
  • InvalidClassException «在反序列化過程中,如果本地類serialVersionUID值與相應的發件人類不同。 那麼結果與java.io.InvalidClassException: com.github.objects.User; local class incompatible: stream classdesc serialVersionUID = 5081877, local class serialVersionUID = 50818771相衝突java.io.InvalidClassException: com.github.objects.User; local class incompatible: stream classdesc serialVersionUID = 5081877, local class serialVersionUID = 50818771 java.io.InvalidClassException: com.github.objects.User; local class incompatible: stream classdesc serialVersionUID = 5081877, local class serialVersionUID = 50818771
  • 該類的非瞬態和非靜態字段的值被序列化。

可外部化的接口

對於Externalizable對象,容器只保存對像類的標識; 該課程必須保存並恢復內容。 Externalizable接口定義如下:

package java.io;

public interface Externalizable extends Serializable
{
    public void writeExternal(ObjectOutput out)
        throws IOException;

    public void readExternal(ObjectInput in)
        throws IOException, java.lang.ClassNotFoundException;
}
  • Externalizable接口有兩種方法,一個可外部化的對象必須實現一個writeExternal和readExternal方法來保存/恢復一個對象的狀態。
  • 程序員必須注意要序列化的對象。 作為一名程序員負責序列化所以,這裡的transient關鍵字不會限制序列化過程中的任何對象。
  • 當重構Externalizable對象時,將使用public no-arg構造函數創建實例,然後調用readExternal方法。 可序列化的對象通過從ObjectInputStream中讀取來恢復。
  • OptionalDataException “這些字段必須與我們寫出它們時的相同順序和類型 。 如果流的類型不匹配,則拋出OptionalDataException。

    @Override public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt( id );
        out.writeUTF( role );
        out.writeObject(address);
    }
    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.id = in.readInt();
        this.address = (Address) in.readObject();
        this.role = in.readUTF();
    }
    
  • 寫入(暴露) ObjectOutput的類的實例字段被序列化。

示例«實現Serializable

class Role {
    String role;
}
class User extends Role implements Serializable {

    private static final long serialVersionUID = 5081877L;
    Integer id;
    Address address;

    public User() {
        System.out.println("Default Constructor get executed.");
    }
    public User( String role ) {
        this.role = role;
        System.out.println("Parametarised Constructor.");
    }
}

class Address implements Serializable {

    private static final long serialVersionUID = 5081877L;
    String country;
}

示例«實現Externalizable

class User extends Role implements Externalizable {

    Integer id;
    Address address;
    // mandatory public no-arg constructor
    public User() {
        System.out.println("Default Constructor get executed.");
    }
    public User( String role ) {
        this.role = role;
        System.out.println("Parametarised Constructor.");
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt( id );
        out.writeUTF( role );
        out.writeObject(address);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.id = in.readInt();
        this.address = (Address) in.readObject();
        this.role = in.readUTF();
    }
}

public class CustomClass_Serialization {
    static String serFilename = "D:/serializable_CustomClass.ser";

    public static void main(String[] args) throws IOException {
        Address add = new Address();
        add.country = "IND";

        User obj = new User("SE");
        obj.id = 7;
        obj.address = add;

        // Serialization
        objects_serialize(obj, serFilename);
        objects_deserialize(obj, serFilename);

        // Externalization
        objects_WriteRead_External(obj, serFilename);
    }

    public static void objects_serialize( User obj, String serFilename ) throws IOException{
        FileOutputStream fos = new FileOutputStream( new File( serFilename ) );
        ObjectOutputStream objectOut = new ObjectOutputStream( fos );

        // java.io.NotSerializableException: com.github.objects.Address
        objectOut.writeObject( obj );
        objectOut.flush();
        objectOut.close();
        fos.close();

        System.out.println("Data Stored in to a file");
    }
    public static void objects_deserialize( User obj, String serFilename ) throws IOException{
        try {
            FileInputStream fis = new FileInputStream( new File( serFilename ) );
            ObjectInputStream ois = new ObjectInputStream( fis );
            Object readObject;
            readObject = ois.readObject();
            String calssName = readObject.getClass().getName();
            System.out.println("Restoring Class Name : "+ calssName); // InvalidClassException

            User user = (User) readObject;
            System.out.format("Obj[Id:%d, Role:%s] \n", user.id, user.role);

            Address add = (Address) user.address;
            System.out.println("Inner Obj : "+ add.country );
            ois.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void objects_WriteRead_External( User obj, String serFilename ) throws IOException {
        FileOutputStream fos = new FileOutputStream(new File( serFilename ));
        ObjectOutputStream objectOut = new ObjectOutputStream( fos );

        obj.writeExternal( objectOut );
        objectOut.flush();

        fos.close();

        System.out.println("Data Stored in to a file");

        try {
            // create a new instance and read the assign the contents from stream.
            User user = new User();

            FileInputStream fis = new FileInputStream(new File( serFilename ));
            ObjectInputStream ois = new ObjectInputStream( fis );

            user.readExternal(ois);

            System.out.format("Obj[Id:%d, Role:%s] \n", user.id, user.role);

            Address add = (Address) user.address;
            System.out.println("Inner Obj : "+ add.country );
            ois.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

@看到


序列化使用某些默認行為來存儲並稍後重新創建對象。 您可以指定按照什麼順序或如何處理引用和復雜的數據結構,但最終歸結為對每個基本數據字段使用默認行為。

在極少數情況下,使用外部化時,您確實想以完全不同的方式存儲和重建對象,並且不使用數據字段的默認序列化機制。 例如,假設你有自己獨特的編碼和壓縮方案。


序列化提供了默認功能來存儲和稍後重新創建對象。 它使用詳細格式來定義要存儲的對象的整個圖形,例如,假設你有一個linkedList,並且你的代碼如下所示,那麼默認的序列化將發現所有鏈接的對象並將序列化。 在默認的序列化中,對象完全由其存儲位構成,不需要構造函數調用。

  ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("/Users/Desktop/files/temp.txt"));
        oos.writeObject(linkedListHead); //writing head of linked list
        oos.close();

但是如果你想限制序列化或者不希望你的對象的一部分被序列化,那麼使用Externalizable。 Externalizable接口擴展Serializable接口並添加兩個方法writeExternal()和readExternal()。 這些在序列化或反序列化時自動調用。 在使用Externalizable時,我們應該記住,默認構造函數應該是public,否則代碼將拋出異常。 請按照下面的代碼:

public class MyExternalizable implements Externalizable
{

private String userName;
private String passWord;
private Integer roll;

public MyExternalizable()
{

}

public MyExternalizable(String userName, String passWord, Integer roll)
{
    this.userName = userName;
    this.passWord = passWord;
    this.roll = roll;
}

@Override
public void writeExternal(ObjectOutput oo) throws IOException 
{
    oo.writeObject(userName);
    oo.writeObject(roll);
}

@Override
public void readExternal(ObjectInput oi) throws IOException, ClassNotFoundException 
{
    userName = (String)oi.readObject();
    roll = (Integer)oi.readObject();
}

public String toString()
{
    StringBuilder b = new StringBuilder();
    b.append("userName: ");
    b.append(userName);
    b.append("  passWord: ");
    b.append(passWord);
    b.append("  roll: ");
    b.append(roll);

    return b.toString();
}
public static void main(String[] args)
{
    try
    {
        MyExternalizable m  = new MyExternalizable("nikki", "student001", 20);
        System.out.println(m.toString());
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/Desktop/files/temp1.txt"));
        oos.writeObject(m);
        oos.close();

        System.out.println("***********************************************************************");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/Desktop/files/temp1.txt"));
        MyExternalizable mm = (MyExternalizable)ois.readObject();
        mm.toString();
        System.out.println(mm.toString());
    } 
    catch (ClassNotFoundException ex) 
    {
        Logger.getLogger(MyExternalizable.class.getName()).log(Level.SEVERE, null, ex);
    }
    catch(IOException ex)
    {
        Logger.getLogger(MyExternalizable.class.getName()).log(Level.SEVERE, null, ex);
    }
}
}

在這裡,如果您評論默認構造函數,那麼代碼將拋出異常:

 java.io.InvalidClassException: javaserialization.MyExternalizable;     
 javaserialization.MyExternalizable; no valid constructor.

我們可以觀察到,因為密碼是敏感信息,所以我不在writeExternal(ObjectOutput oo)方法中序列化它,並且不在readExternal(ObjectInput oi)中設置相同的值。 這就是由Externalizable提供的靈活性。

上述代碼的輸出如下所示:

userName: nikki  passWord: student001  roll: 20
***********************************************************************
userName: nikki  passWord: null  roll: 20

我們可以觀察到,因為我們沒有設置密碼的值,所以它是空的。

將密碼字段聲明為瞬態也可以達到同樣的效果。

private transient String passWord;

希望能幫助到你。 如果我犯了錯誤,我很抱歉。 謝謝。


要添加到其他答案中,通過實現java.io.Serializable ,您可以獲得類的對象的“自動”序列化功能。 不需要實現任何其他邏輯,它只會工作。 Java運行時將使用反射來弄清楚如何編組和解組對象。

在Java的早期版本中,反射非常緩慢,因此序列化大型對像圖(例如,在客戶端 - 服務器RMI應用程序中)有點性能問題。 為了處理這種情況,提供了java.io.Externalizable接口,它與java.io.Serializable類似,但是使用自定義編寫的機制來執行編組和解組函數(您需要在類上實現readExternalwriteExternal方法)。 這為您提供了解決反射性能瓶頸的方法。

在最近的Java版本(當然是1.3)中,反射的性能比以前好得多,所以這個問題就不那麼重要了。 我懷疑你會很難用現代JVM從Externalizable獲得有意義的好處。

另外,內置的Java序列化機制不是唯一的,您可以獲得第三方替換,比如更快的JBoss序列化,並且是默認的替代品。

Externalizable一個很大的缺點是你必須自己維護這個邏輯 - 如果你在類中添加,刪除或者改變一個字段,你必須改變你的writeExternal / readExternal方法來解決它。

總之, Externalizable是Java 1.1天的遺跡。 真的不再需要它了。





serialization