marp: false
Java序列化与反序列化实训
序列化概述
序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。
反序列化(Deserialization)则是将已序列化的对象状态信息恢复为对象的过程。
在分布式系统中,序列化机制允许对象通过网络在不同的Java虚拟机之间传输。
在Hadoop生态系统中,序列化尤为重要,因为MapReduce、YARN等组件需要在不同节点之间传输大量数据,高效的序列化机制能够显著提升性能。
Java序列化基础
要实现序列化,一个类必须实现java.io.Serializable接口。这个接口是一个标记接口,没有定义任何方法。一旦一个类实现了这个接口,Java虚拟机(JVM)就可以将这个类的对象序列化并通过网络传输。
实训目标
- 掌握Java对象序列化机制
- 学习使用Socket进行网络通信
- 实现客户端与服务器之间的对象传输
- 理解transient关键字的作用
示例:Student类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import java.io.Serializable;
public class Student implements Serializable { private static final long serialVersionUID = 1L; private String name; private String sex; private String address; private String phoneNumber; private transient String emotion; public Student(String name, String sex, String address, String phoneNumber, String emotion) { this.name = name; this.sex = sex; this.address = address; this.phoneNumber = phoneNumber; this.emotion = emotion; } @Override public String toString() { return "Student{" + "name='" + name + "'" + ", sex='" + sex + "'" + ", address='" + address + "'" + ", phoneNumber='" + phoneNumber + "'" + ", emotion='" + emotion + "'" + '}'; } public void say(){ System.out.println("高兴死了,我来到了这个世界上~~~\n" + this); }
public void sayAgain(){ System.out.println("我又活过来了~~~\n" + this); } }
|
transient关键字
Java中的transient关键字表示”短暂的”。被transient修饰的成员变量,在对象序列化过程中会被忽略。这意味着:
- transient变量不会被序列化到字节流中
- 反序列化后,transient变量会被初始化为默认值(对象为null,基本类型为0或false)
- 适用于存储临时数据或敏感信息(如密码)
服务器端代码
服务器端负责接收客户端发送的序列化对象,并进行反序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import java.io.IOException; import java.io.ObjectInputStream; import java.net.ServerSocket; import java.net.Socket;
public class ObjectServer { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(80); System.out.println("服务器启动,等待客户端连接..."); Socket socket = serverSocket.accept(); System.out.println("客户端已连接,准备接收对象..."); ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); Student student = (Student) ois.readObject(); student.sayAgain(); ois.close(); socket.close(); serverSocket.close(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
|
服务器端IP地址
1 2 3 4 5 6 7 8 9 10
| 无线局域网适配器 WLAN:
连接特定的 DNS 后缀 . . . . . . . : IPv6 地址 . . . . . . . . . . . . : 240a:42b8:a00:161e:8f54:737a:808d:7a24 临时 IPv6 地址. . . . . . . . . . : 240a:42b8:a00:161e:1c5e:437a:119:1c0e 本地链接 IPv6 地址. . . . . . . . : fe80::b7e2:1e50:ac88:6f32%3 IPv4 地址 . . . . . . . . . . . . : 192.168.43.172 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : fe80::7c1c:47ff:fea1:ae35%3 192.168.43.1
|
客户端代码
客户端负责创建对象,序列化后通过Socket发送到服务器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import java.io.IOException; import java.io.ObjectOutputStream; import java.net.Socket;
public class ObjectClient { public static void main(String[] args) { try { Socket socket = new Socket("192.168.43.172", 80); System.out.println("已连接到服务器,准备发送对象..."); Student zhangsan = new Student("zhangsan", "male", "山东省济南市山东女子学院", "18888888888", "happy"); zhangsan.say(); ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); oos.writeObject(zhangsan); System.out.println("对象已发送到服务器"); oos.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
|
运行步骤
首先编译所有Java文件:
1 2 3 4 5 6 7 8 9 10
| (base) PS C:\Users\qingy\Desktop>javac -encoding UTF-8 .\Student.java (base) PS C:\Users\qingy\Desktop> javac -encoding UTF-8 .\ObjectClient.java (base) PS C:\Users\qingy\Desktop> javac -encoding UTF-8 .\ObjectServer.java
(base) PS C:\Users\qingy\Desktop> java ObjectServer 服务器启动,等待客户端连接... 客户端已连接,准备接收对象... 我又活过来了~~~ Student{name='zhangsan', sex='male', address='山东省济南市山东女子学院', phoneNumber='18888888888', emotion='null'} (base) PS C:\Users\qingy\Desktop>
|
启动服务器:
1 2
| (base) PS C:\Users\qingy\Desktop> java ObjectServer 服务器启动,等待客户端连接...
|
启动客户端:
1 2 3 4 5
| (base) PS C:\Users\qingy\Desktop> java ObjectClient 已连接到服务器,准备发送对象... 高兴死了,我来到了这个世界上~~~ Student{name='zhangsan', sex='male', address='山东省济南市山东女子学院', phoneNumber='18888888888', emotion='happy'} 对象已发送到服务器
|
查看服务器端输出:
1 2 3
| 客户端已连接,准备接收对象... 我又活过来了~~~ Student{name='zhangsan', sex='male', address='山东省济南市山东女子学院', phoneNumber='18888888888', emotion='null'}
|
预期输出
客户端输出:
1 2 3 4
| 已连接到服务器,准备发送对象... 高兴死了,我来到了这个世界上~~~ Student{name='zhangsan', sex='male', address='山东省济南市山东女子学院', phoneNumber='18888888888', emotion='happy'} 对象已发送到服务器
|
服务器输出:
1 2 3 4
| 服务器启动,等待客户端连接... 客户端已连接,准备接收对象... 我又活过来了~~~ Student{name='zhangsan', sex='male', address='山东省济南市山东女子学院', phoneNumber='18888888888', emotion='null'}
|
注意事项
serialVersionUID:为序列化类添加serialVersionUID是一个好习惯,可以确保类的版本兼容性
transient关键字:注意观察服务器端接收到的对象中,emotion字段值为null,这是因为它被transient修饰
资源关闭:记得关闭所有的输入输出流和Socket连接,最好使用try-with-resources语法
异常处理:网络编程中需要处理IOException和ClassNotFoundException等异常
端口占用:如果80端口被占用,可以修改为其他端口号