HashMap深度解析

    xiaoxiao2022-06-22  22

    <span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">hashmap是一种以键值对存储的数据容器,根据key的hashcode值决定存储位置</span>

    1、内部存储对象是Entry<Key,Value>;

    2、内部的每个对象都会生产一个hashcode值,在存储Entry<Key,Value>时根据Key的hashcode以一定的映射关系存储;

    3、获取数据时,根据Key的hashcode和对应的映射关系,直接定位数据的位置。

    HashMap内部维护的是数组+链表的结构来存储Entry<Key,Value>。数组的形式是Entry[] table数组,初始化HashMap的时候数组的长度默认为16,Entry[] table 数组的每个元素要么为null要么是由Entry<Key,Value>组成的链表。HashMap中的Entry<Key,Value>的个数为HashMap的大小(size)。

    下面介绍几个概念:

    阀值(threshold)=容量(capacity)*加载因子(load factor) 容量(capacity):是指HashMap内部Entry[] table线性数组的长度 加载因子(load factor):默认为0.75 阀值(threshold):当HashMap大小超过了阀值,HashMap将扩充2倍,并且rehash。

    两个重要的方法:put/get

    put()方法-向HashMap存储键值对<Key,Value>

    a.  获取这个Key的hashcode值,根据此值确定应该将这一对键值对存放在哪一个桶中,即确定要存放桶的索引;

    b.  遍历所在桶中的Entry<Key,Value>链表,查找其中是否已经有了以Key值为Key存储的Entry<Key,Value>对象,

    c1. 若已存在,定位到对应的Entry<Key,Value>,其中的Value值更新为新的Value值;返回旧值;

    c2. 若不存在,则根据键值对<Key,Value> 创建一个新的Entry<Key,Value>对象,然后添加到这个桶的Entry<Key,Value>链表的头部。

    d.  当前的HashMap的大小(即Entry<key,Value>节点的数目)是否超过了阀值,若超过了阀值(threshold),则增大HashMap的容量(即Entry[] table 的大小),并且重新组织内部各个Entry<Key,Value>排列。

    /** * 将<Key,Value>键值对存到HashMap中,如果Key在HashMap中已经存在,那么最终返回被替换掉的Value值。 * Key 和Value允许为空 */ public V put(K key, V value) { //1.如果key为null,那么将此value放置到table[0],即第一个桶中 if (key == null) return putForNullKey(value); //2.重新计算hashcode值, int hash = hash(key.hashCode()); //3.计算当前hashcode值应当被分配到哪一个桶中,获取桶的索引 int i = indexFor(hash, table.length); //4.循环遍历该桶中的Entry列表 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //5. 查找Entry<Key,Value>链表中是否已经有了以Key值为Key存储的Entry<Key,Value>对象, //已经存在,则将Value值覆盖到对应的Entry<Key,Value>对象节点上 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//请读者注意这个判定条件,非常重要!!! V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; //6不存在,则根据键值对<Key,Value> 创建一个新的Entry<Key,Value>对象,然后添加到这个桶的Entry<Key,Value>链表的头部。 addEntry(hash, key, value, i); return null; } /** * Key 为null,则将Entry<null,Value>放置到第一桶table[0]中 */ private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); return null; }get方法:

    a.  获取这个Key的hashcode值,根据此hashcode值决定应该从哪一个桶中查找;

    b.  遍历所在桶中的Entry<Key,Value>链表,查找其中是否已经有了以Key值为Key存储的Entry<Key,Value>对象;

    c1. 若已存在,定位到对应的Entry<Key,Value>,返回value;

    c2. 若不存在,返回null

    ** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * 返回key对应的Value值,如果HashMap中没有,则返回null; * 支持Key为null情况 * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * key.equals(k))}, then this method returns {@code v}; otherwise * it returns {@code null}. (There can be at most one such mapping.) * * <p>A return value of {@code null} does not <i>necessarily</i> * indicate that the map contains no mapping for the key; it's also * possible that the map explicitly maps the key to {@code null}. * The {@link #containsKey containsKey} operation may be used to * distinguish these two cases. * * @see #put(Object, Object) */ public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); //遍历列表 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }Hashmap中删除节点:

    1. 根据Key的hashcode 值和Key定位到Entry<key,Value> 对象在HashMap中的位置;

    2. 由于Entry<Key,Value>是一个链表元素,之后便是链表删除节点的操作了;

    转载请注明原文地址: https://ju.6miu.com/read-1122956.html

    最新回复(0)