目录
- 一、序列化
- 二、序列化和反序列化的应用
- 三、序列化和反序列化地实现
- 3.1.JDK类库提供的序列化API
- 3.2.序列化要求
- 3.3.实现java序列化和反序列化的三种方法
- 四、CustomerForm 类序列化和反序列化演示
- 五、Externalizable接口实现序列化与反序列化
- 5.1.Externalizable 的不同点
- 5.2.CustomerForm 实现类 Externalizable
- 5.3.Externalizable 实现序列化和反序列化
- 总结
一、序列化
1.1.Serialization(序列化):
将java对象以一连串的字节保存在磁盘文件中的过程,也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)
1.2.deserialization(反序列化)
将保存在磁盘文件中的java字节码重新转换成java对象称为反序列化
二、序列化和反序列化的应用
两个进程在远程通信时,可以发送多种数据,包括文本、图片、音频、视频等,这些数据都是以二进制序列的形式在网络上传输。
java是面向对象的开发方式,一切都是java对象,想要在网络中传输java对象,可以使用序列化和反序列化去实现,发送发需要将java对象转换为字节序列,然后在网络上传送,接收方收到字符序列后,会通过反序列化将字节序列恢复成java对象。
java序列化的优点:
- 实现了数据的持久化,通过序列化可以把数据持久地保存在硬盘上(磁盘文件)。
- 利用序列化实现远程通信,在网络上传输字节序列
三、序列化和反序列化地实现
3.1.JDK类库提供的序列化API
java.io.ObjectOutputStream
表示对象输出流,其中writeObject(Object obj)方法可以将给定参数的obj对象进行序列化,将转换的一连串的字节序列写到指定的目标输出流中。
java.io.ObjectInputStream
该类表示对象输入流,该类下的readObject(Object obj)方法会从源输入流中读取字节序列,并将它反序列化为一个java对象并返回
3.2.序列化要求
实现序列化的类对象必须实现了Serializable类或Externalizable类才能被序列化,否则会抛出异常
3.3.实现java序列化和反序列化的三种方法
现在要对student类进行序列化和反序列化,遵循以下方法:
3.3.1.方法一
若student类实现了serializable接口,则可以通过objectOutputstream和objectinputstream默认的序列化和反序列化方式,对非transient的实例变量进行序列化和反序列化
3.3.2. 方法二
若student类实现了serializable接口,并且定义了writeObject(objectOutputStream out)和
readObject(objectinputStream in)方法,则可以直接调用student类的两种方法进行序列化和反序列化
3.3.3.方法三
若student类实现了Externalizable接口,则必须实现readExternal(Objectinput in)和writeExternal(Objectoutput out)方法进行序列化和反序列化
3.4.序列化步骤
public static void main(String[] args) {
File filename = new File("E:/Student.txt");
try {
// 第一步,创建一个输出流对象,它可以包装一个输出流对象,如:文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
// 第二步,通过输出流对象的 writeObject()方法写对象,从 java 输出到本地文件 out
out.writeObject("第一次输出:第一次打卡哦");
out.writeObject("第二次输出:第二次打卡");
} catch (IOException e) {
e.printStackTrace();
}
}
3.5.反序列化操作
public static void main(String[] args) {
File filename = new File("E:/Student.txt");
try {
// 第一步:创建文件输入流对象
FileInputStream fileInputStream = new FileInputStream(filename);
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
// 调用readObject()方法,本地文件读取数据,输入到程序当中
System.out.println(inputStream.readObject());
System.out.println(inputStream.readObject());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
为了保证正确读取数据,对象输出流写入对象的顺序与对象输入流读取对象的顺序一致
四、CustomerForm 类序列化和反序列化演示
4.1.先创建一个实现了serializable接口的CustomerForm 类
Serializable (/ˈsɪˌriəˌlaɪzəbl/) 序列化
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* <p>Java class for attachmentForm complex type.
*
*/
@Data
public class CustomerForm implements Serializable {
protected String requestCode;
protected List<Customer> reqData;
/**
* transient 修饰的属性不能序列化 *
*/
private transient String msg;
private static String name = "被static修饰的name";
}
把CustomerForm类的对象序列化到CustomerForm.txt 文件(E:/CustomerForm.txt)中,并对文件进行反序列化获取数据:
public static void main(String[] args) {
FileOutputStream fileOutputStream = null;
ObjectOutputStream out = null;
FileInputStream fileInputStream = null;
ObjectInputStream inputStream = null;
try {
File file = new File("E:/CustomerForm.txt");
if (file.exists()){
System.out.println("文件已存在");
} else {
file.createNewFile();
}
CustomerForm customerForm = new CustomerForm();
customerForm.setRequestCode("-2-3-4-5-6-7-8-9");
// 序列化 第一步,创建一个输出流对象,它可以包装一个输出流对象,如:文件输出流
fileOutputStream = new FileOutputStream(file);
out = new ObjectOutputStream(fileOutputStream);
// 序列化 第二步,通过输出流对象的 writeObject()方法写对象,从 java 输出到本地文件 out
out.writeObject(customerForm);
// 反序列化 第一步:创建文件输入流对象
fileInputStream = new FileInputStream(file);
inputStream = new ObjectInputStream(fileInputStream);
// 反序列化 第二步:调用readObject()方法,本地文件读取数据,输入到程序当中
System.out.println(inputStream.readObject());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
out.close();
fileOutputStream.close();
fileInputStream.close();
inputStream.close();
}
}
序列化之后本地文件访问结果:
反序列化结果:
4.2.transient 关键字
transient ( /ˈtrænʃ(ə)nt/)瞬变的
transient 修饰的属性不能参加序列化
以上程序重新执行:
public static void main(String[] args) {
try {
File file = new File("E:/CustomerForm.txt");
if (file.exists()){
System.out.println("文件已存在");
} else {
file.createNewFile();
}
CustomerForm customerForm = new CustomerForm();
customerForm.setRequestCode("-2-3-4-5-6-7-8-9");
/**
* transient 修饰的属性不能序列化 *
*/
customerForm.setMsg("此属性不能参加序列化");
// 序列化 第一步,创建一个输出流对象,它可以包装一个输出流对象,如:文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
// 序列化 第二步,通过输出流对象的 writeObject()方法写对象,从 java 输出到本地文件 out
out.writeObject(customerForm);
// 反序列化 第一步:创建文件输入流对象
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
// 反序列化 第二步:调用readObject()方法,本地文件读取数据,输入到程序当中
System.out.println(inputStream.readObject());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
执行结果:
从结果看出 被transient关键字修饰的变量 msg 赋值内容没有被序列化,被修改为null,被static修饰的属性不能被实例化
五、Externalizable接口实现序列化与反序列化
Externalizable 可外部化的
Externalizable接口继承Serializable接口,实现Externalizable接口需要实现readExternal()方法和writeExternal()方法,这两个方法是抽象方法,对应的是serializable接口的readObject()方法和writeObject()方法,可以理解为把serializable的两个方法抽象出来。Externalizable没有serializable的限制,static和transient关键字修饰的属性也能进行序列化。
5.1.Externalizable 的不同点
Externalizable没有serializable的限制,transient关键字修饰的属性也能进行序列化
5.2.CustomerForm 实现类 Externalizable
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* <p>Java class for attachmentForm complex type.
*
*/
@Data
public class CustomerForm implements Externalizable{
protected String requestCode;
protected List<Customer> reqData;
private transient String msg;
private static String name = "被static修饰的name";
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(requestCode);
out.writeObject(msg);
out.writeObject(reqData);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
requestCode = (String) in.readObject();
msg = (String) in.readObject();
reqData = (List<Customer>) in.readObject();
}
}
实现 Externalizable 类 后需要重写 writeExternal()、readExternal() 方法
5.3.Externalizable 实现序列化和反序列化
public static void main(String[] args) {
try {
File file = new File("E:/CustomerForm.txt");
if (file.exists()){
System.out.println("文件已存在");
} else {
file.createNewFile();
}
CustomerForm customerForm = new CustomerForm();
customerForm.setRequestCode("-2-3-4-5-6-7-8-9");
customerForm.setMsg("该属性被transient修饰");
// 序列化 第一步,创建一个输出流对象,它可以包装一个输出流对象,如:文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
// 序列化 第二步,通过输出流对象的 writeObject()方法写对象,从 java 输出到本地文件 out
out.writeObject(customerForm);
// 反序列化 第一步:创建文件输入流对象
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
// 反序列化 第二步:调用readObject()方法,本地文件读取数据,输入到程序当中
System.out.println(inputStream.readObject());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
执行结果:
可以看到被transient关键字修饰的变量msg已经参与了实例化,被static修饰的变量不能被实例化
总结
序列化和反序列化可以过这两种方式来实现:
1、Bean对象可以通过Serializable接口实现序列化与反序列化
2、Bean对象可以通过Externalizable接口实现序列化与反序列化
不同点
1、Externalizable接口实现实例化,需要重写 writeExternal()、readExternal() 方法
2、Externalizable接口实现实例化,不受 transient关键字限制
3、Serializable接口实现,transient关键字修饰后,不能参与实例化
4、被static修饰的变量(都)不能被实例化
原因:被static修饰的属性是所有类共享的,如果可以序列化,就会出现下面的情况,当我们序列化某个类的一个对象到某个文件后,这个文件中的对象的那个被static修饰的属性值会固定下来,当另外一个普通的的对象修改了该static属性后,我们再去反序列化那个文件中的对象,就会得到和后面的对象不同的static属性值,这显然违背了static关键字诞生的初衷