zoukankan      html  css  js  c++  java
  • HashMap原理剖析

    什么叫hash?

    就是把一个不固定的长度的二进制值映射成一个固定长度的二进制值。

    hash算法:就是对应的这个映射规则。
    hash值:固定长度的二进制值。

    什么叫hash表?HashMap底层的存储结构就是hashtable。

    什么是hash算法?

    1、除留余数法(应用于根据key找到hash表的index值)
    key % table.length = index 得到的这个index数字就是对应的hashtable的索引

    2、冲突解决(冲突hashtable中的元素index相同)
    table.length = 10
    对应的hash值key.hashCode() = 2
    2 % 10 = 2

    另一个key对应的hash值key.hashCode() = 12
    12 % 10 = 2

    那么,该如何解决?

    解决hash冲突

    01.冲突过程:key4占掉key3的位置(后进来的元素占掉先进来的)
    02.那么key3去哪混?
    key4中的next指针会指向key3的位置(Obejct key4_next = new Object(key3))

    下面我们jdk源码的实现方式自定义一个简化版的HashMap,以便于加深理解。

    1.定义MyMap接口

     1 package cn.yzx.hashmap;
     2 /**
     3  * 自定义的Map接口
     4  * @author Xuas
     5  *
     6  */
     7 public interface MyMap<K,V> {
     8     
     9     public V put(K k,V v);//将k v属性包装成对象存储到hash表中
    10     
    11     public V get(K k);//根据key获取value
    12     
    13     public int size();//获取hash表的长度
    14     
    15     /**
    16      * 内部接口Entry<K,V>
    17      *         Entry对象就是存储到hash表中的数据对象
    18      *         hash表中每个元素存储的数据结构是一个Entry对象(包括key,value,next指针三个属性)
    19      */
    20     public interface Entry<K,V> {
    21         public K getKey();//获取key
    22         public V getValue();//获取value
    23     }
    24 }

    2.定义MyHashMap实现类

      1 package cn.yzx.hashmap;
      2 
      3 import java.util.ArrayList;
      4 import java.util.List;
      5 
      6 public class MyHashMap<K,V> implements MyMap<K, V> {
      7 
      8     /**
      9      * hash表的默认长度
     10      * jdk中的static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
     11      */
     12     private static int defaultLength = 16;
     13     
     14     /**
     15      * 负载因子(阈值)
     16      * 当表中元素数量超过 表容量 * 负载因子,那么表长度就需要扩容。(预警作用)
     17      * static final float DEFAULT_LOAD_FACTOR = 0.75f;
     18      */
     19     private static double defaultLoader = 0.75;
     20     
     21     /**
     22      * 表中存储元素的个数
     23      */
     24     private int size = 0;
     25     
     26     /**
     27      * 存储数据的Entry[]
     28      */
     29     private Entry<K,V>[] table = null;
     30     
     31     /**
     32      * 构造初始化属性值
     33      * public HashMap(int initialCapacity, float loadFactor) {}
     34      * @param length
     35      * @param loader
     36      */
     37     public MyHashMap(int length,double loader) {
     38         defaultLength = length;
     39         defaultLoader = loader;
     40         table = new Entry[defaultLength];
     41     }
     42     
     43     /**
     44      * 无参构造使用默认值
     45      */
     46     public MyHashMap() {
     47         this(defaultLength, defaultLoader);
     48     }
     49     
     50     /**
     51      * 需要把k,v存储到hash表中
     52      */
     53     @Override
     54     public V put(K k, V v) {
     55         
     56         if(size >= defaultLength * defaultLoader) {//进行扩容
     57             up2size();
     58         }
     59         
     60         //01.获取表中索引
     61         int index = getIndex(k);
     62         //02.判断此index上是否存在数据
     63         Entry<K,V> entry = table[index];
     64         if(null == entry) {//为空进行填充entry数据
     65             table[index] = new Entry(k,v,null);//因entry==null,故next为null
     66             size++;
     67         }else {//此index上存在数据
     68             table[index] = newEntry(k,v,entry);//新entry覆盖此位置上的原entry,并将新entry的next指针指向原entry
     69             size++;
     70         }
     71         
     72         return table[index].getValue();
     73     }
     74     
     75     /**
     76      * 实例化Entry对象
     77      * @param k
     78      * @param v
     79      * @return
     80      */
     81     private Entry<K,V> newEntry(K k,V v,Entry<K,V> next) {
     82         return new Entry(k,v,next);
     83     }
     84     
     85     /**
     86      * 扩容:默认扩容是扩大一倍
     87      * 疑问:扩容后原表中的元素在新表中的位置会变化吗?
     88      * 一定会。因为表中索引index是通过hash算法得到的。即:除留余数法 key.hashCode() % table.length = index
     89      * 这个table.length已经变化了!(再散列过程,重新散列分配位置 计算新的索引index)
     90      */
     91     private void up2size() {
     92         Entry<K,V>[] newTable = new Entry[defaultLength * 2];
     93     }
     94     
     95     /**
     96      * 再散列(重新分配位置)
     97      * @param newTable
     98      */
     99     private void againHash(Entry<K,V>[] newTable) {
    100         //01.获取原表中的数据
    101         List<Entry<K,V>> list = new ArrayList<Entry<K,V>>();
    102         
    103         for(int i=0 ; i<table.length; i++) {
    104             if(null == table[i]) {//此判断目的:不一定hash表中所有的元素都有数据(index:i是hash算法计算出来的)
    105                 continue;
    106             }
    107             findEntryByNext(table[i], list);
    108         }
    109         
    110         if(list.size() > 0) {
    111             //重新初始化hash表属性
    112             size = 0;
    113             defaultLength = defaultLength * 2;
    114             table = newTable;
    115             
    116             for(Entry<K,V> entry : list) {
    117                 if(null != entry.next) {
    118                     entry.next = null; //初始化next指针
    119                 }
    120                 put(entry.getKey(), entry.getValue());//将数据添加到扩容后的hash表中
    121             }
    122         }
    123     }
    124     
    125     /**
    126      * 因为hash表是链表结构,所以通过next递归将原表中元素添加到List集合中,以便后续存入新表中
    127      * @param entry
    128      * @param list
    129      */
    130     private void findEntryByNext(Entry<K,V> entry,List<Entry<K,V>> list) {
    131         if(null != entry && null != entry.next) {
    132             list.add(entry);
    133             findEntryByNext(entry.next, list);
    134         }else {
    135             list.add(entry);
    136         }
    137     }
    138     
    139     /**
    140      * 根据key使用hash算法返回hash表的索引index
    141      * 根据key的hash值对hash表长度取模。得到index索引就是表中的存储位置
    142      * @param k
    143      * @return
    144      */
    145     private int getIndex(K k) {
    146         int l = defaultLength;
    147         
    148         int index = k.hashCode() % l;
    149         return index >= 0 ? index : -index;
    150     }
    151     
    152     /**
    153      * 根据key递归的去找key相等的value值
    154      */
    155     @Override
    156     public V get(K k) {
    157         
    158         int index = getIndex(k);//获取索引
    159         if(null == table[index]) {
    160             return null;
    161         }
    162         return findValueByEqualKey(k, table[index]);
    163     }
    164     
    165     /**
    166      * 表中两个元素是同一个索引(同一个索引index下存在不同的key)
    167      * 所以我们需要拿到参数传进来的key和index下与之对应的key相同的那个元素的value
    168      * 使用递归实现
    169      * @param k
    170      * @param entry
    171      * @return
    172      */
    173     private V findValueByEqualKey(K k,Entry<K,V> entry) {
    174         if(k == entry.getKey() || k.equals(entry.getKey())) {
    175             return entry.getValue();
    176         }else {
    177             if(null != entry.next) {
    178                 return findValueByEqualKey(k, entry.next);
    179             }
    180         }
    181         return null;
    182     }
    183 
    184     @Override
    185     public int size() {
    186         // TODO Auto-generated method stub
    187         return 0;
    188     }
    189     
    190     /**
    191      * 内部类实现Entry接口(因为Entry是接口无法实例化,此内部类目的是实例化Entry对象)
    192      *  static class Node<K,V> implements Map.Entry<K,V> {}
    193      * @author Xuas
    194      *
    195      * @param <K>
    196      * @param <V>
    197      */
    198     class Entry<K,V> implements MyMap.Entry<K, V> {
    199         
    200         K k;
    201         
    202         V v;
    203         
    204         Entry<K,V> next;//next指针,next是指向Entry对象的
    205         
    206         public Entry(K k,V v,Entry<K,V> next) {
    207             this.k = k;
    208             this.v = v;
    209             this.next = next;
    210         }
    211         
    212         @Override
    213         public K getKey() {
    214             return k;
    215         }
    216 
    217         @Override
    218         public V getValue() {
    219             return v;
    220         }
    221         
    222     }
    223 
    224 }

    3.测试

     1 package cn.yzx.hashmap;
     2 
     3 import java.util.HashMap;
     4 import java.util.Map;
     5 
     6 public class Test {
     7     public static void main(String[] args) {
     8         // 测试jdk的HashMap
     9         Map<String, String> jdkmap = new HashMap<String, String>();
    10 
    11         Long begin = System.currentTimeMillis();
    12         for (int i = 0; i < 1000; i++) {
    13             jdkmap.put("key" + i, "value" + i);
    14         }
    15 
    16         for (int i = 0; i < 1000; i++) {
    17             System.out.println(jdkmap.get("key" + i));
    18         }
    19         Long end = System.currentTimeMillis();
    20         System.out.println("jdk'time:" + (end - begin));
    21         
    22         System.out.println("*************************************************************");
    23         // 自定义的MyHashMap
    24         MyMap<String, String> mymap = new MyHashMap<String, String>();
    25 
    26         Long begin2 = System.currentTimeMillis();
    27         for (int i = 0; i < 1000; i++) {
    28             mymap.put("key" + i, "value" + i);
    29         }
    30 
    31         for (int i = 0; i < 1000; i++) {
    32             System.out.println(mymap.get("key" + i));
    33         }
    34         Long end2 = System.currentTimeMillis();
    35         System.out.println("jdk'time:" + (end2 - begin2));
    36 
    37     }
    38 }

    这里本人对于性能的对比没做太深研究。

    就酱~

  • 相关阅读:
    javascript实现根据时间段显示问候语的方法
    视觉会议收藏一
    cv的期刊和会议
    CVPR2016 Paper list
    CVPR 2017 Paper list
    关注的牛人
    cvpr2016论文
    linux命令技巧:scp多文件远程拷贝
    linux命令技巧--df -lh:查看磁盘占用情况
    spark--01编译安装spark1.3.1
  • 原文地址:https://www.cnblogs.com/9513-/p/8473814.html
Copyright © 2011-2022 走看看