您的位置:首页 > 编程语言

通过github上的项目理解HashMap

2017-01-20 11:14 190 查看
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冲突的办法

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

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));

    }

    

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: