理解Java集合框架里面的的transient关键字

在分析HashMap和ArrayList的源码时,我们会发现里面存储数据的数组都是用transient关键字修饰的,如下:

HashMap里面的:

ArrayList里面的:

既然用transient修饰,那就说明这个数组是不会被序列化的,那么同时我们发现了这两个集合都自定义了独自的序列化方式:

先看HashMap自定义的序列化的代码:

再看HashMap自定义的反序列化的代码:

这里面我们看到HashMap的源码里面自定义了序列化和反序列化的方法,序列化方法主要是把当前HashMap的buckets数量,size和里面的k,v对一一给写到了对象输出流里面,然后在反序列化的时候,再从流里面一一的解析出来,然后又重新恢复出了HashMap的整个数据结构。

接着我们看ArrayList里面自定义的序列化的实现:

然后反序列化的实现:

ArrayList里面也是把其size和里面不为null的数据给写到流里面,然后在反序列化的时候重新使用数据把数据结构恢复出来。

那么问题来了,为什么他们明明都实现了Serializable接口,已经具备了自动序列化的功能,为啥还要重新实现序列化和反序列化的方法呢?

(1)HashMap中实现序列化和反序列化的原因:

在HashMap要定义自己的序列化和反序列化实现,有一个重要的因素是因为hashCode方法是用native修饰符修饰的,也就是用它跟jvm的运行环境有关,Object类中的hashCode源码如下:

也就是说不同的jvm虚拟机对于同一个key产生的hashCode可能是不一样的,所以数据的内存分布可能不相等了,举个例子,现在有两个jvm虚拟机分别是A和B,他们对同一个字符串x产生的hashCode不一样:

所以导致:

在A的jvm中它的通过hashCode计算它在table数组中的位置是3

在B的jvm中它的通过hashCode计算它在table数组中的位置是5

这个时候如果我们在A的jvm中按照默认的序列化方式,那么位置属性3就会被写入到字节流里面,然后通过B的jvm来反序列化,同样会把这条数据放在table数组中3的位置,然后我们在B的jvm中get数据,由于它对key的hashCode和A不一样,所以它会从5的位置取值,这样以来就会读取不到数据。

如何解决这个问题,首先导致上面问题的主要原因在于因为hashCode的不一样从而可能导致内存分布不一样,所以只要在序列化的时候把跟hashCode有关的因素比如上面的位置属性给排除掉,就可以解决这个问题。

最简单的办法就是在A的jvm把数据给序列化进字节流,而不是一刀切把数组给序列化,之后在B的jvm中反序列化时根据数据重新生成table的内存分布,这样就来就完美解决了这个问题。

(2)ArrayList中实现序列化和反序列化的原因:

在ArrayList中,我们知道数组的长度会随着数据的插入而不断的动态扩容,每次扩容都需要增加原数组一半的长度,这而一半的长度极端情况下都是null值,所以在序列化的时候可以把这部分数据排除出去,从而节省时间和空间:

注意ArrayList在序列化的时候用的size来遍历原数组中的元素,而并不是elementData.length也就是数组的长度,而size的大小就是数组里面非null元素的个数,所以这里才采用了自定义序列化的方式。

到这里细心的朋友可能有个疑问:HashMap中也就是采用的动态数组扩容为什么它在序列化的时候用的是table.length而不是size呢,这其实很容易回答在HashMap中table.length必须是2的n次方,而且这个值会决定了好几个参数的值,所以如果也把null值给去掉,那么必须要重新的估算table.length的值,有可能造成所有数据的重新分布,所以最好的办法就是保持原样。

注意上面的null值,指的是table里面Node元素是null,而并不是HashMap里面的key等于null,而key是Node里面的一个字段。

总结:

本文主要介绍了在HashMap和ArrayList中其核心的数据结构字段为什么用transient修饰并分别介绍了其原因,所以使用序列化时,应该谨记effective java中的一句话:当一个对象的物理表示方法与它的逻辑数据内容有实质性差别时,使用默认序列化形式有N种缺陷,所以应该尽可能的根据实际情况重写序列化方法。

原文发布于微信公众号 - 我是攻城师(woshigcs)

原文发表时间:2018-03-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python爱好者

Android面试之Java基础

14830
来自专栏java一日一条

115个Java面试题和答案——终极列表(上)

本文我们将要讨论Java面试中的各种不同类型的面试题,它们可以让雇主测试应聘者的Java和通用的面向对象编程的能力。下面的章节分为上下两篇,第一篇将要讨论面向对...

10610
来自专栏Java面试笔试题

ArrayList、Vector、LinkedList的存储性能和特性简述

ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数...

12420
来自专栏java一日一条

115个Java面试题和答案——终极列表(上)

本文我们将要讨论Java面试中的各种不同类型的面试题,它们可以让雇主测试应聘者的Java和通用的面向对象编程的能力。下面的章节分为上下两篇,第一篇将要讨论面向对...

6920
来自专栏从流域到海域

《笨办法学Python》 第33课手记

《笨办法学Python》 第33课手记 本节课讲while循环,作者强调while循环的缺点在于循环可能永远进行下去,所以作者推荐使用for循环,在确认循环会结...

19560
来自专栏数据小魔方

如何使用管道操作符优雅的书写R语言代码

本文将跟大家分享如果在R语言中使用管道操作符优化代码,以及管道函数调用及传参的注意事项。 使用R语言处理数据或者分析,很多时候免不了要写连续输入输出的代码,按照...

59070
来自专栏Python私房菜

你所不知道的Python | 字符串连接的秘密

字符串连接,就是将2个或以上的字符串合并成一个,看上去连接字符串是一个非常基础的小问题,但是在Python中,我们可以用多种方式实现字符串的连接,稍有不慎就有可...

16050
来自专栏九彩拼盘的叨叨叨

JavaScript 之 this

在 JavaScript 中,this 的值是动态的,即一个函数中在不同的情况下被调用,this 的值可能是不同的。

10520
来自专栏小樱的经验随笔

getline函数(精华版)

在我的印象中,getline函数经常出现在自己的视野里,模糊地记得它经常用来读取字符串 。但是又对它的参数不是很了解,今天又用到了getline函数,现在来细细...

39840
来自专栏老九学堂

这是谁做的作业!C语言编码太不规范了...

1) 程序应采用缩进风格编写,每层缩进使用一个制表位(TAB),类定义、方法都应顶格书写;

18220

扫码关注云+社区

领取腾讯云代金券