首先咱们看一下wiki上面对于序列化的解释。html
序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另外一台计算机环境中,能恢复原先状态的过程。依照序列化格式从新获取字节的结果时,能够利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不归纳以前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操做,是反序列化(也称为解编组、deserialization、unmarshalling)。java
以最简单的方式来讲,序列化就是将内存中的对象变成网络或则磁盘中的文件。而反序列化就是将文件变成内存中的对象。(emm,序列化就是将脑海中的“老婆”变成纸片人?反序列化就是将纸片人变成脑海中的“老婆”?当我没说)若是说的代码中具体一点,序列化就是将对象变成字节,而反序列化就是将字节恢复成对象。程序员
固然,你在一个平台进行序列化,在另一个平台也能够进行反序列化。服务器
对象的序列化主要有两种用途:
1. 把对象的字节序列永久地保存到硬盘上,一般存放在一个文件中;(好比说服务器上用户的session对象)
2. 在网络上传送对象的字节序列。(好比说进行网络通讯,消息(能够是文件)确定要变成二进制序列才能在网络上面进行传输)网络
OK,既然咱们已经了解到什么是(反)序列化了,那么多说无益,让咱们来好好的看一看Java是怎么实现的吧。session
对于Java这把轻机枪来讲,既然序列化是一个很重要的部分,那么它确定自身提供了序列化的方案。数据结构
在Java中,只有实现了Serializable和Externalizable接口的类的对象才可以进行序列化。在下面将分别对二者进行介绍。ide
Serializable能够说是最简单的序列化实现方案了。它就是一个接口,里面没有任何的属性和方法。一个类经过implements Serializable标示着这个类是可序列化的。下面将举一个简单的例子:svg
public class People implements Serializable {
private String name;
private int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
People类显而易见,是可序列化的。那么咱们如何来实现可序列化呢?在序列化的过程当中,有两个步骤:函数
public class Main {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
People people = new People("name", 18);
oos.writeObject(people);
}
}
ObjectOutputStream对象中须要一个输出流,这里使用的是文件输出流(也能够是用其余输出流,例如System.out,输出到控制台)。而后咱们经过调用writeObject就能够讲people对象写入到“object.txt
”了。
public class People implements Serializable {
private String name;
private int age;
public People(String name, int age) {
System.out.println("是否调用序列化?");
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
反序列化和序列化同样,也分为2个步骤:
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
People people = (People) ois.readObject();
System.out.println(people);
}
}
下面是程序运行以后的控制台的图片。
能够很明显的看见,反序列化的时候,并无调用People的构造方法。反序列化的对象是由JVM本身生成的对象,而不是经过构造方法生成。
Ok,经过上面咱们简单的学会了序列化的使用,那么,咱们会有一个问题,一个对象在序列化的过程当中,有哪一些属性是但是序列化的,哪一些是不可序列化的呢?
经过查看源代码,咱们能够知道:
对象的类,签名和非transient和非static变量会写入到类中。
看到不少博客都是这样说的:
若是一个可序列化的类的成员不是基本类型,也不是String类型,那这个引用类型也必须是可序列化的;不然,会致使此类不能序列化。
其实这样说不是很准确,由于即便是String类型,里面也实现了Serializable这个接口。
咱们新建一个Man类,可是它并无实现Serializable方法。
public class Man{
private String sex;
public Man(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Man{" +
"sex='" + sex + '\'' +
'}';
}
}
而后在People类中进行引用。
public class People implements Serializable {
private String name;
private int age;
private Man man;
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", man=" + man +
'}';
}
public People(String name, int age, Man man) {
this.name = name;
this.age = age;
this.man = man;
}
}
若是咱们进行序列化,会发生如下错误:
java.io.NotSerializableException: People
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at Main.main(Main.java:41)
由于Man是不可序列化的,也就致使了People类是不可序列化的。
你们看一下下面的这段代码:
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
People people = new People("name", 11);
oos.writeObject(people);
oos.writeObject(people);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
People people1 = (People) ois.readObject();
People people2 = (People) ois.readObject();
System.out.println(people1 == people2);
}
}
大家以为会输出啥?
最后的结果会输出true
。
而后你们再看一段代码,与上面代码不一样的是,People在第二次writeObject的时候,对name进行了从新赋值操做。
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
People people = new People("name", 11);
oos.writeObject(people);
people.setName("hello");
oos.writeObject(people);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
People people1 = (People) ois.readObject();
People people2 = (People) ois.readObject();
System.out.println(people1 == people2);
}
}
结果会输出啥?
结果仍是:true
,同时在people1和people2对象中,name都为“name”,而不是为“hello”。
why??为何会这样?
在默认状况下,对于一个实例的多个引用,为了节省空间,只会写入一次。而当写入屡次时,只会在后面追加几个字节而已(表明某个实例的引用)。
可是咱们若是向在后面追加实例而不是引用那么咱们应该怎么作?使用rest或writeUnshared便可。
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
People people = new People("name", 11);
oos.writeObject(people);
people.setName("hello");
oos.reset();
oos.writeObject(people);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
People people1 = (People) ois.readObject();
People people2 = (People) ois.readObject();
System.out.println(people1);
System.out.println(people2);
System.out.println(people1 == people2);
}
}
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
People people = new People("name", 11);
oos.writeObject(people);
people.setName("hello");
oos.writeUnshared(people);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
People people1 = (People) ois.readObject();
People people2 = (People) ois.readObject();
System.out.println(people1);
System.out.println(people2);
System.out.println(people1 == people2);
}
}
子类和父类有两种状况:
emm,第一种状况不须要考虑,确定不会出错。让咱们来看一看第二种状况会怎么样!!
父类Man类
public class Man {
private String sex;
public Man(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Man{" +
"sex='" + sex + '\'' +
'}';
}
}
子类People类:
public class People extends Man implements Serializable {
private String name;
private int age;
public People(String name, int age, String sex) {
super(sex);
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
"} " + super.toString();
}
}
若是这个时候,咱们对People进行序列化会怎么样呢?会报错!!
Exception in thread "main" java.io.InvalidClassException: People; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2098)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1625)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:465)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:423)
at Main.main(Main.java:38)
如何解决,咱们能够在Man中,添加一个无参构造器便可。这是由于当父类不可序列化的时候,须要调用默认无参构造器初始化属性的值。
咱们会有一个疑问,序列化能够将对象保存在磁盘或者网络中,but,咱们如何可以保证这个序列化的文件的不会被被人查看到里面的内容。假如咱们在进行序列化的时候就像这些属性进行加密不就Ok了吗?(这个仅仅是举一个例子)
可自定义的可序列化有两种状况:
在上面咱们知道transient和static的变量不会进行序列化,所以咱们可使用transient来标记某一个变量来限制它的序列化。
在第二中状况咱们能够经过重写writeObject与readObject方法来选择对属性的操做。(还有writeReplace和readResolve)
在下面的代码中,经过transient来限制name写入,经过writeObject和readObject来对写入的age进行修改。
public class People implements Serializable {
transient private String name;
private int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(age + 1);
}
private void readObject(ObjectInputStream in) throws IOException {
this.age = in.readInt() -1 ;
}
}
至于main函数怎么调用?仍是正常的调用:
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
People people = new People("name", 11);
oos.writeObject(people);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
People people1 = (People) ois.readObject();
}
}
这个,emm,“强制”两个字都懂吧。让咱们来看一看这个接口的源代码:
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
简单点来讲,就是类经过implements这个接口,实现这两个方法来进行序列化的自定义。
public class People implements Externalizable {
private String name;
private int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
// 注意必需要一个默认的构造方法
public People() {
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(this.age+1);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.age = in.readInt() - 1;
}
}
二者之间的差别
方案 | 实现Serializable接口 | 实现Externalizable接口 |
---|---|---|
方式 | 系统默认决定储存信息 | 程序员决定存储哪些信息 |
方法 | 使用简单,implements便可 | 必须实现接口内的两个方法 |
性能 | 性能略差 | 性能略好 |
我相信不少人都看到过serialVersionUID,随便打开一个类(这里是String类),我么能够看到:
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
使用来自JDK 1.0.2 的serialVersionUID用来保持连贯性
这个serialVersionUID的做用很简单,就是表明一个版本。当进行反序列化的时候,若是class的版本号与序列化的时候不一样,则会出现InvalidClassException
异常。
版本好能够只有指定,可是有一个点要值得注意,JVM会根据类的信息自动算出一个版本号,若是你更改了类(好比说添加/修改了属性或者方法),则计算出来的版本号就发生了改变。这样也就表明这你没法反序列化你之前的东西。
什么状况下须要修改serialVersionUID呢?分三种状况。
讲完了讲完了,序列化实际上仍是挺简单。不过须要注意使用的时候遇到的坑。~~