通过github上的项目理解HashMap

github上有个开源项目对于理解hashMap很有帮助

github地址:

https://github.com/dn-jack/dn-jack-hashMap

参考文献

Hashmap实现原理


HashMap的数据结构


数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为链表的数组 ,如图:



从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

  HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。

  1.首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

HashMap的存取实现

     既然是线性数组,为什么能随机存取?这里HashMap用了一个小算法,大致是这样实现:

//存储时:
int hash = key.hashCode();// 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;

//取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

到这里我们轻松的理解了HashMap通过键值对实现存取的基本原理

    3.疑问:如果两个key通过hash%Entry[].length得到的index相同,会不会有覆盖的危险?

  这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现,我们应该已经清楚了。

  当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因素(也称为因子),随着map的size越来越大,Entry[]会以一定的规则加长长度。

3.解决hash冲突的办法

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

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

github中dn-jack-hashMap这个解决办法是再哈希


查看源码步骤:

1、先看其接口定义DNMap.java

2、再看其对接口的实现DNHashMap.java(类似实现java jdk中的hashmap)

3、最后看其测试代码Test.java


//=========下面先把代码复制上了,有空时补上对其原理的理解


===DNMap.java

package com.dongnao.jack;


public interface DNMap<K, V> {
    
    public V put(K k, V v);
    
    public V get(K k);
    
    public int size();
    
    public interface Entry<K, V> {
        public K getKey();
        
        public V getValue();
    }
}


===DNHashMap.java(类似实现java jdk中的hashmap)



package com.dongnao.jack;


import java.util.ArrayList;
import java.util.List;


public class DNHashMap<K, V> implements DNMap<K, V> {
    
    private static int defaultLength = 16;  //默认数组长度
    
    private static double defaultLoader = 0.75;  //扩张因子,也就是当组数被使用超过0.75%时,该类会自动去扩充数组长度*2
    
    private Entry<K, V>[] table = null;  //存储的hash表数组<主键,值>
    
    private int size = 0;  //已经存放到数组的对象个数
    
    public DNHashMap(int length, double loader) {
        defaultLength = length;
        defaultLoader = loader;
        
        table = new Entry[defaultLength];
    }
    
    public DNHashMap() {
        this(defaultLength, defaultLoader);
    }
    
    public V put(K k, V v) {
        
        //在这里要判断一下,size是否达到了一个扩容的一个标准
        if (size >= defaultLength * defaultLoader) {
            up2size();  //调用宽容申请*2的空间
        }
        
        //1、   创建一个hash函数,根据key和hash函数算出数组下标
        int index = getIndex(k);
        
        Entry<K, V> entry = table[index];
        
        if (entry == null) {
            //如果entry为null,说明table的index位置上没有元素
            table[index] = newEntry(k, v, null);
            size++;
        }
        else {
            //如果index位置不为空,说明index位置有元素,那么就要进行一个替换,然后next指针指向老数据
            table[index] = newEntry(k, v, entry);
        }
        return table[index].getValue();
    }
    
    private void up2size() {
        Entry<K, V>[] newTable = new Entry[2 * defaultLength];
        
        //新创建数组以后,以前老数组里面的元素要对新数组进行再散列
        againHash(newTable);
    }
    
    //新创建数组以后,以前老数组里面的元素要对新数组进行再散列
    private void againHash(Entry<K, V>[] newTable) {
        
        List<Entry<K, V>> list = new ArrayList<Entry<K, V>>();
        
        for (int i = 0; i < table.length; i++) {
            if (table[i] == null) {
                continue;
            }
            findEntryByNext(table[i], list);
        }
        
        if (list.size() > 0) {
            //要进行一个新数组的再散列
            size = 0;
            defaultLength = defaultLength * 2;
            table = newTable;
            
            for (Entry<K, V> entry : list) {
                if (entry.next != null) {
                    entry.next = null;
                }
                put(entry.getKey(), entry.getValue());
            }
        }
    }
    
    private void findEntryByNext(Entry<K, V> entry, List<Entry<K, V>> list) {
        
        if (entry != null && entry.next != null) {
            list.add(entry);
            findEntryByNext(entry.next, list);
        }
        else {
            list.add(entry);
        }
    }
    
    private Entry<K, V> newEntry(K k, V v, Entry<K, V> next) {
        return new Entry(k, v, next);
    }
    
    private int getIndex(K k) {
        
        int m = defaultLength;
        
        int index = k.hashCode() % m;
        
        return index >= 0 ? index : -index;
    }
    
    public V get(K k) {
        
        //1、   创建一个hash函数,根据key和hash函数算出数组下标
        int index = getIndex(k);
        
        if (table[index] == null) {
            return null;
        }
        
        return findValueByEqualKey(k, table[index]);
    }
    
    public V findValueByEqualKey(K k, Entry<K, V> entry) {
        
        if (k == entry.getKey() || k.equals(entry.getKey())) {
            return entry.getValue();
        }
        else {
            if (entry.next != null) {
                return findValueByEqualKey(k, entry.next);
            }
        }
        
        return null;
    }
    
    public int size() {
        return size;
    }
    
    class Entry<K, V> implements DNMap.Entry<K, V> {
        
        K k;
        
        V v;
        
        Entry<K, V> next;
        
        public Entry(K k, V v, Entry<K, V> next) {
            this.k = k;
            this.v = v;
            this.next = next;
        }
        
        public K getKey() {
            return k;
        }
        
        public V getValue() {
            return v;
        }
        
    }

}


===Test.java

package com.dongnao.jack;


public class Test {
    
    public static void main(String[] args) {
        DNMap<String, String> dnmap = new DNHashMap<String, String>();
        
        Long t1 = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            dnmap.put("key" + i, "value" + i);
        }
        
        for (int i = 0; i < 10000; i++) {
            System.out.println("key: " + "key" + i + "  value:"
                    + dnmap.get("key" + i));
        }
        Long t2 = System.currentTimeMillis();
        //        System.out.println("jack写的dnhashMap耗时:" + (t2 - t1));
        //        System.out.println("-----------------------HashMap--------------------------");
        //        
        //        Map<String, String> map = new HashMap<String, String>();
        //        Long t3 = System.currentTimeMillis();
        //        for (int i = 0; i < 1000; i++) {
        //            map.put("key" + i, "value" + i);
        //        }
        //        
        //        for (int i = 0; i < 1000; i++) {
        //            System.out.println("key: " + "key" + i + "  value:"
        //                    + map.get("key" + i));
        //        }
        //        Long t4 = System.currentTimeMillis();
        //        System.out.println("jdk的hashMap耗时:" + (t4 - t3));
    }
    
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牵手生活

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值