zoukankan      html  css  js  c++  java
  • HashMap底层原理及方法实现


    数组:

    劣:存储区间连续、占用内存严重、空间复杂度大、增删

    优:时间复杂度小、易查找


    链表:

    劣:时间复杂度大、难查找

    优:存储区间离散、占用内存小、空间复杂度小、易增删


    哈希表:

    结合数组合链表的优点,存储区间离散占用内存小、空间复杂度小易增删、时间复杂度小易查找

    哈希表可以理解为链表的数组,如图:

     

    从图中可知哈希表是链表和数组的组合。上图2为一个长度为16的数组(哈希表长度一般为2的次幂数)。所存储的元素的下标一般由hash(key)%len(数组长度)获取,也即是key.hashCode()与数组的长度len取模获得。哈希表是以桶式存储元素。


    哈希表的基本方法实现:

    package HashMap;
    /**
     * 基本map接口
     * author Gsan
     */
    public interface Map <K,V> {
        //添加元素
        public V put(K k,V v);
    
        //查找元素
        public V get(K k);
    
        //删除元素
        public V remove(K key);
    
        public interface Entry<K,V> {
            //获取键
            public K getKey();
    
            //获取值
            public V getValue();
        }
    }
    哈希表的方法实现通过基本接口Map。

    
    
    package HashMap;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * author Gsan
     */
    public class MyHashMap<K,V> implements Map<K,V> {
    
        //数组的初始化长度
        private int DEFAULT_INITIAL_CAPACITY = 16;
        //阈值比例,负载因子
        private double DEFAULT_LOAD_FACTOR = 0.75;
    
        private int defaultInitSize;
        private double defaultLoadFactor;
    
        //Map当中数组位置的数量
        private int useSize;
    
        //数组
        private Entry<K, V>[] table = null;
    
        //构造方法
    
        public MyHashMap() {
            this(16, 0.75);
        }
    
        public MyHashMap(int defaultInitCapacity, double defaultLoadFactor) {
            if (defaultInitCapacity < 0)
                throw new IllegalArgumentException("illegal initial capacity:" + defaultInitCapacity);
    
            if (defaultLoadFactor <= 0 || Double.isNaN(defaultLoadFactor))
                throw new IllegalArgumentException("illegal load factor:" + defaultLoadFactor);
    
            this.defaultInitSize = defaultInitCapacity;
            this.defaultLoadFactor = defaultLoadFactor;
    
            table = new Entry[this.defaultInitSize];
        }
    }

    数组的处世长度一般为2的次幂数。负载因子方便扩容,当数组达到阈值时(defaultInitSize*defaultLoadFactor)进行扩容。


    //用hashCode取模计算下标
    private int indexFor(K k, int length) {
        return   hash(k.hashCode()) & (length - 1);
    }
    //计算hashCode

    private int hash(int hashCode) {
        hashCode=hashCode^((hashCode>>>20)^(hashCode>>>12));
    return hashCode^((hashCode>>>7)^(hashCode>>>4));
    }

    元素存储的位置下标是通过hash(key)%len计算的,length从0开始所以len=length-1。计算hashCode使用移位来计算hashcode。移位运算参考:http://www.cnblogs.com/hongten/p/hongten_java_yiweiyunsuangfu.html

    https://blog.csdn.net/qq_34364995/article/details/80544465


      @Override
        public V put(K k, V v) {
            if (useSize > defaultInitSize * defaultLoadFactor) {
                //扩容
                up2Size();
            }
            //通过key来计算存储位置下标
            int index = indexFor((Integer) k, table.length);
    
            Entry<K, V> entry = table[index];
            Entry<K, V> newEntry = new Entry<K, V>(k, v, null);
            if (entry == null) {
                table[index] = newEntry;
                useSize++;
            } else {
                Entry<K, V> temp;
                while ((temp = table[index]) != null) {
                    temp = temp.next;
                }
                temp.next = newEntry;
            }
            return newEntry.getValue();
        }

    存储元素,先进行数组容量判断,是否超出阈值进行扩容(调用扩容函数),再调用计算存储下标方法计算下标index,将table[index]赋值第三方entry进行判断是否为null,创建所需存储元素对象newEntry,若entry为null,即位置为空,将newEntry放到table[index]即可,数组位置数量+1。若entry不为null时即该位置有元素,应存储到next至位置为null为止。


     
    //扩容数组
        private void up2Size() {
    
            Entry<K, V>[] newTable = new Entry[defaultInitSize * 2];
            //将原table中的entry重新散列到新的table中
            againHash(newTable);
        }
    
        //将原table中的entry重新散列到新的table中
        private void againHash(Entry<K, V>[] newTable) {
    
            //数组里的对象封装到list中,包括同一位置链表结构
            List<Entry<K, V>> entryList = new ArrayList<>();
            for (int i = 0; i < table.length; i++) {
                if (table[i] == null) {
                    continue;
                }
                findEntryByNext(table[i],entryList);
            }
            if(entryList.size()>0){
                useSize=0;
                defaultInitSize=defaultInitSize*2;
                table=newTable;
                for(Entry<K,V> entry:entryList){
                    if(entry.next!=null){
                        entry.next=null;
                    }
                    put(entry.getKey(),entry.getValue());
                }
            }
        }
    
        private void findEntryByNext(Entry<K,V> entry,List<Entry<K,V>> entryList){
            if(entry!=null && entry.next!=null){
                //这个entry对象以已经形成链表结构
                entryList.add(entry);
                //递归,将链表中的entry实体都一次封装到entryList链表中
                findEntryByNext(entry.next,entryList);
            }else{
                entryList.add(entry);
            }
        }

     数组超出阈值(defaultInitSize*defaultLoadFactor)进行扩容,将旧的数组链表重新散列到新的数组链表,先遍历table放到entryList,再遍历entry将key,value放到新数组的entryList。


     测试:

    package HashMap;
    
    /**
     * author Gsan
     */
    public class HashMapTest {
        public static void main(String[] args){
            Map<String,String> map=new MyHashMap<>();
            for(int i=0;i<5;i++){
                map.put("key"+i,"value"+i);
            }
            for(int i=0;i<5;i++){
                System.out.println("key"+i+",value is:"+map.get("key"+i));
            }
        }
    }

     

    本文参考:https://blog.csdn.net/mark2when/article/details/71434713

                     https://www.cnblogs.com/holyshengjie/p/6500463.html

  • 相关阅读:
    hdu 1496 equations(哈希)
    为什么要微服务化
    什么是分布式系统中的幂等性
    不积跬步无以至千里
    服务治理与微服务
    使用阿里开源工具 TProfiler 在海量业务代码中精确定位性能代码 (jvm性能调优)
    Spring MVC重定向和转发
    两个实体复制
    IntelliJ IDEA类头注释和方法注释
    Linux下单机安装部署kafka及代码实现
  • 原文地址:https://www.cnblogs.com/Gsan/p/10462754.html
Copyright © 2011-2022 走看看