1 什么是序列化
把原本在内存中的对象状态 变成可存储或传输的过程称之为序列化。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
实现序列化的两个原因:
- 将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;
- 按值将对象从一个应用程序域发送至另一个应用程序域。实现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 总结
- 序列化作用的对象是类的实例.对实例进行序列化,就是保存实例当前在内存中的状态.包括实例的每一个属性的值和引用等.
- 想要实现序列化必须实现Serializable接口,
- 序列化时,只对对象的状态进行保存,而不管对象的方法;
- 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
- 默认序列化机制中当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
- 序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,不能序列化。添加了static、transient关键字后的变量不能序列化。