K:java序列化和反序列化

本文介绍了Java序列化和反序列化的概念及其应用场景,详细解释了实现序列化与反序列化的具体步骤,并通过示例代码展示了如何将一个Java对象序列化到文件中以及如何从文件中反序列化。

@转载自:http://blog.youkuaiyun.com/wangloveall/article/details/7992448/

Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨。

 1.Java序列化与反序列化

 Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。

 2.为什么需要序列化与反序列化

 我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。

 当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。

3.如何实现Java序列化与反序列化

1)JDK类库中序列化API

 java.io.ObjectOutputStream:表示对象输出流

它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

java.io.ObjectInputStream:表示对象输入流

它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。

2)实现序列化的要求

只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。

3)实现Java对象序列化与反序列化的方法

假定一个Student类,它的对象需要序列化,可以有如下三种方法:

方法一:若Student类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化

ObjectOutputStream采用默认的序列化方式,对Student对象的非transient的实例变量进行序列化。

ObjcetInputStream采用默认的反序列化方式,对对Student对象的非transient的实例变量进行反序列化。

方法二:若Student类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。

ObjectOutputStream调用Student对象的writeObject(ObjectOutputStream out)的方法进行序列化。

ObjectInputStream会调用Student对象的readObject(ObjectInputStream in)的方法进行反序列化。

方法三:若Student类实现了Externalnalizable接口,且Student类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。

ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化。

ObjectInputStream会调用Student对象的readExternal(ObjectInput in)的方法进行反序列化。

4)JDK类库中序列化的步骤

步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:

ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\\objectfile.obj”));

步骤二:通过对象输出流的writeObject()方法写对象:

out.writeObject(“Hello”);

out.writeObject(new Date());

5)JDK类库中反序列化的步骤

步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:

ObjectInputStream in = new ObjectInputStream(new fileInputStream(“D:\\objectfile.obj”));

步骤二:通过对象输出流的readObject()方法读取对象:

String obj1 = (String)in.readObject();

Date obj2 = (Date)in.readObject();

说明:为了正确读取数据,完成反序列化,必须保证向对象输出流写对象的顺序与从对象输入流中读对象的顺序一致。

为了更好地理解Java序列化与反序列化,选择方法一编码实现。

Student类定义如下:


package com.jieke.io;

import java.io.Serializable; 
    /**
     *Title:学生类
     *Description:实现序列化接口的学生类
     *Copyright: copyright(c) 2012
     *Filename: Student.java
     *@author Wang Luqing
     *@version 1.0
     */  
    public class Student implements Serializable  
    {  
     private String name;  
     private char sex;  
     private int year;  
     private double gpa;  
      
     public Student()  
     {  
      
     }  
     public Student(String name,char sex,int year,double gpa)  
     {  
      this.name = name;  
      this.sex = sex;  
      this.year = year;  
      this.gpa = gpa;  
     }  
      
     public void setName(String name)  
     {  
      this.name = name;  
     }  
      
     public void setSex(char sex)  
     {  
      this.sex = sex;  
     }  
      
     public void setYear(int year)  
     {  
      this.year = year;  
     }  
      
     public void setGpa(double gpa)  
     {  
      this.gpa = gpa;  
     }  
      
     public String getName()  
     {  
      return this.name;  
     }  
       
     public char getSex()  
     {  
      return this.sex;  
     }  
      
     public int getYear()  
     {  
      return this.year;  
     }  
      
     public double getGpa()  
     {  
      return this.gpa;  
     }  
    } 
把Student类的对象序列化到文件O:\\Java\\com\\jieke\\io\\student.txt,并从该文件中反序列化,向console显示结果。代码如下:

import java.io.*;  

/**
 *Title:应用学生类
 *Description:实现学生类实例的序列化与反序列化
 *Copyright: copyright(c) 2012
 *Filename: UseStudent.java
 *@author Wang Luqing
 *@version 1.0
 */  
 
public class UseStudent  
{  
 public static void main(String[] args)  
 {  
  Student st = new Student("Tom",'M',20,3.6);  
  File file = new File("O:\\Java\\com\\jieke\\io\\student.txt");  
  try  
  {  
   file.createNewFile();  
  }  
  catch(IOException e)  
  {  
   e.printStackTrace();  
  }  
  try  
  {  
   //Student对象序列化过程  
   FileOutputStream fos = new FileOutputStream(file);  
   ObjectOutputStream oos = new ObjectOutputStream(fos);  
   oos.writeObject(st);  
   oos.flush();  
   oos.close();  
   fos.close();  
 
   //Student对象反序列化过程  
   FileInputStream fis = new FileInputStream(file);  
   ObjectInputStream ois = new ObjectInputStream(fis);  
   Student st1 = (Student) ois.readObject();  
   System.out.println("name = " + st1.getName());  
   System.out.println("sex = " + st1.getSex());  
   System.out.println("year = " + st1.getYear());  
   System.out.println("gpa = " + st1.getGpa());  
   ois.close();  
   fis.close();  
  }  
  catch(ClassNotFoundException e)  
  {  
   e.printStackTrace();  
  }  
  catch (IOException e)  
  {  
   e.printStackTrace();  
  }               
 }  

结果如下所示:

name = Tom

sex = M

year = 20

gpa = 3.6

总结:

1)Java序列化就是把对象转换成字节序列,而Java反序列化就是把字节序列还原成Java对象。

2)采用Java序列化与反序列化技术,一是可以实现数据的持久化,在MVC模式中很是有用;二是可以对象数据的远程通信。


<think>我们正在讨论序列化反序列化的实现原理。根据引用内容,序列化是将对象转换为字节流的过程,而反序列化则是将字节流恢复为对象的过程。下面我将详细解释其工作机制。 序列化(Serialization)的底层实现原理: 1. 当对一个对象进行序列化时,Java虚拟机会检查该对象是否实现了java.io.Serializable接口。如果没有实现,则抛出NotSerializableException。 2. 序列化过程通过ObjectOutputStream实现。它通过反射机制获取对象的类元数据,包括类名、字段、方法等。 3. 对于每个对象,ObjectOutputStream会先写入一个标识符(TC_OBJECT)类描述符(包括类名、序列化版本ID等)。 4. 然后递归地写入该对象的非静态非瞬态字段的值。对于引用类型字段,会递归地序列化整个对象图(即引用的对象也会被序列化)。 5. 在序列化过程中,如果对象定义了writeObject方法,则调用该方法进行自定义序列化(引用[5]中提到)。 反序列化(Deserialization)的底层实现原理: 1. 反序列化通过ObjectInputStream实现。它从字节流中读取类描述符,并根据描述符创建对应的类(如果本地存在该类)。 2. 然后,ObjectInputStream会创建一个新的对象实例(不调用构造函数),并通过反射将字节流中的字段值填充到新对象的对应字段中。 3. 对于引用类型的字段,反序列化过程会递归地反序列化整个对象图,恢复原始对象之间的引用关系。 4. 如果对象定义了readObject方法,则调用该方法进行自定义反序列化(引用[5]中提到)。 注意:序列化机制使用一个序列化版本ID(serialVersionUID)来验证版本一致性。如果反序列化时发现本地类的版本ID与字节流中的不一致,会抛出InvalidClassException。 下面我们通过一个简单的代码示例来展示序列化反序列化的过程: 首先定义一个可序列化的类: ```java import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private transient int age; // transient关键字标记的字段不会被序列化 public Person(String name, int age) { this.name = name; this.age = age; } // 自定义序列化方法 private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // 默认序列化非transient字段 // 这里可以添加自定义序列化逻辑 } // 自定义反序列化方法 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 默认反序列化非transient字段 // 这里可以添加自定义反序列化逻辑 } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } } ``` 序列化测试: ```java public class SerializableTest { public static void main(String[] args) throws Exception { Person person = new Person("Alice", 30); // 序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser")); oos.writeObject(person); oos.close(); // 反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser")); Person deserializedPerson = (Person) ois.readObject(); ois.close(); System.out.println(deserializedPerson); // 输出:Person{name='Alice', age=0}(因为age是transient,反序列化后为0) } } ``` 在这个例子中,我们注意到: - 使用transient关键字修饰的age字段不会被序列化,因此在反序列化后,age字段的值是0(int的默认值)。 - 通过自定义writeObjectreadObject方法,我们可以控制序列化反序列化的过程(例如加密敏感数据)。 序列化机制的工作流程总结: 1. 序列化: - 检查对象是否实现Serializable接口。 - 创建ObjectOutputStream。 - 写入对象类型类描述符。 - 递归写入对象数据(非transient非静态字段)。 - 如果对象有自定义的writeObject方法,则调用该方法。 2. 反序列化: - 读取字节流,解析类描述符。 - 验证本地类与字节流中的类版本是否一致(通过serialVersionUID)。 - 创建对象实例(不调用构造函数)。 - 递归读取字段数据并填充到对象中。 - 如果对象有自定义的readObject方法,则调用该方法。 此外,引用[4]提到了在RPC(远程过程调用)中,序列化反序列化需要约定相同的算法,否则会出现解析失败。例如,如果一方使用JSON序列化,另一方使用XML反序列化,则无法正确解析。 引用[5]提到了几种自定义序列化的方式: - 实现Externalizable接口(更高效,但需要手动控制所有字段)。 - 在类中定义writeObjectreadObject方法(更灵活,可以控制部分字段)。 - 使用第三方库如Jackson(支持JSON等格式,且可以通过白名单机制提高安全性)。 性能与安全提示: - 序列化过程可能较慢,因为涉及反射递归处理对象图。 - 反序列化过程可能成为安全漏洞的来源(如反序列化攻击),因此建议: - 不要反序列化不受信任的数据。 - 使用最新版本的序列化机制,并注意安全更新。 - 使用第三方库(如Jackson)并配置白名单,只允许反序列化预期的类。 最后,我们通过一个流程图来展示序列化反序列化的过程: 序列化过程: ``` [开始] -> [检查Serializable接口] -> [创建ObjectOutputStream] -> [写入类描述符] -> [递归写入对象数据] -> [自定义writeObject?] -> [结束] ``` 反序列化过程: ``` [开始] -> [读取字节流] -> [解析类描述符] -> [检查serialVersionUID] -> [创建空对象] -> [递归读取字段数据] -> [自定义readObject?] -> [结束] ``` 希望以上内容能帮助你理解序列化反序列化的实现原理。 §§相关问题§§ 1. 为什么Java序列化中要使用serialVersionUID?如何生成? 2. 如何避免Java反序列化漏洞? 3. 除了Java自带的序列化机制,还有哪些流行的序列化框架?它们有什么优缺点?</think>### 序列化反序列化的实现原理及工作机制 #### 一、核心概念 1. **序列化**:将对象转换为字节流的过程(内存 → 字节流) 2. **反序列化**:将字节流恢复为对象的过程(字节流 → 内存) 3. **核心接口**:`java.io.Serializable`(标记接口,无方法) #### 二、底层实现原理 1. **字节流结构**(序列化后的二进制格式): ```plaintext | 魔数(0xACED) | 版本号 | 类描述符 | 字段数据 | 结束标记 | ``` - **类描述符**:包含类名、serialVersionUID、字段类型信息 - **字段数据**:按字段类型编码的二进制值(如int→4字节) 2. **反射机制**: - 序列化时:通过反射获取对象的所有字段值 - 反序列化时:通过反射创建新对象并填充字段值 3. **递归处理**: - 对象图遍历:序列化时会递归处理所有关联对象 - 引用处理:相同对象的多次引用只存储一次(避免循环引用) #### 三、Java原生序列化工作流程 ```mermaid graph TD A[对象实例] -->|序列化| B[ObjectOutputStream] B --> C[写入类元数据] C --> D[递归写入字段值] D --> E[生成字节流] E -->|网络/存储| F[字节流] F -->|反序列化| G[ObjectInputStream] G --> H[读取类元数据] H --> I[创建空对象] I --> J[递归填充字段值] J --> K[重建对象] ``` #### 四、关键实现细节 1. **serialVersionUID的作用**: ```java private static final long serialVersionUID = 1L; // 显式声明版本号 ``` - 版本一致性校验:反序列化时比对字节流与本地类的版本号 - 未显式声明时:JVM根据类结构自动生成(易因字段修改导致不兼容) 2. **字段处理规则**: - 序列化:所有非`transient`、非`static`字段 - 跳过字段:`transient`修饰符或`static`字段 ```java public class User implements Serializable { private String name; // 序列化 private transient int age; // 不序列化 private static String type; // 不序列化 } ``` 3. **自定义序列化**(通过特殊方法覆盖默认行为): ```java private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // 默认序列化 out.writeUTF(encrypt(data)); // 自定义加密 } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 默认反序列化 this.data = decrypt(in.readUTF()); // 自定义解密 } ``` #### 五、跨语言序列化方案(如Protobuf) 1. **IDL定义**(接口描述语言): ```protobuf syntax = "proto3"; message User { string name = 1; int32 id = 2; } ``` 2. **编码特点**: - **Tag-Length-Value结构**:字段ID + 长度 + 值 - **变长整数编码**:小整数用1字节存储(Varint) - **跨平台**:生成Java/C++/Python等语言的类 3. **性能对比**: | 指标 | Java原生序列化 | Protobuf | |---------------|---------------|----------| | 字节大小 | 大(含元数据) | 小(约1/3) | | 速度 | 慢 | 快(3-5倍) | | 跨语言支持 | 仅Java | 多语言 | #### 六、安全机制 1. **反序列化漏洞防护**: ```java // 启用反序列化过滤器(JDK9+) ObjectInputStream ois = new ObjectInputStream(input); ois.setObjectInputFilter(filter); ``` - 白名单机制:只允许特定类进行反序列化 - 深度验证:限制对象图的最大深度 2. **最佳实践**: - 避免反序列化不可信数据源 - 对敏感字段使用`transient`或自定义加密 - 定期更新serialVersionUID #### 七、应用场景 1. **持久化存储**: ```java // 序列化到文件 try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("user.dat"))) { oos.writeObject(user); } ``` 2. **网络传输**(RPC框架): ```java // 序列化后通过Socket发送 Socket socket = new Socket("host", port); ObjectOutputStream oos = new ObjectOutputStream( socket.getOutputStream()); oos.writeObject(request); ``` 3. **分布式缓存**:Redis等缓存系统存储序列化对象 > **性能优化建议**:高频场景使用Protobuf/Thrift等高效序列化框架[^5]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值