HashMap

HashMap 的特殊存储结构使得在获取指定元素前需要经过哈希运算,得到目标元素在哈希表中的位置,然后再进行少量比较即可得到元素,这使得 HashMap 的查找效率很高。

Hash

  • Hash函数是指把一个大范围一一映射到一个小范围,在jdk中是将一个任意长度的二进制值通过映射关系转换成固定长度的二进制值。
  • Hash函数有如下特点:简单和均匀。
    • 简单指散列函数的计算简单快速;
    • 均匀指对于关键字集合中的任一关键字,散列函数能以等概率将其映射到表空间的任何一个位置上。

final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
  • key.hashCode()函数调用的是jdk自带的nature函数,返回int型散列值。
  • 2进制32位带符号的int表值范围从-2147483648到2147483648。前后加起来大概40亿的映射空间。
  • 这么长的数组在内存中是放不下的,所以对该散列值取模。
static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }

以HashMap初始长度16为例,length-1=15

h       :10101010 01010101 10101010
length-1:00000000 00000000 00001111
-------------------------------------
index   :00000000 00000000 00001010

即保留末四位,取得index的值:10,这也解释了源码中length must be a non-zero power of 2(长度必须为2的幂),长度减一刚好可作为index的掩码。

Entry

static class Entry<K,V> implements Map.Entry<K,V>{
     final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        ...
}
  • 封装具体的key和value

HashTable

  • 实际上就是一个数组,里面存放着Entry
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
  • 数组初始大小16
/**
 * The default initial capacity - MUST be a power of two.
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  • 通过一个key的输入,经由hash函数,找到数组中key唯一映射的value,得到数组下标;
  • 最重要的特点:存储效率很高,取数据的时间复杂度o(1),线性表的时间复杂度o(n);

  • 扩容

//threshold即阈值,下次需要扩容时的值,等于 容量*负载因子。  
//当前数组大小大于等于这个值时,就resize(扩容)。
if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
}
void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    //根据新长度创建Entry[]数组
    Entry[] newTable = new Entry[newCapacity];
    //将旧Entry[]的内容复制到newTable
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    //改变引用地址到newTable
    table = newTable;
    //重新计算阈值
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

Hash冲突

HashMap使用链表解决hash冲突。
这里写图片描述
**当hash值和key值相同时,更新entry中的值;
当hash值相同,key值不同则产生hash冲突,解决方法如下,将hash冲突的值形成单向链表,新的entry加入到链表头部,旧值放末尾。**

void createEntry(int hash, K key, V value, int bucketIndex) {
    //暂存旧Entry
    Entry<K,V> e = table[bucketIndex];
    //将旧Entry加到新Entry的next,并把新Entry放到table[bucketIndex]
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

(实战)精简版HashMap

  • 接口定义
public interface MapBd<K, V> {

    V put(K key, V value);

    V get(K key);

    int size();

    interface Entry<K, V> {
        K getKey();

        V getValue();
    }
}
  • 精简版实现
public class HashMapBd<K, V> implements MapBd<K, V> {
    //hashTable初始化长度,*****这个数组的长度是2的倍数******
    private static Integer defaultLength = 16;
    //负载因子,即数组长度的警戒线
    private static Double defaultLoad = 0.75;
    //hashTable
    private Entry<K, V>[] hashTable = null;
    //记录数组的元素个数
    private int size = 0;

    public HashMapBd() {
        this(defaultLength, defaultLoad);
    }

    public HashMapBd(Integer defaultLength, Double defaultLoad) {
        this.defaultLength = defaultLength;
        this.defaultLoad = defaultLoad;
        this.hashTable = new Entry[defaultLength];
    }

    /**
     * 将元素存入hashmap
     * 如果存在相同的hashcode,那么他们确定的索引位置就相同,这时判断他们的key是否相同,如果不相同,这时就是产生了hash冲突。
     * Hash冲突后,那么HashMap的单个bucket里存储的不是一个 Entry,而是一个 Entry 链。
     *
     * @param key
     * @param value
     * @return
     */
    public V put(K key, V value) {
        //根据Key计算hash值
        int hash = hash(key);
        //计算下标
        int index = indexFor(hash, hashTable.length);
        Entry<K, V> entry = hashTable[index];

        //该下标没有数据
        if (entry == null) {
            hashTable[index] = new Entry<K, V>(key, value, null, hash);
            size++;
        }
        //该下标有数据,创建链表
        else {
            //判断当前确定的索引位置是否存在相同hashcode和相同key的元素,如果存在相同的hashcode和相同的key的元素,那么新值覆盖原来的旧值,并返回旧值。
            if (entry.hash == hash && key.equals(key)) {
                V oldValue = entry.value;
                entry.value = value;
                return oldValue;
            }
            //如果存在相同的hashcode,那么他们确定的索引位置就相同,这时判断他们的key是否相同,如果不相同,这时就是产生了hash冲突。
            //Hash冲突后,就存储一个 Entry 链。
            //创建新Entry,并把旧的Entry放到新Entry的next
            Entry<K, V> newEntry = new Entry<K, V>(key, value, entry, hash);
            //把新Entry放入到hashTable中
            hashTable[index] = newEntry;
        }
        return null;
    }

    /**
     * 从hashmap取值
     *
     * @param key
     * @return
     */
    public V get(K key) {
        //根据Key计算hash值
        int hash = hash(key);
        //计算下标
        int index = indexFor(hash, hashTable.length);
        Entry<K, V> entry = hashTable[index];
        //遍历冲突部分的entry
        while (entry != null) {
            if (entry.hash == hash && key.equals(key)) {
                return entry.getValue();
            }
            entry = entry.next;
        }
        return null;
    }

    public int size() {
        return size;
    }

    class Entry<K, V> implements MapBd.Entry<K, V> {
        K key;
        V value;
        Entry<K, V> next;
        int hash;

        Entry(K key, V value, Entry<K, V> next, int hash) {
            this.key = key;
            this.value = value;
            this.next = next;
            this.hash = hash;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }
    }

    final int hash(Object k) {
        int h = 0;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        //length必须是2的倍数
        return h & (length - 1);
    }
}
  • 测试
    @Test
    public void testHashMapBd() {
        MapBd<String, String> myHashMap = new HashMapBd<String, String>();
        for (int i =0 ;i<32;i++) {
            myHashMap.put("key"+i, "value"+i);
        }

        for (int i =0 ;i<32;i++) {
            System.out.println("{key"+i+":"+myHashMap.get("key"+i)+"}");
        }
    }
  • 执行结果
{key0:value0}
{key1:value1}
{key2:value2}
{key3:value3}
{key4:value4}
{key5:value5}
...
{key27:value27}
{key28:value28}
{key29:value29}
{key30:value30}
{key31:value31}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值