一、绪论

所谓的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、反序列化

ArrayList的反序列化处理原理同上,见源码:

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();

}

}

}

从上面源码又引出另外一个问题,这些方法都定义为private的,那什么时候能调用呢?

3、调用

如果一个类不仅实现了Serializable接口,而且定义了 readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那么将按照如下的方式进行序列化和反序列化:

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方法进行序列化,ObjectInputStream会调用相应的readObject方法进行反序列化。

那么ObjectOutputStream又是如何知道一个类是否实现了writeObject方法呢?又是如何自动调用该类的writeObject方法呢?

答案是:是通过反射机制实现的。

部分解答:

ObjectOutputStream的writeObject又做了哪些事情。它会根据传进来的ArrayList对象得到Class,然后再包装成 ObjectStreamClass,在writeSerialData方法里,会调用ObjectStreamClass的 invokeWriteObject方法,最重要的代码如下:

writeObjectMethod.invoke(obj, new Object[]{ out });

实例变量writeObjectMethod的赋值方式如下:

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;

}

}

在做实验时,我们发现一个问题,那就是为什么需要s.defaultWriteObject();和s.defaultReadObject();语句在readObject(ObjectInputStream o) and writeObject(ObjectOutputStream o)之前呢?

它们的作用如下:

1、It reads and writes all the non transient fields of the class respectively.

2、 These methods also helps in backward and future compatibility. If in future you add some 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

{

s.writeObject(elementData[i]);

}

从源码中,可以观察到 循环时是使用i

参考:

java arraylist 序列化_专题二、ArrayList序列化技术细节详解相关推荐

  1. 高斯消元法java语言设计_高斯消元法(Gauss Elimination)【超详解模板】

    高斯消元法,是线性代数中的一个算法,可用来求解线性方程组,并可以求出矩阵的秩,以及求出可逆方阵的逆矩阵. 高斯消元法的原理是: 若用初等行变换将增广矩阵 化为 ,则AX = B与CX = D是同解方程 ...

  2. java jtable 排序_解决JTable排序问题的方法详解

    JTable的排序是一个让人头疼的问题,Sun没有为排序这个最常用的功能提供类. 但是近日翻看Sun官方java的tutorial,却发现其在文档中提供了这个类的实现,使用非常简单! 使用方法示例: ...

  3. [C#]网络编程系列专题二:HTTP协议详解

    转自:http://www.cnblogs.com/zhili/archive/2012/08/18/2634475.html 我们在用Asp.net技术开发Web应用程序后,当用户在浏览器输入一个网 ...

  4. java不规则算法_分布式id生成算法 snowflake 详解

    背景 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.如在支付流水号.订单号等,随者业务数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需 ...

  5. java 双分派_双分派 和 访问者模式详解 | 学步园

    为什么 网上的人都说 java 只支持 单分派不支持双分派? 这段代码摘子某书[code=Java] public class Dispatch{ static class QQ{} static c ...

  6. java 静态方法顺序_静态方法的加载顺序详解

    Java 静态代码块 静态方法区别 一般情况下,如果有些代码必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的;需要在项目启动的时候就初始化,在不创建对象的情况下,其他程序来调 ...

  7. java+决策树结构_机器学习——决策树,DecisionTreeClassifier参数详解,决策树可视化查看树结构...

    释放双眼,带上耳机,听听看~! 0.决策树 决策树是一种树型结构,其中每个内部节结点表示在一个属性上的测试,每一个分支代表一个测试输出,每个叶结点代表一种类别. 决策树学习是以实例为基础的归纳学习 决 ...

  8. java objectoutputstream怎么用_java序列化与ObjectOutputStream和ObjectInputStream的实例详解...

    java序列化与ObjectOutputStream和ObjectInputStream的实例详解 一个测试的实体类: public class Param implements Serializab ...

  9. ArrayList, LinkedList, Vector - dudu:史上最详解

    ArrayList, LinkedList, Vector - dudu:史上最详解 我们来比较一下ArrayList, LinkedLIst和Vector它们之间的区别.BZ的JDK版本是1.7.0 ...

最新文章

  1. python列表的排序方法_pythonlist排序的两种方法及实例
  2. html表ge模板_16款用户体验优秀的HTML CSS价格表格模板 附演示及下载
  3. Maven常用插件--转
  4. debian编译mysql_MySQL数据库之Debian 6.02下编译安装 MySQL 5.5的方法
  5. Linux之Shell脚本的条件判断和函数
  6. 用master-worker模型比对团队管理
  7. boost::describe模块宏BOOST_DESCRIBE_PP_FOR_EACH的测试程序
  8. linux如何修改网卡序号,CentOS双网卡时改变网卡编号和配置静态路由的方法
  9. Shell命令-文件及内容处理之split、paste
  10. 解决 supervisor中stop django进程不能真正的停止 问题
  11. Node.js Up and Runing 学习日记(八)
  12. java 日期相差年份_JAVA计算两个日期相差的实例
  13. java 虚拟机 分析_Java 虚拟机中的运行时数据区分析
  14. BlockingQueue原理分析(ReentrantLock、Condition的实践)
  15. 拆解查看unity游戏资源
  16. 中国塑料杯市场需求预测与发展前景分析报告2022-2027年
  17. 拼多多无货源店群模式现在还能赚钱吗?(小珏)
  18. 【MT4 Client API 服务器直连接口】接口介绍
  19. 怎么起用计算机无线开关,笔记本电脑无线网卡,教您如何打开电脑无线网卡开关...
  20. 以分割栅格为例实现FME模板的方案优化

热门文章

  1. 18DOM之节点操作
  2. TextRNN用于文本分类
  3. springboot 主键重复导致数据重复_Springboot实现防重复提交和防重复点击(附源码)...
  4. 研究生实名举报深圳大学导师强迫学生延迟毕业,违规报销,各方回应
  5. java char的包装对象,Java 从Character和char的区别来学习自动拆箱装箱
  6. java 判断当前运行的操作系统
  7. Cell | 分子胶水的兴起
  8. k-median聚类算法【基本概念篇】
  9. drugbank下载XML文件解析
  10. netty发送数据_【Netty】JAVA IO模型