什么是序列化?

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

为什么要序列化?

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

Java中的序列化

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

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

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

public class UserInfo implements Serializable {

    private static final long serialVersionUID = 1L;
public static String defaultPostcode = "310000";
private int age;
private String name; public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public static String getDefaultPostcode() {
return defaultPostcode;
} public static void setDefaultPostcode(String defaultPostcode) {
UserInfo.defaultPostcode = defaultPostcode;
} public void desSelf() {
System.out.println("Default Postcode: " + getDefaultPostcode());
System.out.println("Age: " + getAge());
System.out.println("Name: " + getName());
}
}

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

  

public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fos = new FileOutputStream("temp.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
UserInfo user = new UserInfo();
user.setAge(25);
user.setName("Tom");
System.out.println("Before Serialize");
user.desSelf();
// 保存对象后修改了DefaultPostcode
UserInfo.setDefaultPostcode("110");
oos.writeObject(user);
oos.flush();
FileInputStream fis = new FileInputStream("temp.out");
ObjectInputStream ois = new ObjectInputStream(fis);
user = (UserInfo)ois.readObject();
System.out.println("After Deserialize");
user.desSelf();
}

在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型的变量是被包括进去的。

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

public class UserInfo implements Serializable {
private static final long serialVersionUID = 996890129747019948L;
private String name;
private transient String psw; public UserInfo(String name, String psw) {
this.name = name;
this.psw = psw;
} public String toString() {
return "name=" + name + ", psw=" + psw;
}
} public class TestTransient {
public static void main(String[] args) {
UserInfo userInfo = new UserInfo("张三", "123456");
System.out.println(userInfo);
try {
// 序列化,被设置为transient的属性没有被序列化
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
"UserInfo.out"));
o.writeObject(userInfo);
o.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
try {
// 重新读取内容
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"UserInfo.out"));
UserInfo readUserInfo = (UserInfo) in.readObject();
//读取后psw的内容为null
System.out.println(readUserInfo.toString());
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}

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

private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(elementData.length);
for (int i=0; i<size; i++)
s.writeObject(elementData[i]); if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
} private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
int arrayLength = s.readInt();
Object[] a = elementData = new Object[arrayLength];
for (int i=0; i<size; i++)
a[i] = s.readObject();
}

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

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

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

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

private static final long serialVersionUID = 1L;

    private String password = "pass";

    public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} private void writeObject(ObjectOutputStream out) {
try {
PutField putFields = out.putFields();
System.out.println("原密码:" + password);
password = "encryption";//模拟加密
putFields.put("password", password);
System.out.println("加密后的密码" + password);
out.writeFields();
} catch (IOException e) {
e.printStackTrace();
}
} private void readObject(ObjectInputStream in) {
try {
GetField readFields = in.readFields();
Object object = readFields.get("password", "");
System.out.println("要解密的字符串:" + object.toString());
password = "pass";//模拟解密,需要获得本地的密钥
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} } public static void main(String[] args) {
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
out.writeObject(new Test());
out.close(); ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
Test t = (Test) oin.readObject();
System.out.println("解密后的字符串:" + t.getPassword());
oin.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

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

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

public class Test {
public static void main(String[] args) throws IOException,
ClassNotFoundException {
FileOutputStream fos = new FileOutputStream("temp.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
UserInfo user = new UserInfo();
user.setAge(25);
user.setName("Tom");
oos.writeObject(user);
oos.flush();
System.out.println(new File("temp.out").length());
oos.writeObject(user);
oos.flush();
oos.close();
System.out.println(new File("temp.out").length()); FileInputStream fis = new FileInputStream("temp.out");
ObjectInputStream ois = new ObjectInputStream(fis);
UserInfo user1 = (UserInfo) ois.readObject();
UserInfo user2 = (UserInfo) ois.readObject();
ois.close();
System.out.println(user1 == user2);
}
}

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

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

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

http://www.cnblogs.com/hzmark/archive/2013/01/30/Serialization.html

Java序列化总结的更多相关文章

  1. Java 序列化与反序列化

    1.什么是序列化?为什么要序列化? Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程. 我们都知道,在进行浏览器访问的时候,我们看到的文本.图片.音频. ...

  2. Java序列化与反序列化

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列 ...

  3. java序列化

    什么是java序列化,如何实现java序列化? 我们有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,例如,要将java对象存储到硬盘或者传送给网络上的其他计算机 ...

  4. Java 序列化Serializable详解

    Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...

  5. 关于java序列化中的一个细节

    java序列化机制的可以参考很多资料了,最近在看的时候发现了一些问题. 1. 默认的序列化机制,很多书里讲到序列化类只序列化类名,实例变量,不会实例化类变量(static)和瞬态变量(transien ...

  6. 各种Java序列化性能比较

    转载:http://www.jdon.com/concurrent/serialization.html 这里比较Java对象序列化 XML JSON  Kryo  POF等序列化性能比较. 很多人以 ...

  7. Java序列化格式详解

    RPC的世界,由于涉及到进程间网络远程通信,不可避免的需要将信息序列化后在网络间传送,序列化有两大流派: 文本和二进制. 文本序列化 序列化的实现有很多方式,在异构系统中最常用的就是定义成人类可读的文 ...

  8. 简述java序列化

      1. 什么是Java对象序列化     Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期 ...

  9. 透过byte数组简单分析Java序列化、Kryo、ProtoBuf序列化

    序列化在高性能网络编程.分布式系统开发中是举足轻重的之前有用过Java序列化.ProtocolBuffer等,在这篇文章这里中简单分析序列化后的byte数组观察各种序列化的差异与性能,这里主要分析Ja ...

  10. [转] Java序列化与反序列化

    原文地址:http://blog.csdn.net/wangloveall/article/details/7992448 Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java ...

随机推荐

  1. Android设计和开发系列第一篇:Notifications通知(Develop—API Guides)

    Notifications IN THIS DOCUMENT Design Considerations Creating a Notification Required notification c ...

  2. 【WEB前端开发最佳实践系列】高可读的HTML

    一.HTML语义化 HTML5中增加了很多标签都是基于此类原则设计的(article   nav  header  footer).页面标签语义化的优点是使得搜索引擎以及第三方抓包工具等更容易读懂页面 ...

  3. 学习 python 编写规范 pep8 的问题笔记

    决定开始Python之路了,利用业余时间,争取更深入学习Python.编程语言不是艺术,而是工作或者说是工具,所以整理并遵循一套编码规范是十分必要的.所以今天下午我根据PEP 8整理了一份,以后都照此 ...

  4. android开发-c++代码调用so库

    Android项目的CMakeLists.txt代码如下,so文件放在项目的$Project/app/src/main/jniLibs/$arch下,$arch替换为arm64-v8a armv7a等 ...

  5. 开发常见错误之 : IMP-00058: 遇到 ORACLE 错误 1691

    IMP-00058: 遇到 Oracle 错误 1691ORA-01691: Lob 段YQPRO.SYS_LOB0000031467C00006$$无法通过128(在表空间YQPRO中)扩展这种情况 ...

  6. How to Use Postman to Manage and Execute Your APIs

    How to Use Postman to Manage and Execute Your APIs Postman is convenient for executing APIs because ...

  7. jquery validator

    jQuery.validate是一款非常不错的表单验证工具,简单易上手,而且能达到很好的体验效果,虽然说在项目中早已用过,但看到这篇文章写得还是不错的,转载下与大家共同分享. 一.用前必备 官方网站: ...

  8. 关于servlet3.0中的异步servlet

    刚看了一下维基百科上的介绍,servlet3.0是2009年随着JavaEE6.0发布的: 到现在已经有六七年的时间了,在我第一次接触java的时候(2011年),servlet3.0就已经出现很久了 ...

  9. 关于linux例行任务crontab的使用

    Linux 例行性任务(也叫周期性任务)命令使用:crontab1.crontab -l   查看当前用户的任务2.crontab -e  编辑(设置)当前用户的任务,执行行不用重启crond服务.3 ...

  10. [工具] multidesk

    MultiDesk 是一个选项卡(TAB标签)方式的远程桌面连接 (Terminal Services Client). http://www.hoowi.com/multidesk/index_ch ...