Java中可序列化和可外部化的区别是什么?

Java中SerializableExternalizable的区别是什么?

129456 次浏览

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

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

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

默认序列化有点冗长,并且假定序列化对象的使用场景尽可能广泛,因此默认格式(Serializable)用关于序列化对象的类的信息注释结果流。

外部化使对象流的生产者能够完全控制精确的类元数据(如果有的话),而不仅仅是类所需的最小标识(例如它的名称)。这在某些情况下显然是可取的,比如在封闭环境中,对象流的生产者和消费者(从流中具体化对象)是匹配的,关于类的额外元数据没有任何作用,而且会降低性能。

此外(正如Uri指出的那样)外部化还提供了对与Java类型对应的流中数据编码的完全控制。对于(一个人为的)例子,您可能希望将布尔值true记录为“Y”,将false记录为“N”。外部化可以让你做到这一点。

为了补充其他答案,通过实现java.io.Serializable,您可以获得类对象的“自动”序列化功能。不需要实现任何其他逻辑,它就可以工作了。Java运行时将使用反射来确定如何封送和解封送对象。

在早期的Java版本中,反射非常慢,因此序列化大型对象图(例如在客户端-服务器RMI应用程序中)是一个性能问题。为了处理这种情况,提供了java.io.Externalizable接口,它与java.io.Serializable类似,但具有自定义编写的机制来执行编组和反编组函数(你需要在你的类上实现readExternalwriteExternal方法)。这为您提供了绕过反射性能瓶颈的方法。

在Java的最新版本(当然是1.3以后)中,反射的性能比以前好得多,所以这不是一个大问题。我怀疑你在现代JVM中很难从Externalizable中获得有意义的好处。

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

Externalizable的一个很大的缺点是你必须自己维护这个逻辑——如果你在你的类中添加、删除或改变一个字段,你必须改变你的writeExternal/readExternal方法来解释它。

总之,Externalizable是Java 1.1时代的遗迹。真的没有必要了。

在考虑提高性能的选项时,不要忘记自定义序列化。你可以让Java做它擅长的事情,或者至少足够好,免费,并为它做得不好的事情提供自定义支持。这通常比完全的外部化支持少得多。

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

这方面的例子是AMF (ActionScript Message Format)远程处理的实现,通过网络传输本机操作脚本对象。

序列化提供了存储对象和稍后重新创建对象的默认功能。它使用详细格式来定义要存储的对象的整个图,例如,假设你有一个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()和readeexternal()。在序列化或反序列化时自动调用这些函数。在使用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)方法中序列化它,也没有在readexterexternal (ObjectInput oi)中设置相同的值。这就是Externalizable提供的灵活性。

上述代码的输出如下所示:

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

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

通过将密码字段声明为瞬态,也可以实现同样的效果。

private transient String passWord;

希望能有所帮助。如果我犯了错误,我向你道歉。谢谢。

SerializableExternalizable之间的关键区别

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

更多细节请参考博客 by Hitesh Garg

Serializable和Externalizable之间存在很多差异,但是当我们比较自定义Serializable(重写writeObject() &readObject())和Externalizable,然后我们发现自定义实现与ObjectOutputStream类紧密绑定,在Externalizable情况下,我们自己提供了ObjectOutput的实现,它可能是ObjectOutputStream类,也可能是其他类,如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/ stream与类紧密绑定。我们可以扩展ObjectOutputStream/ObjectInputStream,但使用起来有点困难

对象序列化使用Serializable和Externalizable接口。 Java对象只能序列化。如果一个类或它的任何超类实现了java.io.Serializable接口或它的子接口java.io.Externalizable。大多数java类都是可序列化的 . < / p >

  • NotSerializableException: packageName.ClassName«要使类对象参与序列化过程,类必须实现Serializable或Externalizable接口。

enter image description here


Serializable Interface

对象序列化生成一个流,其中包含保存的对象的Java类信息。对于可序列化的对象,即使存在不同的(但兼容的)类实现版本,也会保留足够的信息来恢复这些对象。Serializable接口被定义为识别实现了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 Interface

对于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接口有两个方法,一个Externalizable对象必须实现writeExternal和readeexternal方法来保存/恢复对象的状态。
  • 程序员必须负责序列化哪些对象。因此,这里的瞬态关键字不会限制序列化过程中的任何对象。
  • 当重构Externalizable对象时,使用公共无参数构造函数创建实例,然后调用readeexternal方法。通过从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();
    }
    
  • (exposed)写入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();
}
}
}

@see

基本上,Serializable是一个标记接口,它暗示一个类对于序列化是安全的,并且JVM决定它如何序列化。Externalizable包含两个方法,readExternalwriteExternalExternalizable允许实现者决定对象的序列化方式,而Serializable则以默认方式序列化对象。

一些差异:

  1. 对于序列化,不需要该类的默认构造函数,因为Object,因为JVM在反射API的帮助下构造了相同的构造函数。对于Externalization构造函数,不需要参数,因为控制在编程人员手中,然后通过设置器将反序列化的数据分配给对象。

  2. 在序列化中,如果用户想要跳过某些属性来序列化,那么必须将该属性标记为transient,反之亦然。

  3. 当期望对任何类都提供向后兼容性支持时,建议使用Externalizable。序列化支持defaultObject持久化,如果对象结构被破坏,那么反序列化时将导致问题。