java ArrayList的序列化分析
一、绪论
所谓的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) andwriteObject(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
java ArrayList的序列化分析的更多相关文章
- Java - ArrayList源码分析
java提高篇(二一)-----ArrayList 一.ArrayList概述 ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 nul ...
- Java ArrayList源码分析(含扩容机制等重点问题分析)
写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...
- Java ArrayList源码分析(有助于理解数据结构)
arraylist源码分析 1.数组介绍 数组是数据结构中很基本的结构,很多编程语言都内置数组,类似于数据结构中的线性表 在java中当创建数组时会在内存中划分出一块连续的内存,然后当有数据进入的时候 ...
- Java|ArrayList源码分析|add()增加方法和grow()扩容方法
本文结构: 1.介绍特点 2.基本方法 3.重点源码分析 1.介绍特点 ArrayList: 是List的一个具体实现子类,是List接口的一个数组实现 (里面必定维护了一个数组). 默认初始容量10 ...
- java ArrayList源码分析(转载)
1.ArrayList是一个相对来说比较简单的数据结构,最重要的一点就是它的自动扩容,可以认为就是我们常说的“动态数组”. 来看一段简单的代码: 12345 ArrayList<String&g ...
- Java集合源码分析(二)ArrayList
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- Java中ArrayList源码分析
一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保 ...
- Java集合源码分析(一)ArrayList
前言 在前面的学习集合中只是介绍了集合的相关用法,我们想要更深入的去了解集合那就要通过我们去分析它的源码来了解它.希望对集合有一个更进一步的理解! 既然是看源码那我们要怎么看一个类的源码呢?这里我推荐 ...
- Java集合干货——ArrayList源码分析
ArrayList源码分析 前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体 ...
随机推荐
- Monkey源码分析之事件注入
本系列的上一篇文章<Monkey源码分析之事件源>中我们描述了monkey是怎么从事件源取得命令,然后将命令转换成事件放到事件队列里面的,但是到现在位置我们还没有了解monkey里面的事件 ...
- 关于Office 中的墨迹功能(可作word电子签名)
原文 关于Office 中的墨迹功能 通过使用 Microsoft Office 2003 中的墨迹功能,可使用 Tablet PC 和 Tablet 笔将手写笔记插入到 Microsoft Offi ...
- 设计模式之职责链模式(Chain of Responsibility)摘录
23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于怎样创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...
- leetcode第22题--Merge k Sorted Lists
problem:Merge k sorted linked lists and return it as one sorted list. Analyze and describe its compl ...
- C语言中的内存管理
开始陆续的发一下唐老师视频的笔记吧,顺便带一些正冲哥书的的内容.不能一下都发出来,因为内容发多了自己也受不了,而且发的都是学习视频时候的一些笔记,可能会有一些问题不是很清晰. 先说一下C语言中的内存管 ...
- SQL Server 2008 允许远程链接,适用于广域网和局域网
用户在使用SQL Server 2008远程链接时,可能会弹出如下对话框: 在链接SQL服务器时发生网络链接错误或特定实例错误.SQL服务器不存在或者链接不成功.请验证用户名是否正确或SQL服务器是否 ...
- web中纯java获取配置文件中的数据
/*********获取配置文件,但配置文件中的值改变,不会随着值的改变也获取的参数值改变**********/ /** * 原因是因为,类装载,装载完后,不会再去装载了 * */// I ...
- Asp.Net异步编程
Asp.Net异步编程-使用了异步,性能就提升了吗? Asp.Net异步编程 写在前面的话,很久没有写Blog了,不对,其实一致就没有怎么写过.今天有空,我也来写一篇Blog 随着.Net4.5的推出 ...
- .NET代码自动编译发布
.NET代码自动编译发布 因本人一直使用.NET开发,在做项目的时候,每次都要涉及到各个环境的部署问题,手工操作容易出错,并且重复劳动多,所以一直在寻找一个能实现自动化部署的方案. 废话不多讲,先 ...
- 【ios开发】iOS App测试方案
之前IOS测试一半都是采用的Testflight,但是2014.2.19日以后,testflight已经不提供新注册的用户下载SDK了. 但是不用担心我们还可以采用其他几种方案. 1)Ubertest ...