序列化
概念(Java 提供了一种对象序列化的机制。)
用一个字节序列可以表示一个对象,该字节序列包含该 对象的数据 、 对象的类型和 对象中存储的属性 等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。 对象的数据 、 对象的类型 和 对象中存储的数据 信息,都可以用来在内存中创建对象。
简单说序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]
数组。
为什么要把Java对象序列化呢?
因为序列化后可以把byte[]
保存到文件中,或者把byte[]
通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。有序列化,就有反序列化,即把一个二进制内容(也就是byte[]
数组)变回Java对象。有了反序列化,保存到文件中的byte[]
数组又可以“变回”Java对象,或者从网络上读取byte[]
并把它“变回”Java对象。
Serializable
序列化需要类实现java.io.Serializable
接口,也就是说不实现此接口的类将不能序列化或反序列化。Serializable
类的所有子类都是可序列化的。序列化接口没有方法或字段,仅用于标识可串行化
的语义。我们把这样的空接口称为“标记接口”(Marker Interface)实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。
public interface Serializable { }
ObjectOutputStream
java.io.ObjectOutputStream
类,将Java对象的原始数据类型写出到流,实现对象的持久存储。
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants
构造方法
ObjectOutputStream
提供了一个public
的构造方法:
//创建一个指定OutputStream的ObjectOutputStream public ObjectOutputStream(OutputStream out);
序列化操作
序列化为byte[]
package com.itlaobing.demo; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.Arrays; public class ObjectOutputStreamTest { public static void main(String[] args) throws IOException { try(ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos)){ //写内容 // 写入int: oos.writeInt(12345); // 写入String: oos.writeUTF("云创动力"); // 写入Object: oos.writeObject(Double.valueOf(123.456)); System.out.println(Arrays.toString(bos.toByteArray())); System.out.println(bos.toString()); } catch (IOException e) { e.printStackTrace(); } } }
ObjectOutputStream
既可以写入基本类型,如int
,boolean
,也可以写入String
(以UTF-8编码),还可以写入实现了Serializable
接口的Object
。
因为写入Object
时需要大量的类型信息,所以写入的内容很大
对象序列化
创建一个Student
类,并实现Serializable
接口
package com.itlaobing.demo.seri; import java.io.Serializable; public class Student implements Serializable{ private String name; private double score; private String gender; public Student(String name, double score, String gender) { super(); this.name = name; this.score = score; this.gender = gender; } //省略getter/setter方法 }
例子:
package com.itlaobing.demo.seri; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class ObjectOutputStreamTest2 { public static void main(String[] args) throws FileNotFoundException, IOException { try(FileOutputStream fos = new FileOutputStream("d:\\temp\\student.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);){ Student stu = new Student("尼古拉斯", 99.99, "男"); oos.writeObject(stu); } } }
思考:如果我Student
类中的某个属性不想被序列化怎么办呢?
transient
关键字,transient
瞬态,修饰的成员,不会被序列化。
将Student
类修改为:
package com.itlaobing.demo.seri; import java.io.Serializable; public class Student implements Serializable{ private String name; private double score; private transient String gender; public Student(String name, double score, String gender) { super(); this.name = name; this.score = score; this.gender = gender; } //省略getter/setter @Override public String toString() { return "Student [name=" + name + ", score=" + score + ", gender=" + gender + "]"; } }
序列化:
package com.itlaobing.demo.seri; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class ObjectOutputStreamTest3 { public static void main(String[] args) throws IOException { Student stu = new Student("尼古拉斯", 99.99, "男"); System.out.println("序列化前: " + stu); try(FileOutputStream fos = new FileOutputStream("d:\\temp\\student2.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);){ oos.writeObject(stu); } } }
ObjectInputStream
和java.io.ObjectOutputStream
相反,java.io.ObjectInputStream
负责从一个字节流读取Java对象。将之前使用ObjectOutputStream
序列化的原始数据恢复为对象.
public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
构造方法
ObjectInputStream
有一个public
修饰的构造方法
//创建一个指定InputStream的ObjectInputStream public ObjectInputStream(InputStream in);
反序列化
如果能找到一个对象的class
文件,我们可以进行反序列化操作,调用 ObjectInputStream
读取对象的方法。
将D:\\temp\\student.txt
反序列化成对象。
import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class ObjectInputStreamTest { public static void main(String[] args) throws ClassNotFoundException, IOException { try(FileInputStream in = new FileInputStream("D:\\temp\\student2.txt"); ObjectInputStream ois = new ObjectInputStream(in)){ Object obj = ois.readObject(); System.out.println(obj.getClass());//class com.itlaobing.demo.Student if(obj instanceof Student) { Student stu = (Student) obj; System.out.println(stu); } } } }
反序列化过程中,将字节序列转换成了对象,这个对象的创建没有经过构造方法。这个对象中的域(属性)的值是固定的,如果是transient
声明的域(属性)值是其类型的默认值。
对于JVM
可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个ClassNotFoundException
异常。
另外,当JVM
反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个 InvalidClassException
异常。
为了避免这种class
定义变动导致的不兼容,Java的序列化允许class定义一个特殊的serialVersionUID
静态常量,用于标识Java类的序列化“版本”,通常可以由IDE自动生成。如果增加或修改了字段,可以改变serialVersionUID
的值,这样就能自动阻止不匹配的class版本:
// 加入序列版本号 private static final long serialVersionUID = 1L;
这个时候序列化完成后,修改类后,可以反序列化。
要特别注意反序列化的几个重要特点:
反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。
安全性
因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。
实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON
这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。
json
json 对象:[包惠对象内容,对象的每个内容使用key : vlaue形式表示,多个属性使用,分割。对象的“key~是字符串类型的,~value~可以是字符串、数值、布尔、json对象、json数组
json数组:[]“包裹数组内容,数组的每个内容使用一,分割,数组的元素可以是字符串、数值、布尔、json对象、json数组.
java 中常见的解析 json 的第三方库(jar包):
-
GSON
-
Fastjson
-
Jackson2
-
hutool-json
注意: 13
-
此时,我们深克隆实现就有三种了:
-
自定义 clone 实现 [麻烦死了]
-
-
将引用数据类型的字段也克隆一份
-
-
使用序列化实现+I
-
-
将对象序列化为字节流,再反序列化为对象
-
-
通过JSON,使用第三方库实现 [推荐]
-
-
使用第一方库实现深克隆,比如: GSON、Fast]son、Jackson 2、hutool-json
-