您的位置:首页 > 编程语言 > Java开发

Java序列化(Serializable)与反序列化详解

2018-01-08 19:50 197 查看

什么是序列化?

Java序列化是在JDK 1.1中引入的,是Java内核的重要特性之一。

Java序列化API允许我们将一个对象转换为流,并通过网络发送,或将其存入文件或数据库以便未来使用,

反序列化则是将对象流转换为实际程序中使用的Java对象的过程。

序列化有啥用?

1.暂存大对象

2.Java对象需要持久化的时候

3.需要在网络,例如socket中传输Java对象

    因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收  端读到二进制数据之后反序列化成Java对象

4.深度克隆(复制)

5.跨虚拟机通信

代码怎么写?

1.序列化例子

实体类:Student.java

package com.dylan.serialization;

import java.io.Serializable;

/**
* 学生类
*
* @author xusucheng
* @create 2018-01-08
**/
public class Student implements Serializable {

private int id;
private String name;
private String gender;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public Student(int id, String name, String gender) {
this.id = id;
this.name = name;
this.gender = gender;
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
package com.dylan.serialization;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

/**
* 序列化例子
*
* @author xusucheng
* @create 2018-01-08
**/
public class SerializationDemo {
public static void main(String[] args) throws IOException{
//学生1
Student s1 = new Student(101,"Jack","Male");
//学生2
Student s2 = new Student(102,"Lily","Female");

List<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);

//创建文件流
FileOutputStream fos = new FileOutputStream("D:\\test\\student01.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(list);

os.close();
System.out.println("序列化完成!");
}
}





2.反序列化例子

package com.dylan.serialization;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;

/**
* 反序列化例子
*
* @author xusucheng
* @create 2018-01-08
**/
public class DeserializationDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\test\\student01.ser");
ObjectInputStream is = new ObjectInputStream(fis);
Object obj = null;
List<Student> list = new ArrayList<>();
try {
list = (List<Student>)is.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

is.close();

//遍历list,输出
for (Student student:list){
System.out.println(student.toString());
}
}
}





3.serialVersionUID的作用

可以看到上面序列化例子中的学生实体类只是实现了Serializable接口,假如我现在接到客户需求,要调整Student类,增加学生年龄属性,调整后如下:

public class Student implements Serializable {

private int id;
private String name;
private String gender;
private int age;
...
此时,我在去执行反序列化程序,结果报错了:



啥玩意?就是说Stuent类调整后,其序列化版本ID发生了变化,java又是根据这个ID来判断是不是同一个类。ID不一样则无法反序列化!

事实上如果一个类没有定义serialVersionUID,它会自动计算出来并分配给该类。当发生以下情况,这个ID就会改变:

在类中添加一些新的变量。

将变量从transient转变为非tansient,对于序列化来说,就像是新加入了一个变量而已

将变量从静态的转变为非静态的,对于序列化来说,就也像是新加入了一个变量而已

这个时候就需要我们手动指定这个serialVersionUID了,它告诉jvm,嗨,这个学生类我更新了一下,反序列化的时候记得尽量向后兼容,别报错。通常我们有以下几种方式:

serialVersionUID生成方式:

1)直接写固定的1L;

2)利用JDK自带serialver命令:切到类路径下



3)IDEA生成serialVersionUID

Intellij IDEA 默认没启用这个功能

Preferences->Editor->Inspections->Serialization issues->Serializable class without serialVersionUID 勾上

应用之后就开启了检测功能

在你的class名前(一定是光标移动到类名前):按(mac:option+return)或者( win:Alt+Enter) 就会提示自动创建 serialVersionUID 了。



加上serialVersionUID后的实体类如下:

public class Student implements Serializable {

private static final long serialVersionUID = -2110227637642817458L;

private int id;
private String name;
private String gender;
private int age;
...
再次执行反序列化又恢复正常了:

Student{id=101, name='Jack', gender='Male'}

Student{id=102, name='Lily', gender='Female'}

在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。

那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。


4.static和transient属性无法序列化

transient翻译过来就是短暂的,瞬时的。如果在可序列化的类中某个成员变量加了这个修饰,则说明它只在jvm运行时才保存值,序列化时不会保存的。

为了证明这一点,我们调整一下Student类,加入1个静态变量mess和1个transient变量password。调整后的类如下:

package com.dylan.serialization;

import java.io.Serializable;

/**
* 学生类
*
* @author xusucheng
* @create 2018-01-08
**/
public class Student implements Serializable {

private static final long serialVersionUID = -2110227637642817458L;
private static String mess="null";

private int id;
private String name;
private String gender;
private int age;
private transient String password;

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public Student(int id, String name, String gender) {
this.id = id;
this.name = name;
this.gender = gender;
mess = "something";
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
", password='" + password + '\'' +
", mess='" + mess + '\'' +
'}';
}
}


再次执行序列化,反序列化后:



结论没问题。


5.序列化实战:Socket传输序列化对象例子

package com.dylan.serialization;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author xusucheng
* @create 2018-01-08
**/
public class Server {
public static void main(String[] args) {

//The client is used to handle connections with a client once a connection is
//established.
Socket client = null;

//The following two objects handles our Serialization operations, ObjectOutputStream
//writes an object to the stream. ObjectInputStream reads an object from the stream.
ObjectOutputStream out = null;
ObjectInputStream in = null;

try {
ServerSocket server = new ServerSocket(8888);
client = server.accept();
out = new ObjectOutputStream(client.getOutputStream());
in = new ObjectInputStream(client.getInputStream());

Student student = (Student) in.readObject();
System.out.println(student);

// close resources
out.close();
in.close();
client.close();
server.close();

} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
catch (Exception e) {
e.printStackTrace();
System.exit(-1);
}
}
}

package com.dylan.serialization;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

/**
* @author xusucheng
* @create 2018-01-08
**/
public class Client {
public static void main(String[] args) {
Socket client = null;
ObjectOutputStream out = null;
ObjectInputStream in = null;

try {

client = new Socket("127.0.0.1", 8888);
out = new ObjectOutputStream(client.getOutputStream());
in = new ObjectInputStream(client.getInputStream());

Student student = new Student(991,"Dylan","Male");
out.writeObject(student);
out.flush();

out.close();
in.close();
client.close();

} catch (UnknownHostException e) {
e.printStackTrace();
System.exit(1);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}

}
}


说明:假如Student类没有实现序列化接口,在执行Client程序时会报错:



正如我们前面所讲,网络只能传输二进制数据,Java对象要想传输必须先序列化。

运行效果:



以上只是讲解了一下序列化的常用知识点,还有些深入的部分,以后有空再总结。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: