zoukankan      html  css  js  c++  java
  • 手撸HashMap实现

    前言

    HashMap是Java中常用的集合,而且HashMap的一些思想,对于我们平时解决业务上的一些问题,在思路上有帮助,基于此,本篇博客将分析HashMap底层设计思想,并手写一个迷你版的HashMap!

    对HashMap的思考

    第一,如图所示,HashMap有3个要素:hash函数+数组+单链表

    第二,对于hash函数而言,需要考虑些什么?

    要快,对于给定的Key,要能够快速计算出在数组中的index。那么什么运算够快呢?显然是位运算!

    要均匀分布,要较少碰撞。说白了,我们希望通过hash函数,让数据均匀分布在数组中,不希望大量数据发生碰撞,导致链表过长。那么怎么办到呢?也是利用位运算,通过对数据的二进制的位进行移动,让hash函数得到的数据散列开来,从而减低了碰撞的概率。

    如果发生了碰撞怎么办?上面的图其实已经说明了JDK的HashMap是如何处理hash冲突的,就是通过单链表解决的。那么除了这个方法,还有其他思路么?比如说,如果发生冲突,那么记下这个冲突的位置为index,然后在加上固定步长,即index+step,找到这个位置,看一下是否仍然冲突,如果继续冲突,那么按照这个思路,继续加上固定步长。其实这就是所谓的线性探测来解决Hash冲突的方法!

    通过写一个迷你版的HashMap来深刻理解

    1.定义接口

    package hashMapTest;
    
    /**
     * @ClassName: MyMap
     * @Description: 自定义Map接口,对外暴露快速存取的方法
     * @author Kingram
     * @param <V>
     * @param <K>
     * @date 2018年8月3日
     *
     */
    public interface MyMap<K,V> {
        
        public V put(K k,V v);
        public V get(K k);
        
        interface Entry<K,V> {
            public K getKey();
            public V getValue();
        }
    }

    2.接口的实现

    package hashMapTest;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @ClassName: MyHashMap
     * @Description: 自定义HashMap
     * @author Kingram
     * @date 2018年8月3日
     *
     */
    public class MyHashMap<K, V> implements MyMap<K, V> {
    
        // 数组的默认初始化长度
        private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
        // 设置默认加载因子
        private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
        private int defaultInitSize;
        private float deaultLoadFactor;
    
        // Map当中entry的数量
        private int entryUseSize;
    
        // 数组
        private Entry<K, V>[] table = null;
    
        // 构造方法,这里使用到了"门面模式".
        // 这里的2个构造方法其实指向的是同一个,但是对外却暴露了两个"门面".
        public MyHashMap() {
            this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
        }
    
        public MyHashMap(int defaultInitialCapacity, float deaultLoadFactor) {
            if (defaultInitialCapacity < 0) {
                throw new IllegalArgumentException("参数有误" + defaultInitialCapacity);
            }
    
            if (deaultLoadFactor <= 0 || Float.isNaN(deaultLoadFactor)) {
                throw new IllegalArgumentException("参数有误" + deaultLoadFactor);
            }
    
            this.defaultInitSize = defaultInitialCapacity;
            this.deaultLoadFactor = deaultLoadFactor;
    
            table = new Entry[this.defaultInitSize];
        }
    
        /**
         * 
         * @ClassName: Entry
         * @Description: HashMap的内部类,HashMap的要素之一,单链表的体现就在这里!
         * @author Kingram
         * @date 2018年8月3日
         *
         * @param <K>
         * @param <V>
         */
        class Entry<K, V> implements MyMap.Entry<K, V> {
    
            private K key;
            private V value;
            private Entry<K, V> next;
    
            public Entry() {
            }
    
            public Entry(K key, V value, Entry<K, V> next) {
                this.key = key;
                this.value = value;
                this.next = next;
            }
    
            @Override
            public K getKey() {
                return key;
            }
    
            @Override
            public V getValue() {
                return value;
            }
        }
    
        /**
         * 第一,要考虑是否扩容?
         * 
         * HashMap中的Entry的数量(数组以及单链表中的所有Entry)是否达到加载因子限制的最大值?
         * 
         * 第二,如果扩容,意味着新生成一个Entry[],不仅如此还得重新散列。
         * 
         * 第三,要根据Key计算出在Entry[]中的位置,定位后,如果Entry[]中的元素为null,那么可以放入其中,如果不为空,那么得遍历单链表,
         * 要么更新value,要么形成一个新的Entry“挤压”单链表!
         */
        @Override
        public V put(K k, V v) {
            V oldValue = null;
            // 判断是否需要扩容?
            // 扩容完毕 re肯定需要重新散列
            if (entryUseSize >= defaultInitSize * deaultLoadFactor) {
                resize(2 * defaultInitSize);
            }
            // 得到HASH值,计算出数组的索引
            int index = hash(k) & (defaultInitSize - 1);
            if (table[index] == null) {// 判断当前索引位置有没有元素,如果没有直接插入
                table[index] = new Entry<K, V>(k, v, null);
                ++entryUseSize;
            } else {// 如果有元素就需要遍历链表
                // 遍历链表
                Entry<K, V> entry = table[index];
                Entry<K, V> e = entry;
                while (e != null) {
                    if (k == e.getKey() || k.equals(e.getKey())) {
                        oldValue = e.value;
                        e.value = v;
                        return oldValue;
                    }
                    e = e.next;
                }
                table[index] = new Entry<K, V>(k, v, entry);
                ++entryUseSize;
            }
            return oldValue;
        }
        
        /**
         * 
         * @Title: hash
         * @Description: hash函数,跟据Key计算出索引
         * @param @param k
         * @param @return 参数
         * @return int 返回类型
         * @throws
         */
        private int hash(K k) {
            int hashCode = k.hashCode();
            hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12);
            return hashCode ^ (hashCode >>> 7) ^ (hashCode >>>4);
        }
        
        private void resize(int i) {
            Entry[] newTable = new Entry[i];
            // 改变了数组的大小
            defaultInitSize = i;
            entryUseSize = 0;
            rehash(newTable);
        }
        
        private void rehash(Entry<K,V>[] newTable) {
            // 得到原来老的Entry集合 注意遍历单链表
            List<Entry<K,V>> entryList = new ArrayList<Entry<K,V>>();
            for(Entry<K,V> entry : table) {
                if(entry != null) {
                    do{
                        entryList.add(entry);
                        entry = entry.next;
                    }while(entry != null);
                }
            }
            
            // 覆盖旧的引用
            if(newTable.length > 0) {
                table = newTable;
            }
            
            // 所谓的重新HASH就是重新PUT ENTRY到HASHMAP
            for(Entry<K,V> entry : entryList) {
                put(entry.getKey(),entry.getValue());
            }
        }
        
        @Override
        public V get(K k) {
            int index = hash(k) & (defaultInitSize -1);
            if(table[index] == null) {
                return null;
            } else {
                Entry<K,V> entry = table[index];
                do{
                    if(k == entry.getKey() || k.equals(entry.getKey())) {
                        return entry.value;
                    }
                    entry = entry.next;
                }while(entry != null);
            }
            return null;
        }
    
    }

    3.测试

    package hashMapTest;
    
    /**
     * @ClassName: HashMapTest
     * @Description: 测试模拟HashMap的实现用例
     * @author Kingram
     * @date 2018年8月3日
     *
     */
    public class HashMapTest {
         public static void main(String[] args) {
            MyMap<String,String> myMap = new MyHashMap<String,String>();
            for(int i =0;i < 500;i++) {
                myMap.put("key"+i, "value"+i);
            }
            
            for(int i = 0;i < 500;i++) {
                System.out.println("key"+i+",value is:"+myMap.get("key"+i));
            }
        }
    }

    4.输出...嗯...亲测可用...

  • 相关阅读:
    Unity 项目中委托Delegate用法案例
    如何让jpa 持久化时不校验指定字段
    Unity 3D光源-Spot Light聚光灯用法详解、模拟手电筒、台灯等线性教程
    unity 实现调用Windows窗口/对话框交互
    Unity 3D光源-Point Light点光源详解/灯泡、模拟灯光效果教程
    unity ugui Toggle Group详解(Chinar出品、简单易懂)
    Unity 3D UGUI Toggle用法教程
    Unity3D UGUI下拉菜单/Dropdown组件用法、总结
    CI/CD学习一
    智慧校园整体架构
  • 原文地址:https://www.cnblogs.com/Kingram/p/9412291.html
Copyright © 2011-2022 走看看