scala的serializable


1 什么是序列化

把原本在内存中的对象状态 变成可存储或传输的过程称之为序列化。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

实现序列化的两个原因:

  1. 将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;
  2. 按值将对象从一个应用程序域发送至另一个应用程序域。实现serializabel接口的作用是就是可以把对象存到字节流,然后可以恢复,所以你想如果你的对象没实现序列化怎么才能进行持久化和网络传输呢,要持久化和网络传输就得转为字节流,所以在分布式应用中及设计数据持久化的场景中,就得实现序列化。

2 Scala 和 java 序列化

java serializable

最好显示地指定 UID

public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String userId;
    private String userName;
}

scala serializable

scala 序列化需要扩展Serializable 特质, 并加@Serializable 注解

@SerialVersionUID(100L)
class Stock(var symbol: String, var price: BigDecimal) extends Serializable {
    // code here ...
}

import java.io._

// create a serializable Stock class
@SerialVersionUID(123L)
class Stock(var symbol: String, var price: BigDecimal) extends Serializable {
    override def toString = f"$symbol%s is ${price.toDouble}%.2f"
}

object SerializationDemo extends App {

    // (1) create a Stock instance
    val nflx = new Stock("NFLX", BigDecimal(85.00))

    // (2) write the instance out to a file
    val oos = new ObjectOutputStream(new FileOutputStream("/tmp/nflx"))
    oos.writeObject(nflx)
    oos.close

    // (3) read the object back in
    val ois = new ObjectInputStream(new FileInputStream("/tmp/nflx"))
    val stock = ois.readObject.asInstanceOf[Stock]
    ois.close

    // (4) print the object that was read back in
    println(stock)

}

// NFLX is 85.00

serializableUID的理解:

serialVersionUID是用来辅助对象的序列化与反序列化的,原则上序列化后的数据当中的serialVersionUID与当前类当中的serialVersionUID一致,那么该对象才能被反序列化成功。这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟原对象的serialVersionUID是否一致,如果一直则反序列化成功,否则就说明当前类跟序列化后的类发生了变化。

3 序列化读写

//序列化过程
public void toSerial() {
    try {
        User user = new User("id", "user");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("user.txt"));
        objectOutputStream.writeObject(user);
        objectOutputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

//反序列化过程
public void fromSerial(){
    try {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("user.txt"));
        User user = (User) objectInputStream.readObject();
        objectInputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

4 自定义序列化

默认序列化会序列化所有的成员变量,或是当对象的实例变量引用了其他对象,那么序列化该对象的过程中也会把引用对象进行序列化,那么问题来了,1.我们不想序列化一些信息,比如关键的隐私信息,2. 当实例变量引用了大量的其他对象,那么序列化的成本也是很高的。 下面就简单说三种 自定义序列化方式。

4.1 transient关键字

当某个字段被声明为 transient时,那么机制就会忽略它,对它不进行序列化

4.2 writeObject()方法与readObject()方法

解释下面代码

我们将 age字段声明为了 transient,那么机制不会去序列化它,但是我们在类中多加了两个方法writeObject()方法与readObject()方法

我们在 writeObject中调用 ObjectOutputStream的ObjectOutputStream方法写进行序列化的写操作,因为 age 字段被声明了 trasient,此时会忽略它,而紧跟其后,我们又调用了 wirteInt方法,显示地将 age字段进行了序列化。在readObject中同理。

public   class  Person  implements  Serializable {

     private  String name  =   null ;
     private  Integer age  =   null ;
     private  Gender gender  =   null ;

     public  Person() {
        System.out.println( " none-arg constructor " );
    }

     public  Person(String name, Integer age, Gender gender) {
        System.out.println( " arg constructor " );
         this .name  =  name;
         this .age  =  age;
         this .gender  =  gender;
    }
}

// writeObject()方法与readObject()方法
public   class  Person  implements  Serializable { 
     transient   private  Integer age  =   null ;
     private   void  writeObject(ObjectOutputStream out)  throws  IOException {
        out.defaultWriteObject();
        out.writeInt(age);
    }
     private   void  readObject(ObjectInputStream in)  throws  IOException, ClassNotFoundException {
        in.defaultReadObject();
        age  =  in.readInt();
    }
}

4.3 Externalizable接口

Externalizable是继承自Serializable, 实现 Externalizable重写其两个方法,writeExternal 、readExternal

下面这段代码中,对name与age字段进行序列化,忽略掉gender字段

public   class  Person  implements  Externalizable {

     private  String name  =   null ;
     transient   private  Integer age  =   null ;
     private  Gender gender  =   null ;

     public  Person() {
        System.out.println( " none-arg constructor " );
    }

     public  Person(String name, Integer age, Gender gender) {
        System.out.println( " arg constructor " );
         this .name  =  name;
         this .age  =  age;
         this .gender  =  gender;
    }

    @Override
     public   void  writeExternal(ObjectOutput out)  throws  IOException {
        out.writeObject(name);
        out.writeInt(age);
    }

    @Override
     public   void  readExternal(ObjectInput in)  throws  IOException, ClassNotFoundException {
        name  =  (String) in.readObject();
        age  =  in.readInt();
    }

    /*
arg constructor
none - arg constructor
[John,  31 ,  null ]
*/

若使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。

5 readSolve()方法

当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能会略有不同。

public  class Person implements Serializable {

   private  static  class InstanceHolder {
     private  static  final Person instatnce =  new Person( " John " , 31 , Gender.MALE);
  }

   public  static Person getInstance() {
     return InstanceHolder.instatnce;
  }

   private String name =  null ;
   private Integer age =  null ;
   private Gender gender =  null ;

   private Person() {
    System.out.println( " none-arg constructor " );
  }

   private Person(String name, Integer age, Gender gender) {
    System.out.println( " arg constructor " );
     this .name = name;
     this .age = age;
     this .gender = gender;
  }
}


public  class SimpleSerial {

   public  static  void main(String[] args) throws Exception {
    File file =  new File( " person.out " );
    ObjectOutputStream oout =  new ObjectOutputStream( new FileOutputStream(file));
    oout.writeObject(Person.getInstance()); // 保存单例对象
    oout.close();

       ObjectInputStream oin =  new ObjectInputStream( new FileInputStream(file));
       Object newPerson = oin.readObject();  oin.close();
       System.out.println(newPerson);

       System.out.println(Person.getInstance() == newPerson); // 将获取的对象与Person类中的单例对象进行相等性比较
  }
}

/*
arg constructor
[John,  31 , MALE]
false
*/

从文件person.out中获取的Person对象与Person类中的单例对象并不相等。为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法

public  class Person implements Serializable {

   private  static  class InstanceHolder {
     private  static  final Person instatnce =  new Person( " John " , 31 , Gender.MALE);
  }

   public  static Person getInstance() {
     return InstanceHolder.instatnce;
  }

   private String name =  null ;
   private Integer age =  null ;
   private Gender gender =  null ;

   private Person() {
    System.out.println( " none-arg constructor " );
  }

   private Person(String name, Integer age, Gender gender) {
    System.out.println( " arg constructor " );
     this .name = name;
     this .age = age;
     this .gender = gender;
  }

   private Object readResolve() throws ObjectStreamException {
     return InstanceHolder.instatnce;
  }
}

/*
arg constructor
[John,  31 , MALE]
true
*/

无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用。反序列化的过程实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。

6 总结

  1. 序列化作用的对象是类的实例.对实例进行序列化,就是保存实例当前在内存中的状态.包括实例的每一个属性的值和引用等.
  2. 想要实现序列化必须实现Serializable接口,
  3. 序列化时,只对对象的状态进行保存,而不管对象的方法;
  4. 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
  5. 默认序列化机制中当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
  6. 序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,不能序列化。添加了static、transient关键字后的变量不能序列化。

7 参考

  1. Scala Serializable的使用
  2. 对Java Serializable(序列化)的理解和总结

作者: jdi146
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 jdi146 !
评论
评论
  目录