Java中的Serializable和Externalizable有什么区别?


Answers:


267

要添加其他答案,请通过实现java.io.Serializable,为类对象提供“自动”序列化功能。无需实现任何其他逻辑,它就可以工作。Java运行时将使用反射来弄清楚如何对您的对象进行封送。

在Java的早期版本中,反射非常慢,因此序列化大型对象图(例如,在客户端-服务器RMI应用程序中)存在一些性能问题。为了处理这种情况,该java.io.Externalizable界面提供,这就好比java.io.Serializable但进行打包和解包的功能自定义编写机制(需要实现readExternalwriteExternal在类上方法)。这为您提供了解决反射性能瓶颈的方法。

在Java的最新版本(当然,从1.3开始)中,反射的性能比以前好得多,因此,这不再是问题。我怀疑您很难从Externalizable现代JVM中获得有意义的收益。

此外,内置的Java序列化机制并不是唯一的机制,您可以获取第三方替代品,例如JBoss序列化,它要快得多,并且是默认替代品。

一个很大的缺点Externalizable是您必须自己维护此逻辑-如果您在类中添加,删除或更改了一个字段,则必须更改您的writeExternal/ readExternal方法以解决该问题。

总而言之,Externalizable是Java 1.1的遗物。真的不再需要它了。


61
不按照这些基准测试:[ code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking],手动序列化(使用externizable)比使用Java的默认序列化要快得多。如果速度对您的工作很重要,则一定要编写自己的串行器。
volni 2010年


3
github.com/eishay/jvm-serializers/wiki中的“ java-manual” 使用Externalizable,这意味着将使用ObjectOutputStream。有关代码的链接,请参见github.com/eishay/jvm-serializers/wiki/ToolBehavior。相反,它是使用DataOutputStream的手写代码,因此不会受到使ObjectOutputStream变慢的影响(例如跟踪对象实例和支持对象循环)。
Esko Luontola'5

7
如果类永不改变并且您永远不必读取旧数据的持久版本,则必须自己维护逻辑只是一个弊端。如果您希望自由地更改类而不必编写地狱的代码来反序列化其旧版本,则会有很大Externalizable帮助。
Tim Boudreau

2
我只需要编写一个自定义集合,就不得不说这更Externalizable适合我,因为我不想输出带有空空格或占位符对象的数组,以及带有可以处理继承的显式接口的输出,这意味着我的同步子对象-class可以轻松地在调用周围添加锁定writeExternal()。是的,Externalizable仍然非常重要,当然对于大型或复杂对象。
Haravikk 2014年

37

序列化提供了默认功能来存储和以后重新创建对象。它使用冗长的格式来定义要存储的对象的整个图,例如,假设您有一个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 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

我们可以观察到,因为我们没有设置passWord的值,所以它为null。

通过将密码字段声明为瞬态也可以实现相同的目的。

private transient String passWord;

希望能帮助到你。如果我有任何错误,我深表歉意。谢谢。


22

Serializable和之间的主要区别Externalizable

  1. 标记界面Serializable是没有任何方法的标记界面。Externalizable接口包含两种方法:writeExternal()readExternal()
  2. 序列化过程:将为实现Serializable接口的类启动默认的序列化过程。程序员定义的序列化过程将为实现Externalizable接口的类启动。
  3. 维护不兼容的更改可能会中断序列化。
  4. 向后兼容和控制:如果必须支持多个版本,则可以完全控制Externalizable界面。您可以支持对象的不同版本。如果实现Externalizable,则序列化super类是您的责任
  5. public No-arg构造函数Serializable使用反射来构造对象,不需要arg构造函数。但是Externalizable需要公共的无参数构造函数。

请参阅博客通过Hitesh Garg更多的细节。


1
(3)不正确。您可以在不破坏现有对象的反序列化的情况下对类进行大量更改,并且添加可序列化成员当然是其中之一。删除它们是另一回事。请参阅Java对象序列化规范的“ 对象版本控制”一章
洛恩侯爵

1
重新措词。
Ravindra babu

感谢您分享序列化规范的链接。
JL_SO

21

序列化使用某些默认行为来存储并稍后重新创建对象。您可以指定以什么顺序或如何处理引用和复杂的数据结构,但是最终归结为对每个原始数据字段使用默认行为。

在极少数情况下,您确实希望以完全不同的方式存储和重建对象,而无需为数据字段使用默认的序列化机制,因此可以使用外部化。例如,假设您有自己的独特编码和压缩方案。


5
我们将Externalizable用于“选择的ID”的大集合-与默认序列化相比,外部化的效率要高得多,因为计数或原始整数的数组差不多。那是一个非常简单的用例,没有“特殊的”或“独特的”。
Thomas W

9

对象序列化使用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将完全控制序列化对象。使用瞬态关键字来防止数据成员被序列化。
  • 在这里,可序列化的对象直接从流中重建而无需执行
  • InvalidClassException«在反序列化过程中,如果本地类为serialVersionUID值与相应的发送者的类不同。然后结果是冲突 java.io.InvalidClassException: com.github.objects.User; local class incompatible: stream classdesc serialVersionUID = 5081877, local class serialVersionUID = 50818771
  • 该类的非瞬态和非静态字段的值将被序列化。

可外部化的接口

对于可外部化的对象,容器仅保存对象类的标识;该类必须保存并恢复其内容。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接口有两种方法,一个Externalizable对象必须实现writeExternal和readExternal方法来保存/恢复对象的状态。
  • 程序员必须照顾要序列化的对象。由于程序员要负责序列化,因此,这里的transient关键字将不会限制序列化过程中的任何对象。
  • 重建Externalizable对象时,将使用公共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要序列化的类的实例字段。


示例«实现了可序列化

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

示例«实现了可外部化

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

@看到


7

实际上并未提供Externalizable接口来优化序列化过程的性能!但要提供实现自己的自定义处理的方法,并提供对对象及其超类型的流的格式和内容的完全控制!

这样的示例是实现AMF(ActionScript消息格式)远程处理以通过网络传输本机操作脚本对象的实现。


7

https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serialTOC.html

默认序列化有些冗长,并且假定序列化对象的使用范围最广,因此,默认格式(可序列化)会使用有关序列化对象类的信息来注释结果流。

外部化使对象流的生产者可以完全控制精确的类元数据(如果有的话),而不必超出类的最小标识(例如,其名称)。在某些情况下,例如封闭环境中,对象流的生产者及其使用者(从流中对对象进行验证)是匹配的,并且有关该类的其他元数据毫无用处并降低了性能,这显然是合乎需要的。

另外(如Uri所指出的),外部化还提供对与Java类型相对应的流中数据编码的完全控制。对于(人为)示例,您可能希望将布尔值true记录为“ Y”,将false记录为“ N”。外部化允许您执行此操作。


2

在考虑提高性能的选项时,请不要忘记自定义序列化。你可以让Java的做它好,或者至少不够好,自由,和它做什么不好提供定制的支持。与完全的可外部化支持相比,这通常少得多的代码。


2

Serializable和Externalizable之间存在太多差异,但是当我们比较自定义Serializable(overrided writeObject()&readObject())和Externalizable之间的差异时,我们发现自定义实现与ObjectOutputStream类紧密绑定,就像在Externalizable情况下,我们自己提供ObjectOutput的实现,该实现可以是ObjectOutputStream类,也可以是诸如org.apache.mina.filter.codec.serialization.ObjectSerializationOutputStream之类的其他对象

如果是可外部化的接口

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

我添加了示例代码以更好地解释。请检查外部对象的进/出情况。这些不直接绑定到任何实现。
在哪里,出流/入流与类紧密绑定。我们可以扩展ObjectOutputStream / ObjectInputStream,但是使用起来有点困难。


1
您能详细说明一下吗?在阅读时,我不明白您要说的是什么。另外,如果您可以使用一些段落和示例来设置文本格式,这可能是一个很好的答案。
Shirkam '17

0

基本上,它Serializable是一个标记接口,表示类可以安全地进行序列化,并且JVM确定如何对其进行序列化。Externalizable包含2个方法,readExternalwriteExternalExternalizable允许实现者决定如何序列Serializable化对象,默认情况下序列化对象的方式。


0

一些差异:

  1. 对于序列化,不需要该类的默认构造函数,因为Object是因为JVM在Reflection API的帮助下构造了相同的对象。在没有arg的外部化构造器的情况下,因为控制权在编程人员手中,后来又通过setter将反序列化的数据分配给对象。

  2. 在序列化中,如果用户要跳过某些要序列化的属性,则必须将这些属性标记为临时属性,反之则不需要外部化。

  3. 如果希望任何类都具有向后兼容性支持,则建议使用Externalizable。序列化支持defaultObject持久化,如果对象结构被破坏,则在反序列化时会引起问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.