zoukankan      html  css  js  c++  java
  • 动手实现hashmap

    动手实现hashmap

    引入问题

    为什么需要hashmap?

    我已经知道的数据结构:

    • 数组
    • 链表

    数组的优点:

    • 有数组下标,按下标查找快,
    • 由于存储空间连续,插入删除慢
    • 扩容不方便,容易造成空间浪费

    链表的优点

    • 传入链表节点,则插入删除快,O(1)
    • 由于没有下标,必须按顺序遍历,按下标查找慢,O(n)
    • 扩容方便

    我们开始思考:有什么方式既能够具备数组的 快速查询 的优点又能融合链表 方便快捷的增加删除元素 的优势?

    hashmap呼之欲出。

    hashmap原理

    HashMap的底层主要是基于 数组链表 来实现的,它之所以有相当快的查询速度主要是因为它是通过计算 散列码 来决定存储的位置。HashMap中主要是通过key的 hashCode 来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的 hash冲突 。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过 链表 来解决hash冲突的。

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

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

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

    HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。

    测试

    还得用JUnit测试

    (12条消息) IDEA中添加junit4的三种方法(详细步骤操作)_gakki_200的博客-CSDN博客

    Map.Entry接口

        public interface Entry<K, V> {
            K getKey();
    
            V getValue();
    
            V setValue(V var1);
    
            boolean equals(Object var1);
    
            int hashCode();
    
            static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey() {
                return (Comparator)((Serializable)((var0x, var1x) -> {
                    return ((Comparable)var0x.getKey()).compareTo(var1x.getKey());
                }));
            }
    
            static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K, V>> comparingByValue() {
                return (Comparator)((Serializable)((var0x, var1x) -> {
                    return ((Comparable)var0x.getValue()).compareTo(var1x.getValue());
                }));
            }
    
            static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> var0) {
                Objects.requireNonNull(var0);
                return (Comparator)((Serializable)((var1x, var2x) -> {
                    return var0.compare(var1x.getKey(), var2x.getKey());
                }));
            }
    
            static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> var0) {
                Objects.requireNonNull(var0);
                return (Comparator)((Serializable)((var1x, var2x) -> {
                    return var0.compare(var1x.getValue(), var2x.getValue());
                }));
            }
        }
    

    hashmap为什么线程不安全

    JDK1.7 中,由于多线程对HashMap进行扩容,调用了HashMap#transfer(),具体原因:某个线程执行过程中,被挂起,其他线程已经完成数据迁移,等CPU资源释放后被挂起的线程重新执行之前的逻辑,数据已经被改变,造成死循环、数据丢失。

    本人自己实现的resize()方法:

        public void resize(){
            // get every entry in the old table
            Set<Entry<K, V>> entrySet = myEntrySet();
            // update length and threshold
            length*=2;
            threshold= (int) (length*LOAD_FACTOR);
    
            // 建个新表
            // 把键值对项按hash规则挂到合适的地方
            Entry<K,V>[] newTable = new Entry[length];
            for (Entry<K, V> entry:entrySet){
                entry.next=null;
                int index = indexFor(entry.key.hashCode());
                if (newTable[index]==null){
                    newTable[index] = entry;
                }else {
                    Entry<K,V> temp = newTable[index];
                    while (temp.next!=null){
                        temp=temp.next;
                    }
                    temp.next=entry;
                }
            }
            // 更新 table
            table = newTable;
            newTable = null;
        }
    
    

    hashmap hashtable concurrent hashmap 的关系

    面试必备:HashMap、Hashtable、ConcurrentHashMap的原理与区别 - 猿人谷 - 博客园 (cnblogs.com)

    联系:

    都是 Map 接口的实现

    都采用了 链表+数组 的实现方式

    区别:

    从线程安全角度

    hashmap 线程不安全,没有锁机制

    其他两个都是线程安全的

    从键的角度

    hashmap可以存储null键

    其他两个都不可以

  • 相关阅读:
    2016 多校赛3 A 水 B 期望,规律 C 各种博弈 J 物理题,积分 K 暴力,水
    2016 多校赛4 A dp,KMP E(待补) F(待补) J LIS变形,套路 K 水 L 水
    2016-12-27 spoj MINSUB 二分,单调栈 spoj INTSUB 思维
    sqlmap用户手册
    用PROXYCHAINS实现SSH全局代理
    linux之sort用法
    PHP 变量定义及使用
    查漏补缺
    总结2
    最近
  • 原文地址:https://www.cnblogs.com/studentWangqy/p/15302842.html
Copyright © 2011-2022 走看看