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

猜你喜欢

转载自www.cnblogs.com/Gsan/p/10462754.html