zoukankan      html  css  js  c++  java
  • HashMap详解

    HashMap详解

    HashMap


    注:由于HashMap的实现机制过于复杂,内部由哈希数组+链表+红黑树实现,并且其中涉及到Hash算法哈希数组扩容机制负载因子红黑树等等一系列较为复杂的问题,具体可以去看美团网技术团队的关于HashMap的文章。在我自己实现的HashMap中,我把许多东西难度都降低了,我主要想把HashMap的实现机制说清楚,而具体怎么实现的,我自己许多东西不会,以后再来写清楚吧。

    1 概述

    public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

    • HashMap内部是哈希数组+链表+红黑树实现
    • HashMap遍历无序,但其有很快的访问速度
    • HashMap中key不能有重复元素,可以有一个null;value可以有重复元素多个null对象
    • HashMap是线程不安全的

    2 实现机制

    2.1 原理

    HashMap的实现如下图:
    enter description here

    HashMap是使用哈希表存放元素,使用了链地址法(数组+链表)解决哈希冲突。JDK1.8后当链表长度大于8时使用了红黑树解决哈希冲突。

    首先通过key的hashCode()方法找到key的哈希值,通过Hash算法算出该key存放的位置(哈希数组的下标),在这里可能发生哈希冲突,然后插入到链表或红黑树中存放。

    HashMap解决哈希冲突主要有两点:1.好的Hash算法使key-value对均匀分布在哈希数组中。2.key-value对过多时,扩充哈希数组的容量。

    2.2 具体实现

    HashMap的几个字段

    1 transient Node<K,V>[] table;  // 哈希数组
    2 transient int size;  // HashMap存放元素个数
    3 int threshold;  // key-value对的最大容量(哈希数组长度Capacity*负载因子loadFactor)
    4 final float loadFactor;  // 负载因子

    (1).Node[] table是哈希桶数组。Node实现了Map.Entry接口(Map.Entry就是键值对)的链表。

     1 static class Node implements Map.Entry {
     2     final int hash;  // 经过hash运算后得出的数组下标
     3     final K key;
     4     V value;
     5     Node next;
     6     Node(int hash, K key, V value, Node next) {
     7         this.hash = hash;
     8         this.key = key;
     9         this.value = value;
    10         this.next = next;
    11     }
    12     public final K getKey()        { return key; }
    13     public final V getValue()      { return value; }
    14     public final String toString() { return key + "=" + value; }
    15     public final int hashCode() {
    16         return Objects.hashCode(key) ^ Objects.hashCode(value);
    17     }
    18     public final V setValue(V newValue) {
    19         V oldValue = value;
    20         value = newValue;
    21         return oldValue;
    22     }
    23     public final boolean equals(Object o) {
    24         if (o == this)
    25             return true;
    26         if (o instanceof Map.Entry) {
    27             Map.Entry e = (Map.Entry)o;
    28             if (Objects.equals(key, e.getKey()) &&
    29                     Objects.equals(value, e.getValue()))
    30                 return true;
    31         }
    32         return false;

    (2).threshold表示该哈希数组存放的最大存放key-value对数,threshold = loadFactory(负载因子) * Capatity(哈希数组长度)。
    loadFactoy默认0.75,Capatity默认16。在每次存放key-value对时检查threshold,若其不足则需要扩容(哈希数组长度)。
    扩容方法,我不太懂就不写了,源码见HashMap中的resize()方法。
    HashMap中put方法
    注:美团上面的摘抄
    enter description here

     1 public V put(K key, V value){
     2     // 对key的hashCode()做hash
     3     return putVal(hash(key), key, value, false, true);
     4 }
     5 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){
     6     Node<K,V>[] tab; Node<K,V> p; int n, i;
     7     // 步骤①:tab为空则创建
     8     if ((tab = table) == null || (n = tab.length) == 0)
     9         n = (tab = resize()).length;
    10     // 步骤②:查看该链表或者红黑树中是否有元素
    11     if ((p = tab[i = (n - 1) & hash]) == null)
    12         // 没有元素就创建头元素
    13         tab[i] = newNode(hash, key, value, null);
    14     else {
    15         Node<K,V> e; K k;
    16         // 步骤③:判断table[i]中首个元素是否是该key
    17         if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))){
    18             // 是-->替换
    19             e = p;
    20         }
    21         // 步骤④:判断是否是红黑树
    22         else if (p instanceof TreeNode){
    23             // 是-->直接在树中插入键值对
    24             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    25         }
    26         else {
    27             // 遍历链表
    28             for (int binCount = 0; ; ++binCount) {
    29                 if ((e = p.next) == null) {
    30                     // 步骤⑤:判断链表长度是否大于8,若大于转换成红黑树处理
    31                     p.next = newNode(hash, key, value, null);
    32                     // 转换成红黑树处理 
    33                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    34                         treeifyBin(tab, hash);
    35                     break;
    36                 }
    37                 if (e.hash == hash &&
    38                     ((k = e.key) == key || (key != null && key.equals(k))))
    39                     break;
    40                 p = e;
    41             }
    42         }
    43         if (e != null) { // existing mapping for key
    44             V oldValue = e.value;
    45             if (!onlyIfAbsent || oldValue == null)
    46                 e.value = value;
    47             afterNodeAccess(e);
    48             return oldValue;
    49         }
    50     }
    51     ++modCount;
    52     // 步骤⑥:插入成功,判断是否超过最大容量
    53     if (++size > threshold){
    54         // 扩容
    55         resize();
    56     }
    57     afterNodeInsertion(evict);
    58     return null;
    59 }

    (3).扩容机制:不会以后来写。

    3.自己写的HashMap

      1 class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>{
      2     private static final int DEFAULT_CAPACITY = 16;  // 哈希数组默认大小
      3     private int size;  // HashMap存放元素个数
      4     private Node[] table;  // 哈希数组
      5 
      6     HashMap(){
      7         table = new Node[DEFAULT_CAPACITY];
      8     }
      9 
     10     @Override
     11     public V put(K key, V value) {
     12         // 找到该key对应的哈希数组下标
     13         int index = hash(key);
     14         // 该链表没有元素
     15         if(table[index] == null){
     16             table[index] = new Node<>(index, key, value, null);
     17             size++;
     18             return null;
     19         }
     20         @SuppressWarnings("unchecked")
     21         Node<K, V> node = table[index];
     22         Node<K, V> prev = node;
     23         // 遍历该链表
     24         while(node != null){
     25             // 找到key,则替换value并返回原value
     26             if(Objects.equals(node.getKey(),key)){
     27                 V oldValue = node.getValue();
     28                 node.setValue(value);
     29                 return oldValue;
     30             }
     31             prev = node;
     32             node = node.next;
     33         }
     34         // 未找到,则加入链表
     35         prev.next = new Node<>(index, key, value, null);
     36         size++;
     37         return null;
     38     }
     39 
     40     @Override
     41     public V get(Object key) {
     42         // 找到该key对应的哈希数组下标
     43         int index = hash(key);
     44         // 该链表没有元素
     45         if(table[index] == null){
     46             return null;
     47         }
     48         @SuppressWarnings("unchecked")
     49         Node<K, V> node = table[index];
     50         // 遍历该链表
     51         do {
     52             if(Objects.equals(node.getKey(),key)){
     53                 return node.getValue();
     54             }
     55             node = node.next;
     56         } while (node != null);
     57         return null;
     58     }
     59 
     60     @Override
     61     public V remove(Object key) {
     62         // 找到该key对应的哈希数组下标
     63         int index = hash(key);
     64         @SuppressWarnings("unchecked")
     65         Node<K, V> node = table[index];
     66         // 该链表没有元素
     67         if(node == null){
     68             return null;
     69         }
     70         // 该链表只有一个元素
     71         if(node.next == null){
     72             if(Objects.equals(node.getKey(),key)){
     73                 V oldValue = node.getValue();
     74                 table[index] = null;
     75                 size--;
     76                 return oldValue;
     77             }
     78             return null;
     79         }
     80         // 遍历该链表
     81         Node<K, V> prev = node;
     82         node = node.next;
     83         while(node != null){
     84             if(Objects.equals(node.getKey(),key)){
     85                 prev.next = node.next;
     86                 size--;
     87                 return node.getValue();
     88             }
     89             node = node.next;
     90             prev = prev.next;
     91         }
     92         return null;
     93     }
     94 
     95     @Override
     96     @SuppressWarnings("unchecked")
     97     public Set<Entry<K, V>> entrySet() {
     98         Set<Entry<K, V>> set = new HashSet<>();
     99         for(Node<K, V> node: table ){
    100             while(node != null){
    101                 set.add(node);
    102                 node = node.next;
    103             }
    104         }
    105         return set;
    106     }
    107 
    108     @Override
    109     public int size() {
    110         return size;
    111     }
    112 
    113     /**
    114      * 计算key的哈希值,然后Hash算法(取模)求出该key对应的数组下标
    115      */
    116     public int hash(Object key){
    117         return Objects.hashCode(key) % table.length;
    118     }
    119 
    120     static class Node<K, V> implements Map.Entry<K, V>{
    121         final int hash;  // 经过hash运算得出的数组下标
    122         final K key;
    123         V value;
    124         Node<K, V> next;
    125 
    126         Node(int hash, K key, V value, Node<K, V> next) {
    127             this.hash = hash;
    128             this.key = key;
    129             this.value = value;
    130             this.next = next;
    131         }
    132 
    133         @Override
    134         public K getKey() {
    135             return this.key;
    136         }
    137 
    138         @Override
    139         public V getValue() {
    140             return this.value;
    141         }
    142 
    143         @Override
    144         public V setValue(V value) {
    145             V oldValue = this.value;
    146             this.value = value;
    147             return oldValue;
    148         }
    149 
    150         @Override
    151         public boolean equals(Object obj) {
    152             if(obj == this){
    153                 return true;
    154             }
    155             if(obj instanceof Map.Entry<?, ?>){
    156                 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
    157                 return Objects.equals(entry.getKey(),this.key) && Objects.equals(entry.getValue(),this.value);
    158             }
    159             return false;
    160         }
    161 
    162         @Override
    163         public int hashCode() {
    164             return Objects.hashCode(this.key) ^ Objects.hashCode(this.value);
    165         }
    166 
    167         @Override
    168         public String toString() {
    169             return this.key + "=" + this.value;
    170         }
    171     }
    172 }

    4.Reference

    美团点评技术团队:http://tech.meituan.com/java-hashmap.html

  • 相关阅读:
    could not detect mdm peripheral on hardware
    学习zynq的一些感受
    sdk添加新的C文件编译出错
    linux下驱动webcam
    转:fatal error: SDL/SDL.h: No such file or directory
    转:Unknown module(s) in QT: multimedia
    HFSS设计导入AD中
    REST(Representational state transfer)的四个级别以及HATEOAS介绍
    Servlet CDI Example Analysis
    Introduction and use of Cookie and Session(Cookie&Session的介绍和使用)
  • 原文地址:https://www.cnblogs.com/maying3010/p/6492542.html
Copyright © 2011-2022 走看看