美文网首页
数据结构与算法之哈希表(二)HashMap简单手写实现

数据结构与算法之哈希表(二)HashMap简单手写实现

作者: kakaxicm | 来源:发表于2018-06-11 10:12 被阅读0次

    引言

    上一篇博客学习了HashMap的源码,了解了hash函数设计原理和基本的增删改查及扩容实现,今天我们依葫芦画瓢,基于数组和单链表尝试手写简单的HashMap,实现它的put\get\remove\resize操作。

    Map接口

    包含了基本的操作:put\get\remove\size方法:

    package hash;
    
    /**
     * Created by chenming on 2018/6/10
     * 基本的map接口
     */
    public interface Map<K, V> {
        /**
         * 大小
         * @return
         */
        int size();
    
        /**
         * 添加元素
         * @param key
         * @param object
         */
        void put(K key, V object);
    
        /**
         * 查找元素
         * @param key
         * @return
         */
        V get(K key);
    
        /**
         * 删除元素
         * @param key
         * @return
         */
        V remove(K key);
    }
    

    节点Node定义

    节点为单链表节点,思路和JDK一样

    package hash;
    
    /**
     * Created by chenming on 2018/6/10
     */
    public class Node<K, V> {
        private int hash;//桶中的hash值,与key不一样
        private K key;
        private V value;
        public Node next;
    
        public Node(int hash, K key, V value) {
            this.hash = hash;
            this.key = key;
            this.value = value;
        }
    
        public final K getKey() {
            return key;
        }
    
        public final V getValue() {
            return value;
        }
    
        public final String toString() {
            return key + "=" + value;
        }
    
        public int getHash() {
            return hash;
        }
    
        public void setHash(int hash) {
            this.hash = hash;
        }
    
        public void setKey(K key) {
            this.key = key;
        }
    
        public void setValue(V value) {
            this.value = value;
        }
    }
    

    hash函数

    参考JDK1.8,具体设计思想参考上篇的源码分析,这里不在赘述

       /**
         * 对key重新hash
         *
         * @param key
         * @return
         */
        private int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
    
       /**
         * hash函数,hash到索引的映射
         *
         * @param hash
         * @param len
         * @return
         */
        private int indexFromHash(int hash, int len) {
            int index = (len - 1) & hash;
            return index;
        }
    

    判断key命中

    前提:
    1.hashcode相等;
    2.key对象相同。

       /**
         * 判断key是否命中
         *
         * @param key
         * @param n
         * @return
         */
        private boolean isNodeHit(K key, Node n) {
            int hash = hash(key);
            if (n.getHash() == hash) {
                if (key == n.getKey() || (key != null) && (key.equals(n.getKey()))) {
                    return true;
                }
            }
            return false;
        }
    

    put方法

       /**
         * put操作
         * @param key
         * @param value
         */
        @Override
        public void put(K key, V value) {
            int hash = hash(key);
            int index = indexFromHash(hash, table.length);
            Node first = table[index];
            if (first == null) {//空坑直接占上
                first = new Node(hash, key, value);
                table[index] = first;
            } else {
                Node p = first;
                while (p.next != null) {
                    if (isNodeHit(key, p)) {//链表中存在hit的node
                        //替换value,直接反馈
                        p.setValue(value);
                        return;
                    }
                    p = p.next;
                }
                p.next = new Node(hash, key, value);
            }
            if (++size > threhold) {
                resize();//扩容
            }
            return;
        }
    

    程序结构和JDK1.8有出入,思路是一样的.

    get方法

       /**
         * 获取方法
         * @param key
         * @return
         */
        @Override
        public V get(K key) {
            int index = indexFromHash(hash(key), table.length);
            Node<K, V> first = table[index];
            if (first == null) {
                return null;
            } else {
                Node<K, V> p = first;
                while (p != null) {
                    if (isNodeHit(key, p)) {//如果命中则返回节点值
                        return p.getValue();
                    }
                    p = p.next;
                }
            }
            return null;
        }
    

    remove方法

        /**
         * 删除元素
         * @param key
         * @return
         */
        @Override
        public V remove(K key) {
            int index = indexFromHash(hash(key), table.length);
            Node<K, V> first = table[index];
            V oldVal = null;
            if (first != null) {
                if (first.next == null) {//桶里面只有一个元素,直接删除
                    oldVal = first.getValue();
                    //直接删除
                    table[index] = null;
                } else {
                    if (isNodeHit(key, first)) {//头结点命中,则删除头结点
                        //直接删除头结点
                        table[index] = first.next;
                    } else {
                        //开始遍历,查找key命中的节点
                        Node<K, V> pre = first;
                        Node<K, V> p = pre.next;
                        while (p != null) {
                            if (isNodeHit(key, p)) {//找到命中节点
                                oldVal = p.getValue();
                                pre.next = p.next;//删除操作
                                p.next = null;
                                break;
                            }
                            pre = p;
                            p = p.next;
                        }
    
                        if (p == null) {//没找到命中节点,直接返回
                            return null;
                        }
                    }
    
                }
            }
            size--;
            return oldVal;
        }
    

    思路:如果对应位置只有一个元素,则直接删除,否则遍历单链表,删除节点。

    扩容操作

     /**
         * 扩容操作
         */
        private void resize() {
            int oldCap = table.length;
            int newCap = oldCap << 1;
            threhold = (int) (newCap * DEFAULT_LOAD_FACTOR);
            Node<K, V>[] newTable = new Node[newCap];
            for (int i = 0; i < oldCap; i++) {
                Node<K, V> first = table[i];
    
                if (first != null) {
                    int hash = first.getHash();
                    if (first.next == null) {
                        newTable[indexFromHash(hash, newCap)] = first;
                    } else {//拆分单链表成两个部分,一个挂载低索引,另外一个挂载高索引
                        Node<K, V> next;//保存旧链表的后继节点
                        //两个单链表的首尾指针
                        Node<K, V> loHead = null, loTail = null;
                        Node<K, V> hiHead = null, hiTail = null;
                        Node<K, V> p = first;
                        do {
                            next = p.next;
                            if ((oldCap & p.getHash()) == 0) {//低位索引
                                if (loTail == null) {
                                    loHead = p;
                                } else {
                                    loTail.next = p;
                                }
                                loTail = p;
                            } else {//高位索引
                                if (hiTail == null) {
                                    hiHead = p;
                                } else {
                                    hiTail.next = p;
                                }
                                hiTail = p;
                            }
                        } while ((p = next) != null);
    
                        if (loHead != null) {
                            loTail.next = null;
                            newTable[i] = loHead;
                        }
    
                        if (hiHead != null) {
                            hiTail.next = null;
                            newTable[i + oldCap] = hiHead;
                        }
                    }
                }
            }
            table = newTable;//替换旧数组
        }
    

    单链表的拆分操作和设计思想和jdk一样,具体分析看上篇文章。
    最后献上测试代码:

       /**
         * 测试哈希基本操作
         */
        @Test
        public void testHashMap() {
            HashA a = new HashA("ABC");
            HashB b = new HashB("ABC");
            //HashMap的key可以为空
            hash.HashMap<Object, String> hashMap = new hash.HashMap<>();
            hashMap.put(null, "90");
            System.out.println(hashMap.get(null));
    
            hash.HashMap<Object, String> myHashMap = new hash.HashMap<>();
            hash.HashMap<Object, String> myHashCrashMp = new hash.HashMap<>();
            System.out.println("=====哈希冲突测试====");
            myHashCrashMp.put(a, "a");
            myHashCrashMp.put(b, "b");
            System.out.println(myHashCrashMp.get(a));
            System.out.println(myHashCrashMp.get(b));
            List<HashA> keys = new LinkedList<>();
            for (int i = 0; i < 13; i++) {
                HashA hashObj = new HashA("ABC" + i);
                keys.add(hashObj);
                myHashMap.put(hashObj, "" + i);
            }
            System.out.println("=====哈希删除测试====");
            int delIndex = 4;
            myHashMap.remove(keys.get(delIndex));
            keys.remove(delIndex);
            System.out.println("=====哈希大小====");
            System.out.println(myHashMap.size());
            System.out.println("=====哈希打印====");
            for (int i = 0; i < keys.size(); i++) {
                HashA hashObj = keys.get(i);
                String v = myHashMap.get(hashObj);
                System.out.println(v);
            }
        }
    

    完整代码地址:数据结构与算法学习JAVA描述GayHub地址

    相关文章

      网友评论

          本文标题:数据结构与算法之哈希表(二)HashMap简单手写实现

          本文链接:https://www.haomeiwen.com/subject/byuceftx.html