一、绪论
所谓的JAVA序列化与反序列化,序列化就是将JAVA 对象以一种的形式保持,比如存放到硬盘,或是用于传输。反序列化是序列化的一个逆过程。
JAVA规定被序列化的对象必须实现java.io.Serializable这个接口,而我们分析的目标ArrayList同样实现了该接口。
通过对ArrayList源码的分析,可以知道ArrayList的数据存储都是依赖于elementData数组,它的声明为:
transient Object[] elementData;
注意transient修饰着elementData这个数组。
1、先看看transient关键字的作用
我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。
然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上 transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
既然elementData被transient修饰,按理来说,它不能被序列化的,那么ArrayList又是如何解决序列化这个问题的呢?
二、序列化工作流程
类通过实现java.io.Serializable接口可以启用其序列化功能。要序列化一个对象,必须与一定的对象输出/输入流联系起来,通过对象输出流将对象状态保存下来,再通过对象输入流将对象状态恢复。
在序列化和反序列化过程中需要特殊处理的类必须使用下列准确签名来实现特殊方法:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
1、对象序列化步骤
a) 写入
- 首先创建一个OutputStream输出流;
- 然后创建一个ObjectOutputStream输出流,并传入OutputStream输出流对象;
- 最后调用ObjectOutputStream对象的writeObject()方法将对象状态信息写入OutputStream。
b)读取
- 首先创建一个InputStream输入流;
- 然后创建一个ObjectInputStream输入流,并传入InputStream输入流对象;
- 最后调用ObjectInputStream对象的readObject()方法从InputStream中读取对象状态信息。
举例说明:
public class Box implements Serializable {
private static final long serialVersionUID = -3450064362986273896L;
private int width;
private int height;
public static void main(String[] args) {
Box myBox=new Box();
myBox.setWidth(50);
myBox.setHeight(30);
try {
FileOutputStream fs=new FileOutputStream("F:\\foo.ser");
ObjectOutputStream os=new ObjectOutputStream(fs);
os.writeObject(myBox);
os.close();
FileInputStream fi=new FileInputStream("F:\\foo.ser");
ObjectInputStream oi=new ObjectInputStream(fi);
Box box=(Box)oi.readObject();
oi.close();
System.out.println(box.height+","+box.width);
} catch (Exception e) {
e.printStackTrace();
}
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
三、ArrayList解决序列化
1、序列化
从上面序列化的工作流程可以看出,要想序列化对象,使用ObjectOutputStream对象输出流的writeObject()方法写入对象状态信息,即可使用readObject()方法读取信息。
那是不是可以在ArrayList中调用ObjectOutputStream对象的writeObject()方法将elementData的值写入输出流呢?
见源码:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException
{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i = 0; i < size; i++)
{
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount)
{
throw new ConcurrentModificationException();
}
}
虽然elementData被transient修饰,不能被序列化,但是我们可以将它的值取出来,然后将该值写入输出流。
// 片段1 它的功能等价于片段2
s.writeObject(elementData[i]); // 传值时,是将实参elementData[i]赋给s.writeObject()的形参
// 片段2
Object temp = new Object(); // temp并没有被transient修饰
temp = elementData[i];
s.writeObject(temp);
2、反序列化
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException
{
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0)
{
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i = 0; i < size; i++)
{
a[i] = s.readObject();
}
}
}
3、调用
ObjectOutputStream会调用这个类的writeObject方法进行序列化,ObjectInputStream会调用相应的readObject方法进行反序列化。
事情到底是这样的吗?我们做个小实验,来验明正身。
实验1:
public class TestSerialization implements Serializable
{
private transient int num; public int getNum()
{
return num;
} public void setNum(int num)
{
this.num = num;
} private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException
{
s.defaultWriteObject();
s.writeObject(num);
System.out.println("writeObject of "+this.getClass().getName());
} private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException
{
s.defaultReadObject();
num = (Integer) s.readObject();
System.out.println("readObject of "+this.getClass().getName());
} public static void main(String[] args)
{
TestSerialization test = new TestSerialization();
test.setNum(10);
System.out.println("序列化之前的值:"+test.getNum());
// 写入
try
{
ObjectOutputStream outputStream = new ObjectOutputStream(
new FileOutputStream("D:\\test.tmp"));
outputStream.writeObject(test);
} catch (FileNotFoundException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
}
// 读取
try
{
ObjectInputStream oInputStream = new ObjectInputStream(
new FileInputStream("D:\\test.tmp"));
try
{
TestSerialization aTest = (TestSerialization) oInputStream.readObject();
System.out.println("读取序列化后的值:"+aTest.getNum());
} catch (ClassNotFoundException e)
{
e.printStackTrace();
}
} catch (FileNotFoundException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
序列化之前的值:10
writeObject of TestSerialization
readObject of TestSerialization
读取序列化后的值:10
那么ObjectOutputStream又是如何知道一个类是否实现了writeObject方法呢?又是如何自动调用该类的writeObject方法呢?
writeObjectMethod.invoke(obj, new Object[]{ out });
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class[] { ObjectOutputStream.class },
Void.TYPE); private static Method getPrivateMethod(Class cl, String name,
Class[] argTypes, Class returnType)
{
try
{
Method meth = cl.getDeclaredMethod(name, argTypes);
// *****通过反射访问对象的private方法
meth.setAccessible(true);
int mods = meth.getModifiers();
return ((meth.getReturnType() == returnType)
&& ((mods & Modifier.STATIC) == 0) && ((mods & Modifier.PRIVATE) != 0)) ? meth
: null;
} catch (NoSuchMethodException ex)
{
return null;
}
}
readObject(ObjectInputStream o)
and writeObject(ObjectOutputStream o)
之前呢?non transient
fields of the class respectively.non-transient
field to the class and you are trying to deserialize it by the older version of class then the defaultReadObject() method will neglect the newly added field, similarly if you deserialize the old serialized object by the new version then the new non transient field will take default value from JVM四、为什么使用transient修饰elementData?
既然要将ArrayList的字段序列化(即将elementData序列化),那为什么又要用transient修饰elementData呢?
回想ArrayList的自动扩容机制,elementData数组相当于容器,当容器不足时就会再扩充容量,但是容器的容量往往都是大于或者等于ArrayList所存元素的个数。
比如,现在实际有了8个元素,那么elementData数组的容量可能是8x1.5=12,如果直接序列化elementData数组,那么就会浪费4个元素的空间,特别是当元素个数非常多时,这种浪费是非常不合算的。
所以ArrayList的设计者将elementData设计为transient,然后在writeObject方法中手动将其序列化,并且只序列化了实际存储的那些元素,而不是整个数组。
见源码:
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
{
s.writeObject(elementData[i]);
}
从源码中,可以观察到 循环时是使用i<size而不是 i<elementData.length,说明序列化时,只需实际存储的那些元素,而不是整个数组。
参考:
3、ArrayList源码分析——如何实现Serializable
专题二、ArrayList序列化技术细节详解的更多相关文章
-
[C#]网络编程系列专题二:HTTP协议详解
转自:http://www.cnblogs.com/zhili/archive/2012/08/18/2634475.html 我们在用Asp.net技术开发Web应用程序后,当用户在浏览器输入一个网 ...
-
转:【专题二】HTTP协议详解
我们在用Asp.net技术开发Web应用程序后,当用户在浏览器输入一个网址时就是再向服务器发送一个HTTP请求,此时就使用了应用层的HTTP协议,在上一个专题我们简单介绍了网络协议的知识,主要是为了后 ...
-
专题二:HTTP协议详解
我们在用Asp.net技术开发Web应用程序后,当用户在浏览器输入一个网址时就是再向服务器发送一个HTTP请求,此时就使用了应用层的HTTP协议,在上一个专题我们简单介绍了网络协议的知识,主要是为了后 ...
-
Velocity魔法堂系列二:VTL语法详解
一.前言 Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板引擎,而且可以作为普通文本的模板引擎来增强服务端程序文本处理能力.而且Velocity被移植到不 ...
-
Java8初体验(二)Stream语法详解(转)
本文转自http://ifeve.com/stream/ Java8初体验(二)Stream语法详解 感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com上篇文章Java8初体验(一 ...
-
Java8初体验(二)Stream语法详解---符合人的思维模式,数据源--》stream-->;干什么事(具体怎么做,就交给Stream)--》聚合
Function.identity()是什么? // 将Stream转换成容器或Map Stream<String> stream = Stream.of("I", & ...
-
数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解
数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...
-
Java 序列化Serializable详解
Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...
-
Hadoop Mapreduce分区、分组、二次排序过程详解[转]
原文地址:Hadoop Mapreduce分区.分组.二次排序过程详解[转]作者: 徐海蛟 教学用途 1.MapReduce中数据流动 (1)最简单的过程: map - reduce (2) ...
随机推荐
-
hibernate(八)一对多关联
一.一对多单向关良 一对多单向关联与多对一相似 假设一个组有多个用户,即一(Group)对多(User) 需要在Group类中添加一个User类的Set集合(数据库中的用户不可能是重复的,所以要用Se ...
-
python requests库学习
Python 第三方 http 库-Requests 学习 安装 Requests 1.通过pip安装 $ pip install requests 2.或者,下载代码后安装: $ git clone ...
-
阿里云+wordpress搭建个人博客网站
[正文] 在阿里云上搭建使用个人博客主要分为以下几个步骤: 1.购买阿里云ECS主机 2.购买域名 3.申请备案 4.环境配置 5.安装wordpress 6.域名解析 声明一下,本人对服务器端的知识 ...
-
HeadFirst设计模式之状态模式
一. 1. 2.The State Pattern allows an object to alter its behavior when its internal state changes. Th ...
-
(DDD)仓储的思考
(DDD)仓储的思考 为什么需要仓储呢?领域对象(一般是聚合根)的被创建出来后的到最后持久化到数据库都需要跟数据库打交道,这样我们就需要一个类似数据库访问层的东西来管理领域对象.那是不是我们就可以设计 ...
-
List去除重复的元素
有两种方法,一种是去重不带顺序的,一种是去重带顺序的. /* * 方法1: 无顺序 * Hastset根据hashcode判断是否重复,数据不会重复 */ public static Lis ...
-
js异步处理历程
为什么会出现异步: js执行环境是单线程的,异步处理就非常重要. 处理的方法: 方法一:callback hell 解决:解决了异步处理 存在问题:出现多个回调函数嵌套,代码就会比较乱,出现回调地狱现 ...
-
【转】最近很火的 Safe Area 到底是什么
iOS 7 之后苹果给 UIViewController 引入了 topLayoutGuide 和 bottomLayoutGuide 两个属性来描述不希望被透明的状态栏或者导航栏遮挡的最高位置(st ...
-
UI自动化框架——构建思维
目的:从Excel中获取列的值,传输到页面 技巧:尽可能的提高方法的重用率 Java包: 1.java.core包 3个类:1)日志(LogEventListener)扩展web driver自带的事 ...
-
RocketMQ NameServer
NameServer 路由管理,服务注册,服务发现.(类比为soa框架中的zookeeper) 一.路由管理 1.路由注册,由 Broker 向 NameServer 发送心跳,NameServer ...