1、什么是序列化?
答:
序列化我的理解是我们看到的数据转化成二进制bytes流向磁盘,网络。并经过磁盘、网络之后另外个时间点/另外一个操作系统进行反序列化,将二进制的bytes流翻译成我们能理解的数据格式;比如:json格式。
上面是大白话的回答:
下面是官方回答:
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化不仅仅局限于语言,而是网络、操作系统方面的层次。
也可以从生命周期的角度理解:
JVM里面的数据都是随着JVM消失而消失的,如果我们需要数据比JVM的生命周期长,我们就可以为它实现一个序列化接口,让它可以保存到磁盘,或者发送到网络外面。
2、Java的序列化是什么样子?
答:
从第一问可以得知:序列化是对对象流的读写操作。大白话就是怎么将一个对象变成一串流。 Java为我们封装好了使用方法,我们只需要implements Serializable
即可。
如果需要更多的定制化内容,就需要重写 writeObject()
方法。而需要定制化反序列化需要重写 readObject()
方法。
对象流在java中就是ObjectOutputStream,下面是一个ObjectOutputStream的使用案例:
@Test
public void test2() {
String suffix = String.valueOf(Math.random());
String rootPath = Thread.currentThread().getContextClassLoader().getResource("").toString().substring(6);
List<File> files = new ArrayList<>(100);
try {
for (int i = 8; i < 108; i++) {
String fileName = Integer.toBinaryString(Objects.hashCode(i));
List<Comment> comments = new ArrayList<>(20);
for (int j = 0; j < 20; j++) {
Comment comment = new Comment();
comment.setId(j);
comment.setContent("Love " + fileName);
comments.add(comment);
}
User user = new User();
user.setId(i);
user.setName(fileName);
user.setPassword(Integer.toBinaryString(fileName.hashCode()));
user.setComments(comments);
File file = File.createTempFile(fileName, suffix, new File(rootPath));
try (FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(file, true);
) {
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
ObjectInputStream ois = new ObjectInputStream(fis);
User object = (User) ois.readObject();
System.out.println(object);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
files.forEach(File::deleteOnExit);
}
}
private static class Comment implements Serializable {
private int id;
private String content;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Comment{" +
"id=" + id +
", content='" + content + '\'' +
'}';
}
}
private static class User implements Serializable {
private int id;
private String name;
private String password;
private List<Comment> comments;
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 getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> comments) {
this.comments = comments;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", comments=" + comments +
'}';
}
}
对象流的使用要点是:使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
3、什么是反序列化?Java的反序列化
答:
反序列化就是序列化的反过程,指的是从磁盘/网络的流(bytes)回到内存,怎么恢复我们认识的样子。
Java的反序列化也是需要实现Serializable的,然后使用输入流,将bytes放入内存。案例可以看第2问的代码。
定制化反序列化需要重写 readObject()
方法。
4、序列化/放序列化不会保存/解析静态变量?
答:
是的,序列化会忽略静态变量,这是jdk考虑一般静态变量是共同拥有的不是这个类独有的,所以不会将其序列化,不过使用者可以重写writeObject()
,将静态变量写入磁盘/流向网络。
5、序列化 ID (serialVersionUID)为什么有的地方有,有的地方没有?
答:
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)。
serialVersionUID其实是一个对象的身份证,可能在负载的网络交互过程中,存在着两个很类似的对象(类名,字段大多相等),但是两个是不同的对象。这个时候程序就需要使用serialVersionUID去做区分。
如果改动serialVersionUID,就会出现反序列化异常的情况。
比如:
public class SerializableDemo1 {
public static void main(String[] args) {
//Initializes The Object
User1 user = new User1();
user.setName("hollis");
//Write Obj to File
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile")); oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(oos);
}
}
}
class User1 implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
修改一下User1 类,把 serialVersionUID 的值改为 2L。
class User1 implements Serializable {
private static final long serialVersionUID = 2L;
然后执行以下代码,把文件中的对象反序列化出来:
public class SerializableDemo2 {
public static void main(String[] args) {
//Read Obj from File
File file = new File("tempFile");
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(file));
User1 newUser = (User1) ois.readObject();
System.out.println(newUser);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(ois);
try {
FileUtils.forceDelete(file);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
最后运行出现如下异常:
java.io.InvalidClassException: serializablation.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
这是因为,在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。
上面说明了serialVersionUID的作用,但是为什么有的地方有,有的地方没有呢?
我们试验一段代码:
去掉上面代码的序列化id
class User1 implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
运行保存
加上一个字段,运行加载
class User1 implements Serializable {
//private static final long serialVersionUID = 2L;
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
运行的结果为:
java.io.InvalidClassException: self.donghao.experiments.serializablation.User1; local class incompatible: stream classdesc serialVersionUID = -3952380580598475573, local class serialVersionUID = 1397259157723020558
从这里可以看出,系统自己添加了一个 serialVersionUID。
强烈建议序列化的对象一定要加上serialVersionUID
可以使用IDEA根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段,比如:
private static final long serialVersionUID = xxxxL;
6、序列化的继承关系
答:
序列化如果需要父类也序列化,就需要父类也implements Serializable
。
7、使用了序列化框架,还需要序列化吗?
答:
看情况,比如我们使用了fastjson框架,发现序列化/反序列化一切正常,数据也是随意乱改程序也没异常,其实这只是http协议帮我们把数据可视化了。在rmi/dubbo等rpc网络调用中,还是需要序列化的,序列化id。如果是http协议,确实是不需要我们自己implements Serializable
,json框架也会帮我们构建对象的流。所以使用者需要根据自己的情况去使用序列化。
8、Java中transient 关键字可以阻止该变量被序列化到文件中
答:
字段使用transient声明了之后,就不会序列化到文件/网络之中。
在变量声明前加上 transient 关键字,可以阻止该变量被序列化到文件中,在被反序列化后, transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。