Java序列化总结

简介: 前一段时间写的关于集合类源码分析的博客中其实一直没有提到两个方法,那就是writeObject和readObject方法。这两个方法涉及到序列化的内容,这篇博文总结遇到过的和序列化相关的内容。

     前一段时间写的关于集合类源码分析的博客中其实一直没有提到两个方法,那就是writeObject和readObject方法。这两个方法涉及到序列化的内容,这篇博文总结遇到过的和序列化相关的内容。

     什么是序列化?

     序列化是将对象的状态信息转化为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后可以通过存储区中读取或反序列化对象的状态重新创建对象。

     为什么要序列化?

     有两个最重要的原因促使对序列化的使用:一个原因是将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本;另一个原因是通过值将对象从一个应用程序域发送到另一个应用程序域中。例如,在网络中传输的数据都必须要序列化。

     Java中的序列化

     Java中的序列化机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输或者持久化存储到数据库或文件系统中,然后再需要的时候可以读取字节流中的信息重构一个相同的对象。序列化在Java中有着广泛的应用,RMI、Hessian等技术都是以此为基础的。

     下面是一些序列化涉及到的内容的例子。

     UserInfo类是下面序列化例子中都要用到的一个保存基本信息的类。

 1 public class UserInfo implements Serializable {
 2 
 3     private static final long serialVersionUID = 1L;
 4     public static String defaultPostcode = "310000";
 5     private int age;
 6     private String name;
 7 
 8     public int getAge() {
 9         return age;
10     }
11 
12     public void setAge(int age) {
13         this.age = age;
14     }
15 
16     public String getName() {
17         return name;
18     }
19 
20     public void setName(String name) {
21         this.name = name;
22     }
23 
24     public static String getDefaultPostcode() {
25         return defaultPostcode;
26     }
27 
28     public static void setDefaultPostcode(String defaultPostcode) {
29         UserInfo.defaultPostcode = defaultPostcode;
30     }
31 
32     public void desSelf() {
33         System.out.println("Default Postcode: " + getDefaultPostcode());
34         System.out.println("Age: " + getAge());
35         System.out.println("Name: " + getName());
36     }
37 }

     结合UserInfo的内容,先看下面这个main方法。

 1 public static void main(String[] args) throws IOException, ClassNotFoundException {
 2         FileOutputStream fos = new FileOutputStream("temp.out");
 3         ObjectOutputStream oos = new ObjectOutputStream(fos);
 4         UserInfo user = new UserInfo();
 5         user.setAge(25);
 6         user.setName("Tom");
 7         System.out.println("Before Serialize");
 8         user.desSelf();
 9         // 保存对象后修改了DefaultPostcode
10         UserInfo.setDefaultPostcode("110");
11         oos.writeObject(user);
12         oos.flush();
13         FileInputStream fis = new FileInputStream("temp.out");
14         ObjectInputStream ois = new ObjectInputStream(fis);
15         user = (UserInfo)ois.readObject();
16         System.out.println("After Deserialize");
17         user.desSelf();
18     }

     在5、6两行设置了age为25,name为Tom,然后输出了UserInfo自己的描述:

Before Serialize
Default Postcode: 310000
Age: 25
Name: Tom

     可以看到Age和Name分别是设置的值。defaultPostCode是UserInfo的一个静态变量,它的值是类中指定的310000。然后将这个对象进行了序列化,保存在temp.out中。

     在第10行修改了defaultPostcode的值为110,然后反序列化并输出user的描述信息,结果如下:

After Deserialize
Default Postcode: 110
Age: 25
Name: Tom

     为什么反序列化后defaultPostcode不是310000而是修改的110呢?因为序列化保存的是对象的状态,而静态变量属于类的状态,在序列化的时候不会被保存。

     注意,需要被序列化的对象必须实现Serializable接口,否则在序列化的时候会抛出java.io.NotSerializableException异常。

     实现Serializable接口总是会要求添加一个serialVersionUID属性,有以下两种形式:

  private static final long serialVersionUID = 5511561554099546149L;
  private static final long serialVersionUID = 1L;

     它们有什么区别呢?一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。

     下面看一个和序列化相关的关键字Transient(在《ArrayList源码分析》中提到过)。

      Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。

      transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。

    有点抽象,看个例子应该能明白。

 1 public class UserInfo implements Serializable {
 2      private static final long serialVersionUID = 996890129747019948L;
 3      private String name;
 4      private transient String psw;
 5  
 6      public UserInfo(String name, String psw) {
 7          this.name = name;
 8          this.psw = psw;
 9      }
10  
11      public String toString() {
12          return "name=" + name + ", psw=" + psw;
13      }
14  }
15  
16 public class TestTransient {
17     public static void main(String[] args) {
18         UserInfo userInfo = new UserInfo("张三", "123456");
19         System.out.println(userInfo);
20         try {
21             // 序列化,被设置为transient的属性没有被序列化
22             ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
23                     "UserInfo.out"));
24             o.writeObject(userInfo);
25             o.close();
26         } catch (Exception e) {
27             // TODO: handle exception
28             e.printStackTrace();
29         }
30         try {
31             // 重新读取内容
32             ObjectInputStream in = new ObjectInputStream(new FileInputStream(
33                     "UserInfo.out"));
34             UserInfo readUserInfo = (UserInfo) in.readObject();
35             //读取后psw的内容为null
36             System.out.println(readUserInfo.toString());
37         } catch (Exception e) {
38             // TODO: handle exception
39             e.printStackTrace();
40         }
41     }
42 }

     下面说一下上文提到的ArrayList中的writeObject和readObject。先看这两个方法在ArrayList中的具体内容。

 1 private void writeObject(java.io.ObjectOutputStream s)
 2         throws java.io.IOException{
 3     int expectedModCount = modCount;
 4     s.defaultWriteObject();
 5     s.writeInt(elementData.length);
 6     for (int i=0; i<size; i++)
 7         s.writeObject(elementData[i]);
 8 
 9     if (modCount != expectedModCount) {
10         throw new ConcurrentModificationException();
11     }
12 }
13 
14 private void readObject(java.io.ObjectInputStream s)
15         throws java.io.IOException, ClassNotFoundException {
16     s.defaultReadObject();
17     int arrayLength = s.readInt();
18     Object[] a = elementData = new Object[arrayLength];
19     for (int i=0; i<size; i++)
20         a[i] = s.readObject();
21 }

     这两个方法都是private的且没在ArrayList中被调用过,那为什么需要这两个方法呢?

     writeObject和readObject并不是在每个类和接口中都会定义,而只是定义在哪些在序列化和反序列化过程中需要特殊处理的类中。

  stackoverflow上的解答:http://stackoverflow.com/questions/7467313/why-are-readobject-and-writeobject-private-and-why-would-i-write-transient-vari

     也就是说通过这两个方法可以自己去控制序列化和反序列化的过程。下面是这两个方法的一个例子。

 1 private static final long serialVersionUID = 1L;
 2 
 3     private String password = "pass";
 4 
 5     public String getPassword() {
 6         return password;
 7     }
 8 
 9     public void setPassword(String password) {
10         this.password = password;
11     }
12 
13     private void writeObject(ObjectOutputStream out) {
14         try {
15             PutField putFields = out.putFields();
16             System.out.println("原密码:" + password);
17             password = "encryption";//模拟加密
18             putFields.put("password", password);
19             System.out.println("加密后的密码" + password);
20             out.writeFields();
21         } catch (IOException e) {
22             e.printStackTrace();
23         }
24     }
25 
26     private void readObject(ObjectInputStream in) {
27         try {
28             GetField readFields = in.readFields();
29             Object object = readFields.get("password", "");
30             System.out.println("要解密的字符串:" + object.toString());
31             password = "pass";//模拟解密,需要获得本地的密钥
32         } catch (IOException e) {
33             e.printStackTrace();
34         } catch (ClassNotFoundException e) {
35             e.printStackTrace();
36         }
37 
38     }
39 
40     public static void main(String[] args) {
41         try {
42             ObjectOutputStream out = new ObjectOutputStream(
43                     new FileOutputStream("result.obj"));
44             out.writeObject(new Test());
45             out.close();
46 
47             ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
48                     "result.obj"));
49             Test t = (Test) oin.readObject();
50             System.out.println("解密后的字符串:" + t.getPassword());
51             oin.close();
52         } catch (FileNotFoundException e) {
53             e.printStackTrace();
54         } catch (IOException e) {
55             e.printStackTrace();
56         } catch (ClassNotFoundException e) {
57             e.printStackTrace();
58         }
59     }

     writeObject 方法中,对密码进行了加密,在 readObject 中则对 password 进行解密,只有拥有密钥的客户端,才可以正确的解析出密码,确保了数据的安全。

     最后说一下序列化的存储规则。

 1 public class Test {
 2     public static void main(String[] args) throws IOException,
 3             ClassNotFoundException {
 4         FileOutputStream fos = new FileOutputStream("temp.out");
 5         ObjectOutputStream oos = new ObjectOutputStream(fos);
 6         UserInfo user = new UserInfo();
 7         user.setAge(25);
 8         user.setName("Tom");
 9         oos.writeObject(user);
10         oos.flush();
11         System.out.println(new File("temp.out").length());
12         oos.writeObject(user);
13         oos.flush();
14         oos.close();
15         System.out.println(new File("temp.out").length());
16 
17         FileInputStream fis = new FileInputStream("temp.out");
18         ObjectInputStream ois = new ObjectInputStream(fis);
19         UserInfo user1 = (UserInfo) ois.readObject();
20         UserInfo user2 = (UserInfo) ois.readObject();
21         ois.close();
22         System.out.println(user1 == user2);
23     }
24 }

     为什么同一对象写入两次,文件的大小不是写入一次的文件大小的两倍呢?而第三次写入和第二次写入增加的长度是一样的呢?为什么反序列化后的两个对象比较结果是true呢?

     Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得user1和user2二者相等,输出 true。该存储规则极大的节省了存储空间。

     肯定还有我不知道的内容,望多交流讨论。 

如果本文对您有帮助,点一下右下角的“推荐”
目录
相关文章
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
16天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
53 5
|
17天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
21 3
|
20天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
1月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
1月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。
|
1月前
|
消息中间件 存储 Java
大数据-58 Kafka 高级特性 消息发送02-自定义序列化器、自定义分区器 Java代码实现
大数据-58 Kafka 高级特性 消息发送02-自定义序列化器、自定义分区器 Java代码实现
43 3
|
1月前
|
分布式计算 资源调度 Hadoop
Hadoop-10-HDFS集群 Java实现MapReduce WordCount计算 Hadoop序列化 编写Mapper和Reducer和Driver 附带POM 详细代码 图文等内容
Hadoop-10-HDFS集群 Java实现MapReduce WordCount计算 Hadoop序列化 编写Mapper和Reducer和Driver 附带POM 详细代码 图文等内容
88 3
|
1月前
|
Java 数据库 对象存储
Java 序列化详解
本文详细解析了Java序列化的概念与应用。通过具体实例,深入探讨了其在对象存储和传输中的作用及实现方法,帮助读者理解如何有效利用这一特性来简化数据交换,并对其实现机制有了更深入的认识。
|
20天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
18 0