zoukankan      html  css  js  c++  java
  • Java HashMap 分析之一:基本结构

    Java的HashMap非常的常用,本篇研究它的实现算法,最后希望计算出内存占用,性能的量化数据,然后得出什么时候使用HashMap,什么时候不能滥用的结论。
    HashMap实际上是一个数组,数组里面的每个元素都是一个链表。每个元素在通过put方法放入HashMap中的时候,要按照如下步骤进行:
    1.根据该元素自身提供的hashcode计算出散列值,该散列值就是数组的下标
    2.将新元素放入该数组位置的链表中

    先来看一下数组的定义:

    1. /** 
    2.      * The table, resized as necessary. Length MUST Always be a power of two. 
    3.      */  
    4.     transient Entry[] table;  


    这是一个数组,transient关键字告诉我们它不会参与序列化。既然是一个数组,总有数目上限,也就意味着如果存入HashMap的元素太多,导致数组大小不能够存放所有的链表的时候,数组大小必须要能够调整。所以首先来考察一下数组容量的相关算法。
    第一,Entry是什么类型?

    1. static class Entry<K,V> implements Map.Entry<K,V> {  
    2.         final K key;  
    3.         V value;  
    4.         Entry<K,V> next;  
    5.         final int hash;  
    6.   
    7.         /** 
    8.          * Creates new entry. 
    9.          */  
    10.         Entry(int h, K k, V v, Entry<K,V> n) {  
    11.             value = v;  
    12.             next = n;  
    13.             key = k;  
    14.             hash = h;  
    15.         }  
    16.         ....  
    17.         public final boolean equals(Object o) {  
    18.             if (!(o instanceof Map.Entry))  
    19.                 return false;  
    20.             Map.Entry e = (Map.Entry)o;  
    21.             Object k1 = getKey();  
    22.             Object k2 = e.getKey();  
    23.             if (k1 == k2 || (k1 != null && k1.equals(k2))) {  
    24.                 Object v1 = getValue();  
    25.                 Object v2 = e.getValue();  
    26.                 if (v1 == v2 || (v1 != null && v1.equals(v2)))  
    27.                     return true;  
    28.             }  
    29.             return false;  
    30.         }  
    31.   
    32.         public final int hashCode() {  
    33.             return (key==null   ? 0 : key.hashCode()) ^  
    34.                    (value==null ? 0 : value.hashCode());  
    35.         }  
    36.         ....  


    这是一个HashMap类的内部静态类。实现了Map.Entry接口。接受两个模板参数K和V。key和hash一旦在构造函数中被初始化,就不可改变,并且由于有next的存在,Entry可以构成一个单向链表。
    比较重要的是equals和hashCode方法。代码先列出来,后面再解释。

    第二,初始容量的设定
    大多数都在下面的构造函数里面.用于指定的initialCapacity不准小于0,也不能超过最大值。并且最终的capicity必须是2的n次方。还有如果使用了无参数的构造函数,默认会创建一个拥有16个元素的数组。

    1. public HashMap(int initialCapacity, float loadFactor) {  
    2.         if (initialCapacity < 0)  
    3.             throw new IllegalArgumentException("Illegal initial capacity: " +  
    4.                                                initialCapacity);  
    5.         if (initialCapacity > MAXIMUM_CAPACITY)  
    6.             initialCapacity = MAXIMUM_CAPACITY;  
    7.         if (loadFactor <= 0 || Float.isNaN(loadFactor))  
    8.             throw new IllegalArgumentException("Illegal load factor: " +  
    9.                                                loadFactor);  
    10.   
    11.         // Find a power of 2 >= initialCapacity  
    12.         int capacity = 1;  
    13.         while (capacity < initialCapacity)  
    14.             capacity <<= 1;  
    15.   
    16.         this.loadFactor = loadFactor;  
    17.         threshold = (int)(capacity * loadFactor);  
    18.         table = new Entry[capacity];  
    19.         init();  
    20.     }  


    第三,什么时候应该调整数组的大小?
    算法是这样,有一个变量size保存了实际数组已经使用了多少个元素,并且如果size的值达到了变量threshold的值,就必须扩充数组的容量。threshold=capicity*loadFactor.capicity是数组最大的容纳元素个数,loadFactor可以在构造函数中制定,否则采用默认值0.75f。capicity的最大值是1<<30(也就是2的30次方,1073741824).由此我们可以看到HashMap最多存放10亿多个链表。
    第四,如何调整数组大小?
    答案是2倍,很像C++里面的vector的分配策略。

    1. void addEntry(int hash, K key, V value, int bucketIndex) {  
    2.         Entry<K,V> e = table[bucketIndex];  
    3.         table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
    4.         if (size++ >= threshold)  
    5.             resize(2 * table.length);  
    6.     }  


    第五,为什么数组大小必须是2的倍数?
    在后面介绍散列值算法的时候会回答。

  • 相关阅读:
    Zircon
    Linux与Windows的设备驱动模型对比
    c++11 右值引用、移动语义和完美转发
    【Java学习笔记之十四】Java中this用法小节
    【机器学习笔记之四】Adaboost 算法
    【Java学习笔记之十三】初探Java面向对象的过程及代码实现
    【Java学习笔记之十二】Java8增强的工具类:Arrays的用法整理总结
    【机器学习笔记之三】CART 分类与回归树
    【Java学习笔记之十一】Java中常用的8大排序算法详解总结
    【Java学习笔记之十】Java中循环语句foreach使用总结及foreach写法失效的问题
  • 原文地址:https://www.cnblogs.com/chenying99/p/3120345.html
Copyright © 2011-2022 走看看