zoukankan      html  css  js  c++  java
  • Java的HashMap实现原理整理总结

    通过Debug 探寻Java-HashMap 实现原理:

    一个简单的例子,代码如下,

    测试方法 main:

     1     public static void main(String[] args) {
     2         
     3         KeyObj obj1 = new KeyObj("AAAA");
     4         KeyObj obj2 = new KeyObj("BBBB");
     5         KeyObj obj3 = new KeyObj("CCCCC");
     6         KeyObj obj4 = new KeyObj("DDDDD");
     7         
     8         HashMap<KeyObj, String> hashMap = new HashMap<KeyObj, String>();
     9         hashMap.put(obj1, "aaaa");
    10         hashMap.put(obj2, "bbbb");
    11         hashMap.put(obj3, "ccccc");
    12         hashMap.put(obj4, "ddddd");
    13         
    14         System.out.println(hashMap.values());
    15         
    16     }

    KeyObj.java

     1 public class KeyObj {
     2 
     3     String keyStr;
     4     
     5     public KeyObj(String keyStr) {
     6         super();
     7         this.keyStr = keyStr;
     8     }
     9 
    10     public String getKeyStr() {
    11         return keyStr;
    12     }
    13 
    14     public void setKeyStr(String keyStr) {
    15         this.keyStr = keyStr;
    16     }
    17   
    18     @Override
        // 覆盖hashCode方法,使得key的单双数分别获得一致的hash值,方便测试
    19 public int hashCode() { 20 if (keyStr.length() % 2 == 0) { 21 return 31; 22 } 23 return 95; 24 } 25 26 @Override 27 public boolean equals(Object obj) { 28 KeyObj obj1 = (KeyObj) obj; 29 if (this.keyStr.equalsIgnoreCase(obj1.keyStr)) 30 return true; 31 return false; 32 } 33 34 }

    运行测试方法main,Debug查看HashMap:

    1.可以看到HashMap其实是有一个名称为table的Entry数组,我们使用HashMap的put方法,本质是把我们的Key-Value作为Entry对象放入到HashMap中。

    2.HashMap的table数组初始大小为16.

    3.为何我们put了4个对象却只使用了table[10] 与 table[14]?

    查看put方法JDK代码:

     1     public V put(K key, V value) {
     2         if (table == EMPTY_TABLE) { 
     3             inflateTable(threshold); // 3.1 table若为空创建table,16大小
     4         }
     5         if (key == null)
     6             return putForNullKey(value); // 3.2 key若为空放入table[0]
     7         int hash = hash(key); // 3.3 计算放入key的hash,值为调用KeyObj的hashcode方法,再hash
     8         int i = indexFor(hash, table.length); 
           // 3.4 计算当前put的Enrty在table数组中精确位置(int i),跟踪代码可以很容易看出:
           // 3.4.1 精确位置i是由key的hash值与table.length取模
    9 for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 3.5 遍历table[i]的Entry 10 Object k; 11 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // 3.6 若put相同key的KeyObj,则替换旧值 12 V oldValue = e.value; 13 e.value = value; 14 e.recordAccess(this); 15 return oldValue; 16 } 17 } 18 19 modCount++; 20 addEntry(hash, key, value, i);// 3.7 创建Entry,当前table[i]的首节点变为当前put节点Entry对象的next节点,注:JDK8之前每一个table[i]上的Entry使用单链表存储的 21 return null; 22 }

    理解了put,查看HashMap的get方法就很简单了:

    1     public V get(Object key) {
    2         if (key == null)
    3             return getForNullKey();
    4         Entry<K,V> entry = getEntry(key);
    5 
    6         return null == entry ? null : entry.getValue();
    7     }

    继续查看第四行getEntry方法:

     1     final Entry<K,V> getEntry(Object key) {
     2         if (size == 0) {
     3             return null;
     4         }
     5 
     6         int hash = (key == null) ? 0 : hash(key);
     7         for (Entry<K,V> e = table[indexFor(hash, table.length)]; // 找到table[i],并便利该位置节点
     8              e != null;
     9              e = e.next) {
    10             Object k;
    11             if (e.hash == hash &&
    12                 ((k = e.key) == key || (key != null && key.equals(k)))) // 找到,返回
    13                 return e;
    14         }
    15         return null;
    16     }

    其他:

    在put函数代码中有一个注释:

    JDK8之前每一个table[i]上的Entry使用单链表存储的

    一直到JDK7为止,HashMap的结构都是这么简单,基于一个数组以及多个链表的实现,hash值冲突的时候,就将对应节点以链表的形式存储。

    这样子的HashMap性能上就抱有一定疑问,如果说成百上千个节点在hash时发生碰撞,存储一个链表中,那么如果要查找其中一个节点,那就不可避免的花费O(N)的查找时间,这将是多么大的性能损失。这个问题终于在JDK8中得到了解决。再最坏的情况下,链表查找的时间复杂度为O(n),而红黑树一直是O(logn),这样会提高HashMap的效率。

    所以在JDK8中,当同一个hash值的节点数不小于8时,将不再以单链表的形式存储了,会被调整成一颗红黑树。

  • 相关阅读:
    ZYNQ. Interrupt(1)Private Timer
    RaspberryPi.1.开机与远程桌面
    ZYNQ. DMA基本用法
    ZYNQ. LwIP.PHY.KSZ9031RNX
    Verilog笔记.三段式状态机
    c语言.函数指针数组
    c语言学习笔记.链表.
    D3D基本框架:即D3D头文件分类
    win32窗口:关于鼠标定位位置偏移问题的原因及解决方法
    <转>C++基础知识: 引用
  • 原文地址:https://www.cnblogs.com/javapath/p/7346357.html
Copyright © 2011-2022 走看看