HashMap原理以及实现

来源:互联网 发布:域名的作用是什么 编辑:程序博客网 时间:2024/06/01 08:05

一、HashMap概述

  HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

 

二、   HashMap的数据结构

       Hash,一般翻译做散列,也有直接音译为哈希的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

HashMap内部其实是一个线性的数组作为存储数据的容器,线性数组如何实现key,value访问数据,HashMap实现静态内部类entry,其重要属性key,value,next,从key,value可以很清楚这就是HashMap的基础Bean,Entry[]就是HashMap存储数据的容器,next是实现链表的基础,用来存放相同下标(hash值不同但是分到同一个下标)的对象地址。

HashMap的数据结构可以理解为数组加链表,数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。HashMap是两者的综合特性的产物,寻址容易,插入删除也容易的数据结构——哈希表。哈希表有多种不同的实现方法,我们来看看hashMap的实现方法——拉链法,我们可以理解为链表 的数组,如图:


从上图中可以发现HashMap的数据结构是数组加链表,一个长度为16的数组,每一个数组存储的是一个链表的头节点,一般情况下是通过indexFor方法来确定存储的下标位置,这里我们便于理解使用key.hash%entry[].length的逻辑(key.hash和indexFor方法的实现直接影响hash冲突发生的概率,好的算法可以提高存储和提取元素的效率)。上图采用的是key的hash值%数组的长度,可以看到,41%16=89%16=121%16=9。所以,41,89,121都存放在下标9下。


三、   HashMap的存储实现

看一下hashMap的put方法实现

public V put(K key, V value) {     // 若“key为null”,则将该键值对添加到table[0]中。         if (key == null)             return putForNullKey(value);     // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。         int hash = hash(key.hashCode());     //搜索指定hash值在对应table中的索引     //hash方法和indexFor方法的实现直接影响hash冲突的发生概率。         int i = indexFor(hash, table.length);     // 循环遍历Entry数组,若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!         for (Entry<K,V> e = table[i]; e != null; e = e.next) {              Object k;              if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //如果key相同则覆盖并返回旧值                  V oldValue = e.value;                 e.value = value;                 e.recordAccess(this);                 return oldValue;              }         }     //修改次数+1         modCount++;     //将key-value添加到table[i]处     addEntry(hash, key, value, i);     return null;}//存储,取值逻辑概要<pre>//存储时:<span style="color:#0000ff;">int</span> hash = key.hashCode();<span style="color:#0000ff;">int</span> index = hash %<span style="color:#000000;"> Entry[].length;Entry[index] </span>=<span style="color:#000000;"> value;</span><span style="color:#008000;">  <span style="color:#000000;"> //</span></span>取值时:<span style="color:#0000ff;">int</span> hash =<span style="color:#000000;"> key.hashCode();</span><span style="color:#0000ff;">int</span> index = hash %<span style="color:#000000;"> Entry[].length;</span><span style="color:#0000ff;">return</span> Entry[index];
 

到这里我们可以理解hashMap通过键值的储存和取值的原理了。接下来会有一个问题,如果两个key通过hash%Entry[].length得到的index相同,会不会有覆盖的危险?上图就是答案,hashMap在每个数组元素里加入了一个链表,Entry类中有一个next属性,是实现链表的根本——指针,用来存储下一个entry的地址。就用上图下标9的元素,41,89,121都在下标9下。他们存储的过程是:1.indexFor(121)返回下标9.把121存储到下标92.indexFor(89)返回下标9,把121存放在临时entry中,把89存放在下标9中,并把next指向临时entry 1213.indexFor(41)返回下标9,把89存放在新临时entry中,把41存放在下标9中,并把next指向临时entry 41这样就形成了Entry{key=41, value=41, next=Entry{key=89, value=89, next=Entry{key=121,value=121}}}这样的链式结构,next作为存储下一个节点地址。同时数组下标保存的是最后插入的元素,即链表每次在头部插入新元素。到这里HashMap的实现原理轮廓已经清晰了。

四、   解决hash冲突的办法

  1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
  2. 再哈希法
  3. 链地址法
  4. 建立一个公共溢出区

Java中hashmap的解决办法就是采用的链地址法。

五、  实现自己的HashMap

       自定义entry类

public class Entry<K,V> {//键final K key;//值V value;//链表下一个节点地址Entry<K,V> next;//构造器public Entry(K k,V value,Entry1 next){this.key=k;this.value=value;this.next=next;}public K getKey(){return key;}public V getValue(){return value;}public Entry getNext(){return next;}public V setValue(V newValue){V oldValue=value;value=newValue;return oldValue;}//equals重写,key和value都相等 返回truepublic boolean equals(Object o){if(this==o)return true;if(!(o instanceof Entry)||o==null){return false;}Entry e=(Entry)o;if (e.getKey().equals(key)&&e.getValue().equals(value)){return true;}return false;}public int hashCode(){int result=(getKey()!=null?getKey().hashCode():0)*31;result=result+(getValue()!=null?getValue().hashCode():0)*31;return result;}@Overridepublic String toString() {return "Entry{" +"key=" + key +", value=" + value +", next=" + next +'}';}}


自定义hashMap类:

public class MyHashMap<K,V> {private int capacity;private Entry[] table;public MyHashMap() {capacity=16;table=new Entry[capacity];}public MyHashMap(int capacity){if(capacity<0)                     capacity=16;this.capacity=capacity;table=new Entry[capacity];}public int getSize() {return size;}public int indexFor(int hash,int length){return hash%(length-1);}public V get(Object key){if(key==null)return null;int hash=key.hashCode();int index=indexFor(hash,table.length);Entry<K,V>e=table[index];while(e!=null){Object k=e.getKey();if(k.hashCode()==hash&&k.equals(key)){return e.getValue();}e=e.next;}return  null;}public V put(K key, V value) {if (key == null)return null;int hash = key.hashCode();int index = indexFor(hash, table.length);// 如果添加的key已经存在,那么只需要修改value值即可for (Entry<K, V> e = table[index]; e != null; e = e.next) {Object k = e.key;if (e.key.hashCode() == hash && (k == key || key.equals(k))) {V oldValue = e.value;e.value = value;return oldValue;// 原来的value值}}// 如果key值不存在,那么需要添加Entry<K, V> e = table[index];// 获取当前数组中的etable[index] = new Entry<K, V>(key, value, e);// 新建一个Entry,并将其指向原先的esize++;return v;}public V remove(K key){if(key==null){return null;}int hash=key.hashCode();int index=indexFor(hash,table.length);Entry<K,V> e=table[index];Entry<K,V> oldNext=e;while(e!=null){if(e.getKey().hashCode()==hash&&(e.getKey()==key||e.getKey().equals(key))){oldNext.next=e.next;return e.getValue();}oldNext=e;e=e.next;}return null;}public static void main(String[] args){MyHashMap<Integer,Integer> map=new MyHashMap<Integer, Integer>();map.put(1,3);                map.put(2,13);                map.put(3,23);                map.put(17,33);                map.put(18,43);                map.put(32,53);                System.out.println(map.get(1));                System.out.println(map.get(2));                System.out.println(map.get(3));                System.out.println(map.get(4));                System.out.println(map.get(17));                System.out.println(map.get(18));                System.out.println(map.get(32));}}



希望本文能对你有所帮助


0 0
原创粉丝点击