2019独角兽企业重金招聘Python工程师标准>>>

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

HashMap里面的:

 transient Node<K,V>[] table;

ArrayList里面的:

 transient Object[] elementData

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

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

//1private void writeObject(java.io.ObjectOutputStream s)throws IOException {int buckets = capacity();// Write out the threshold, loadfactor, and any hidden stuffs.defaultWriteObject();s.writeInt(buckets);s.writeInt(size);internalWriteEntries(s);}
//2    public void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {Node<K,V>[] tab;if (size > 0 && (tab = table) != null) {for (int i = 0; i < tab.length; ++i) {for (Node<K,V> e = tab[i]; e != null; e = e.next) {s.writeObject(e.key);s.writeObject(e.value);}}}} 

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

//1private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {// Read in the threshold (ignored), loadfactor, and any hidden stuffs.defaultReadObject();reinitialize();if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new InvalidObjectException("Illegal load factor: " +loadFactor);s.readInt();                // Read and ignore number of bucketsint mappings = s.readInt(); // Read number of mappings (size)if (mappings < 0)throw new InvalidObjectException("Illegal mappings count: " +mappings);else if (mappings > 0) { // (if zero, use defaults)// Size the table using given load factor only if within// range of 0.25...4.0float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);float fc = (float)mappings / lf + 1.0f;int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?DEFAULT_INITIAL_CAPACITY :(fc >= MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY :tableSizeFor((int)fc));float ft = (float)cap * lf;threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?(int)ft : Integer.MAX_VALUE);@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] tab = (Node<K,V>[])new Node[cap];table = tab;// Read the keys and values, and put the mappings in the HashMapfor (int i = 0; i < mappings; i++) {@SuppressWarnings("unchecked")K key = (K) s.readObject();@SuppressWarnings("unchecked")V value = (V) s.readObject();putVal(hash(key), key, value, false, false);}}}

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

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

    private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException{// Write out element count, and any hidden stuffint 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();}}

然后反序列化的实现:

        private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {elementData = EMPTY_ELEMENTDATA;// Read in size, and any hidden stuffs.defaultReadObject();// Read in capacitys.readInt(); // ignoredif (size > 0) {// be like clone(), allocate array based upon size not capacityensureCapacityInternal(size);Object[] a = elementData;// Read in all elements in the proper order.for (int i=0; i<size; i++) {a[i] = s.readObject();}}}

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

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

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

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

 public native int 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值,所以在序列化的时候可以把这部分数据排除出去,从而节省时间和空间:

     for (int i=0; i<size; i++) {s.writeObject(elementData[i]);}

注意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种缺陷,所以应该尽可能的根据实际情况重写序列化方法。

转载于:https://my.oschina.net/u/1027043/blog/1627441

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

  1. 深入理解Java集合框架

    Java集合实现了常用数据结构,是开发中最常用的功能之一. Java集合主要的功能由三个接口:List.Set.Queue以及Collection组成. 常见接口: List : 列表,顺序存储,可重 ...

  2. arraylist如何检测某一元素是否为空_我们应该如何理解Java集合框架的关键知识点?...

    以下介绍经常使用的集合类,这里不介绍集合类的使用方法,只介绍每个集合类的用途和特点,然后通过比较相关集合类的不同特点来让我们更深入的了解它们. Collection接口 Collection是最基本的 ...

  3. java集合的添加方法_深入理解java集合框架之---------Arraylist集合 -----添加方法

    Arraylist集合 -----添加方法 1.add(E e) 向集合中添加元素 /** * 检查数组容量是否够用 * @param minCapacity */ public void ensur ...

  4. java arraylist 构造_深入理解java集合框架之---------Arraylist集合 -----构造函数

    ArrayList有三个构造方法 ArrayList有三个常量 1.private transient Object[] elementData (数组); 2.private int size (元 ...

  5. 深入理解java集合框架之---------Arraylist集合 -----添加方法

    Arraylist集合 -----添加方法 1.add(E e) 向集合中添加元素 /*** 检查数组容量是否够用* @param minCapacity*/public void ensureCap ...

  6. Java集合框架(JCF)归纳总结

    Java集合框架--JCF,在java 1.2版本中被加入,它包含了大量集合操作,是Java体系中的重要组成部分.网上已有很多JCF的框架图,这里根据自己的理解整理了一份JCF框架图如下: JCF主要 ...

  7. Java集合框架之三:HashMap源码解析

    Java集合框架之三:HashMap源码解析 版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! HashMap在我们的工作中应用的非常广泛,在工作面试中也经常会被问到,对于这样一个重要的集 ...

  8. 史上最全的集合框架讲解 ----- Java 集合框架(3)---- Map 相关类最全解析

    引言 好了,步入正题,上篇文章Java 集合框架(2)---- List 相关类解析中我们一起看了一下 List 接口的相关具体类(ArrayList.LinkedList-.),这篇开始我们开始探索 ...

  9. Java 集合框架(5)---- Map 相关类解析(中)

    本文标题大纲: 文章目录 前言 HashMap TreeMap 指定 TreeMap 的元素排序方式 前言 还是先上那张图吧,我又偷懒了,还是只关注 Map 接口下的类就行了: 在上上篇文章中我们一起 ...

最新文章

  1. 安卓开发小知识 - 3
  2. SQL日期时间格式自由转换大全
  3. TCP 三次握手原理,你真的理解吗
  4. Apollo进阶课程㉑丨Apollo规划技术详解——Basic Motion Planning and Overview
  5. 男女择偶基本心理类型
  6. git使用的基本流程_我并不是高冷(Git进行中01):git基本流程简介
  7. php 去掉script,php怎么去除js
  8. COJ-1271 Brackets Sequence
  9. oracle用分号拼接函数,ORACLE以逗号分隔连接列的值   函数名:wmsys.wm_concat
  10. C#中的System.Speech命名空间初探
  11. Windows的图形设备接口(GDI)入门 上篇
  12. 自己经常测试,不要等别人发现系统死了
  13. plsql导出表结构到excel_PLSQL导出表结构和数据的三种方式
  14. 全网最强红黑树的理解和实现
  15. 平板示波器如何进行探头的补偿和衰减系数设定-Pintech品致
  16. 正常查看网页中压缩的js代码
  17. 一款优秀的开源SQL检查审核神器!再也不用担心写SQL出问题了
  18. 显示农历天气时钟小部件下载_玛雅日历安卓版下载|玛雅日历app下载_v5.3.2
  19. Linux-tab键应用
  20. nginx $remote_addr 详解

热门文章

  1. 如何设置 jqplot 图表插件的轴和网格
  2. matlab练习程序(图像放大/缩小,放大没有进行插值操作)
  3. 前端需要了解的nginx(2)
  4. 如何在新版的gitbook上写自己的书
  5. 【leetcode】390. Elimination Game
  6. 2017-06-02 前端日报
  7. mysql配置——库表操作、用户操作
  8. Word2013中制作按钮控件
  9. Spring框架之Filter应用
  10. LeetCode 162. 寻找峰值