HashMap的源码解读

目录

    • 一、介绍
    • 二、用途
    • 三、源码
      • 1、常量
      • 2、字段
      • 3、方法
        • 3.1 新建hashMap方法
        • 3.2 get方法
        • 3.3 put方法
        • 3.4 resize方法
        • 3.5 remove方法
        • 3.6 其他方法
    • 四、实现一个简单的HashMap

一、介绍

简单的来说HashMap就是Java中的一种数据结构,用于存储键值对

我们也可以通过HashMap的备注来看看具体的描述:
HashMap的源码解读_第1张图片

翻译过来就是
1、HashMap基于哈希表的Map接口实现。 此实现提供所有可选映射操作,并允许key和value为null值。(HashMap类大致等同于Hashtable,只是它不同步并允许空值。 这个类不保证map的顺序,特别是它不保证顺序随时间保持不变

2、这个实现为基本操作(get和put)提供了恒定的时间性能,假设哈希函数将元素正确地分散在桶中。 对集合视图的迭代需要与HashMap实例的"容量"(存储桶的数量)加上其大小(键值映射的数量)成比例的时间。 因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载因子设置得太低)。

3、HashMap的实例有两个影响其性能的参数:初始容量和负载因子。 容量是哈希表中存储桶的数量,初始容量只是创建哈希表时的容量。 负载因子是哈希表在容量自动增加之前允许获得多满的度量。 当散列表中的条目数超过负载因子与当前容量的乘积时,散列表被重新散列(即重建内部数据结构),使得散列表具有大约两倍的桶数。

4、作为一般规则,默认负载因子(0.75)提供了时间和空间成本之间的良好权衡。 较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大部分操作中,包括get和put)。 在设置其初始容量时,应考虑到map中的预期条目数及其负载因子,从而最大限度地减少rehash操作的次数。 如果初始容量大于最大条目数除以负载因子,则不会发生重洗操作。

5、如果要将许多映射存储在HashMap实例中,则使用足够大的容量创建它将使映射存储更有效,而不是让它根据需要执行自动重新散列以增长表。 需要注意,使用具有相同hashCode()的许多键是减慢任何哈希表性能的可靠方法。 为了改善影响,当键具有可比性时,这个类可以使用键之间的比较顺序来帮助打破联系。

6、请注意,此实现不同步。 如果多个线程并发访问哈希映射,并且其中至少一个线程在结构上修改了该映射,则必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅仅更改与实例已包含的键关联的值并不是结构修改。)这通常是通过在一些自然封装map的对象上进行同步来实现的。 如果不存在这样的对象,则应该使用集合"包装"map。synchronizedMap方法。 这最好在创建时完成,以防止对map的意外不同步访问:

 Map m = Collections.synchronizedMap(new HashMap(...));

7、这个类的所有"集合视图方法"返回的迭代器都是快速失败的:如果在迭代器创建后的任何时候,以任何方式修改map,除了通过迭代器自己的remove方法,迭代器将抛出ConcurrentModificationException。 因此,面对并发修改,迭代器快速而干净地失败,而不是在未来未确定的时间冒着任意,非确定性行为的风险。

8、注意迭代器的快速失败行为不能得到保证,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。 Fail-fast迭代器在尽力而为的基础上抛出ConcurrentModificationException。 因此,编写依赖于此异常的程序是错误的:迭代器的快速失败行为应该仅用于检测bug。

总结:
可以看到源码中的注释还是非常详细的,包含了实现方式、可以为空的信息、排序、容量信息、参数信息、同步信息等内容能够帮助我们更好的了解这个类

二、用途

HashMap继承AbstractMap并且实现了Map, Cloneable, Serializable,
而AbstractMap也实现了Map,而map就相当于一个容器,它特别适合需要快速查找和插入键值对的场景,它可以用作以下几点功能:

1、缓存:可以将一些经常使用的数据存储在HashMap中,这样可以加快访问速度,减少对数据库或文件的访问次数
2、数据处理:HashMap可以方便地统计某些数据的数量,例如单词出现的次数、网站访问次数等
3、存储数据:避免了接收一个对象数据需要写实体类,在报表查询或者临时使用有着相当便捷的用途,而且map的有着自定义泛型的功能,所以在接收参数的类型时有着相当高的适应度
4、网络编程:可以使用HashMap来存储客户端与服务器之间的会话,每个客户端对应一个唯一的键值对,方便查找
5、GUI编程:可以使用HashMap来管理界面元素,例如用组件的名称作为键来查找对应的组件对象
6、并发编程:可以使用ConcurrentHashMap来实现线程安全的HashMap,支持多线程访问和修改,避免了由于并发访问导致的数据不一致问题

总之,HashMap是一个非常常用的数据结构,几乎在所有的Java程序中都会用到。无论是在运行效率还是代码简洁性上,都是一个非常优秀的选择

三、源码

1、常量

//The default initial capacity - MUST be a power of two.
//默认初始容量-必须是2的幂
//例如你设置了一个初始容量大小为6的map,则最终的容量会被调整为8,不设置初始容量默认16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//The maximum capacity, used if a higher value is implicitly specified by either of the constructors with arguments. MUST be a power of two <= 1<<30
//最大容量,如果任何一个具有参数的构造函数隐式指定了更高的值,则使用该容量。 必须是2的幂<=1<<30,也就是1073741824
static final int MAXIMUM_CAPACITY = 1 << 30;

//The load factor used when none specified in constructor
//在构造函数中指定none时使用的负载因子,当map中数量等于0.75*当前容量触发扩容
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/*
The bin count threshold for using a tree rather than list for a bin. 
Bins are converted to trees when adding an element to a bin with at least this many nodes. 
The value must be greater than 2 and should be at least 8 to mesh with assumptions in tree removal about conversion back to plain bins upon shrinkage
*/
/*
使用树而不是列表的bin计数阈值。 当向至少有这么多节点的bin中添加元素时,bin会转换为树。 
该值必须大于2,并且应该至少为8,以便与树移除中关于收缩时转换回普通箱的假设相啮合
就是链表转成红黑树的阈值,在存储数据时,当链表长度 > 该值时,则将链表转换成红黑树
*/
static final int TREEIFY_THRESHOLD = 8;

/*
The bin count threshold for untreeifying a (split) bin during a resize operation. 
Should be less than TREEIFY_THRESHOLD, and at most 6 to mesh with shrinkage detection under removal
*/
/*
在调整大小操作期间取消压缩(拆分)bin的bin计数阈值。 应小于TREEIFY_THRESHOLD,最多为6以去除下的收缩检测网格
就是红黑树转为链表的阈值,当在扩容(resize())时(此时HashMap的数据存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内数量 < 6时,则将 红黑树转换成链表
*/
static final int UNTREEIFY_THRESHOLD = 6;

/*
The smallest table capacity for which bins may be treeified. (Otherwise the table is resized if too many nodes in a bin.)
Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts between resizing and treeification thresholds.
*/
/*
最小的表容量,其桶可以被树化. (否则,如果bin中的节点太多,则会调整表的大小。)应至少为4*TREEIFY_THRESHOLD,以避免调整大小和treeification阈值之间的冲突。
就是当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)
*/
static final int MIN_TREEIFY_CAPACITY = 64;

2、字段

//The table, initialized on first use, and resized as necessary. When allocated, length is always a power of two. (We also tolerate length zero in some operations to allow bootstrapping mechanics that are currently not needed.)
//该表在首次使用时初始化,并根据需要调整大小。 分配时,长度总是2的幂。 (在某些操作中,我们还允许长度为零,以允许当前不需要的自举机制。)
transient Node<K,V>[] table;

//Holds cached entrySet(). Note that AbstractMap fields are used for keySet() and values()
//保存缓存的entrySet()。 注意AbstractMap字段用于keySet()和values()
transient Set<Map.Entry<K,V>> entrySet;

//The number of key-value mappings contained in this map
//此映射中包含的键值映射的数量(key值的数量)
transient int size;

// The number of times this HashMap has been structurally modified Structural modifications are those that change the number of mappings in the HashMap or otherwise modify its internal structure (e.g., rehash).
// This field is used to make iterators on Collection-views of the HashMap fail-fast. (See ConcurrentModificationException).
//这个HashMap被结构修改的次数结构修改是那些改变HashMap中映射的数量或以其他方式修改其内部结构(例如,rehash)的次数。 此字段用于使HashMap的集合视图上的迭代器快速失败。 (请参阅ConcurrentModificationException)。
transient int modCount;

//The next size value at which to resize (capacity * load factor)
//(The javadoc description is true upon serialization.Additionally, if the table array has not been allocated, this field holds the initial array capacity, or zero signifying DEFAULT_INITIAL_CAPACITY.)
//要调整大小的下一个大小值(容量*负载因子),例如设置5自动调整为2<<2 = 8
//Javadoc描述在序列化时是真实的。此外,如果尚未分配表数组,则此字段保存初始数组容量,或表示DEFAULT_INITIAL_CAPACITY的零。
int threshold;

//The load factor for the hash table
// 哈希表的负载因子,默认0.75
final float loadFactor;

3、方法

3.1 新建hashMap方法

/*
Constructs an empty HashMap with the specified initial capacity and load factor.
Params:
initialCapacity – the initial capacity
loadFactor – the load factor
Throws:
IllegalArgumentException – if the initial capacity is negative or the load factor is nonpositiv 
*/
//构造具有指定初始容量和负载因子的空HashMap
public HashMap(int initialCapacity, float loadFactor) {
		//1、初始容量小于0,抛出异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                              	initialCapacity);
        //2、初始容量超过最大容量,指定初始容量为最大容量1<<30
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //3、负载因子小于0 或者 负载因子是一个"非数字的值",抛出异常
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //4、指定负载因子,指定调整数量
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

//Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75).
//如果不指定负载因子,则默认创建一个负载因子为0.75的hashMap
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

//Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
//如果不指定hashMap的初始容量和负载因子,则默认创建一个初始容量16和负载因子为0.75的hashMap
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

//Constructs a new HashMap with the same mappings as the specified Map. The HashMap is created with default load factor (0.75) and an initial capacity sufficient to hold the mappings in the specified Map.
//使用与指定映射相同的映射构造一个新的HashMap。 HashMap使用默认加载因子(0.75)和足以在指定映射中保存映射的初始容量创建
//说简单点就是会创建一个新map,然后将参数map的信息放到新map中,继承参数map的容量
public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

/*
Params:
m – the map(参数map)
evict – false when initially constructing this map, else true (relayed to method afterNodeInsertion)
(在最初构造此映射时为false,否则为true)
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        //如果map的容量大于0
        if (s > 0) {
        	//如果table未初始化
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                // 计算阈值
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                 计算得到的t大于阈值,则初始化阈值
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            //已初始化,并且m元素个数大于阈值,进行扩容处理(扩容在后面会讲)
            else if (s > threshold)
                resize();
            //循环放入map中的元素
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

3.2 get方法

/*
Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
More formally, if this map contains a mapping from a key k to a value v such that (key==null ? k==null : key.equals(k)), then this method returns v; otherwise it returns null. (There can be at most one such mapping.)
A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also possible that the map explicitly maps the key to null. The containsKey operation may be used to distinguish these two cases
*/
/*
返回指定键映射到的值,如果此映射不包含键的映射,则返回null。
更正式地说,如果此映射包含从键k到值v的映射,则(key==null ? k==null:key.equals(k)),则此方法返回v;否则返回null。 (最多可以有一个这样的映射。)
返回值为null不一定表示映射不包含键的映射;映射也可能显式地将键映射为null。 ContainsKey操作可用于区分这两种情况
最常用的map.get方法,会返回key中的value,不过需要注意hashMap是允许key和val为空的,所以可能存在key为null的键,val返回null也不一定指的key不存在,可能val就是null
*/
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

//1、先来看里面的hash方法,首先key为null,它的hash值就是0,否则返回key的一个异或运算
//h会先等于key的hashCode 再和 key的hashCode>>>16计算,例如key为2,则返回的hash值就是50 ^ (50>>>16) = 50,
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

//2、再看外面的getNode方法,将上一步获取的hash值和key进行计算,返回一个Node
final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // 判断表是否为空,表大小是否大于零,并且根据此 key 对应的表内是否存在 Node节点
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node(总是检查第一个节点)
                ((k = first.key) == key || (key != null && key.equals(k))))
                // 桶中第一项(数组元素)相等,则直接返回第一项
                return first;
            // 桶中不止一个结点
            if ((e = first.next) != null) {
            	//如果是红黑树
                if (first instanceof TreeNode)
                // 则从红黑树中查找结果
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                // 否则循环遍历链表,while循环,直到命中结束或者遍历结束
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

hashMap的get方法总结:

  • 1、对key的hashCode()做hash运算,hash值为(h = k.hashCode()) ^ (h >>> 16)
  • 2、如果存储数组不为空,且计算得到的位置上的元素不为空。继续,否则,返回 null
  • 3、如果桶中第一项(数组元素)相等,则直接返回
  • 4、如果获取到的元素的 key 值不相等,查找 next 节点中的元素
  • 5、若为红黑树,则去树中查找,空间复杂度O(logn)
  • 6、若为链表,则在链表中通过key.equals(k)遍历查找,空间复杂度O(n)

3.3 put方法

//Associates the specified value with the specified key in this map.
//If the map previously contained a mapping for the key, the old value is replaced.
//将指定的值与此map中的指定键相关联。 如果这个map包含键的映射,则替换旧值(最常用的put方法)
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

/*
Params:
hash – hash for key(要存储key的hash值)
key – the key(要存储的key值)
value – the value to put(要存储的value值)
onlyIfAbsent – if true, don't change existing value(如果true,将不会替代当前已经存在的对应value,默认false)
evict – if false, the table is in creation mode(表是否在创建模式,如果为false,则表是存在创建模式,默认true)
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //1、检查table是否为空,如果为空就初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //2、检查table中位置为(n -1 ) & hash 是否为空
        if ((p = tab[i = (n - 1) & hash]) == null)
       		//如果为空,直接放入数组
            tab[i] = newNode(hash, key, value, null);
        else {
        //已经存在元素,代表了这个位置发生了碰撞
            Node<K,V> e; K k;
            //3、如果桶中存在的元素的hash 和 key 都相等,则直接覆盖旧value
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //4、如果存放该元素的链表是红黑树,放入树中
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            	//5、循环链表,从链表末尾插入元素
                for (int binCount = 0; ; ++binCount) {
                	//如果链表下一个元素为空,也就是到达链表的尾部
                    if ((e = p.next) == null) {
                    	//6、 在尾部插入新结点
                        p.next = newNode(hash, key, value, null);
                        //7、如果结点数量到达红黑树的阈值,则转换为红黑树,直接跳出循环
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //8、看链表中是否存在hash和key与要插入进来的元素相同,如果存在相同的元素跳出循环
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //9、遍历桶中的链表
                    p = e;
                }
            }
            //10、如果存在着key值、hash值与插入元素相等的结点
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                // onlyIfAbsent为false或者旧值为null
                if (!onlyIfAbsent || oldValue == null)
                	//覆盖旧的值
                    e.value = value;
                // 访问后回调
                afterNodeAccess(e);
                // 返回旧值
                return oldValue;
            }
        }
        //11、将记录修改次数加1,并判断是否需要扩容
        ++modCount;
        //12、如果容量超过了临界点,就扩容
        if (++size > threshold)
            resize();
        // 插入后回调
        afterNodeInsertion(evict);
        return null;
    }

hashMap的put方法总结:

  • 1、对key进行计算,得到key的hash
  • 2、如果散列table为空时,调用resize()初始化散列table
  • 3、如果没有发生碰撞,直接添加元素到散列表中去
  • 4、如果发生了碰撞,就会在以下3种情况下判断
    • 4.1、如果桶中存在的元素的hash 和 key 都相等,则直接覆盖旧value
    • 4.2、如果是红黑树结构,就调用树的插入方法
    • 4.3、如果是链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖
  • 5、如果桶满了大于阀值,则resize进行扩容

put的图解
HashMap的源码解读_第2张图片
图片来源:https://www.cnblogs.com/xiaoxi/p/7233201.html

3.4 resize方法

/*
Initializes or doubles table size. If null, allocates in accord with initial capacity target held in field threshold. 
Otherwise, because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move with a power of two offset in the new table.
*/
/*
初始化表或加倍表大小。 如果为null,则按照字段阈值中保持的初始容量目标进行分配。 
否则,因为我们使用的是两次幂的扩展,所以每个bin中的元素必须保持在同一个索引,或者在新表中以两次幂的偏移量移动。
目前基本上所有的集合或者map都使用了位运算进行扩容,hashMap使用了size<<1的计算方式,将容量提升至原来的2倍,所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置
另外,resize()函数在size > threshold时被调用,也就是容量大于map临界点的时候
*/
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        //oldCap大于 0 代表原来的 table 表非空(oldCap 为原表的大小)
        if (oldCap > 0) {
        	//判断旧表是否超过最大长度
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //旧表向左偏移一位小于最大值 并且旧表长度大于等于16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold(双倍阈值)
        }
        // 如果旧表的阈值大于0,新容量直接等于旧阈值
        else if (oldThr > 0) // initial capacity was placed in threshold (初始容量被置于阈值)
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults(初始阈值为0使用map容量默认值16)
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //阈值等于初始容量*默认负载因子
        }
        //如果新的阈值等于0,需要重新计算阈值
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            // 当新表小于最大容量 并且新表*0.75 小于最大容量时,等于新表*0.75,否则等于最大容量
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //调整后的大小 等于 新阈值
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        //新建hash桶数组
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
        	//如果旧的表不为空,开始扩容操作,遍历旧的hash表
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //如果旧的hash桶数组在j结点处不为空,复制给e
                if ((e = oldTab[j]) != null) {
                	//将旧表的元素设为空,方便JVM后续进行垃圾回收
                    oldTab[j] = null;
                    //如果后面没有Node结点
                    if (e.next == null)
                    	//直接对结点的hash值对新的数组长度求模获得存储位置
                        newTab[e.hash & (newCap - 1)] = e;
                        //如果结点属于红黑树,那么添加到红黑树中
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order(保留旧hash表中链表的顺序)
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                        	//将Node结点的next赋值给next
                            next = e.next;
                            //如果结点e的hash值与原hash桶数组的长度作与运算为0
                            if ((e.hash & oldCap) == 0) {
                            	//如果loTail为空,将e结点赋值给loHead
                                if (loTail == null)
                                    loHead = e;
                                //否则将e赋值给loTail.next,loTail也一并赋值
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //如果结点e的hash值与原hash桶数组的长度作与运算不为0
                            else {
                            	//如果hiTail 为空,将e结点赋值给hiHead 
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                //否则将e赋值给hiTail.next,hiTail也一并赋值
                                    hiTail.next = e;
                                hiTail = e;
                            }
                         //直到e为空
                        } while ((e = next) != null);
                        //如果loTail 不等于空,loTail.next就为空,将loHead赋值给新的hash桶数组[j]处
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //如果hiTail 不等于空,hiTail.next就为空,将hiHead赋值给新的hash桶数组[j+旧hash桶数组长度]处
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

hashMap的resize方法总结:

  • resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容
  • 每次扩展的时候,都是扩展到原来的2倍
  • 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置

扩容图解
HashMap的源码解读_第3张图片

3.5 remove方法

//Removes the mapping for the specified key from this map if present
//从该map中删除指定键的映射(如果存在)
public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

/*
Implements Map.remove and related methods
Params:
hash – hash for key 	key的hash值
key – the key 			key值
value – the value to match if matchValue, else ignored 要匹配的值,没有则忽略(默认null)
matchValue – if true only remove if value is equal 如果为true,则仅在值相等时删除(默认false)
movable – if false do not move other nodes while removing 如果为false,则在删除时不要移动其他节点(默认true)
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        //如果当前table已经初始化且index位置上的元素不为空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            //如果当前index位置上第一个元素就是需要被删除的元素,将node指定为p
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            //如果p结点的下一个元素不为空,则需要判断类型
            else if ((e = p.next) != null) {
            	//如果p结点属于红黑树,则调用树的查找
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                //否则遍历查找链表
                    do {
                    	//如果hash值相等,node结点等于e,跳出循环
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        //否则将p结点指定为e
                        p = e;
                    //当下一个元素不为空,继续循环
                    } while ((e = e.next) != null);
                }
            }
            //如果node不为null,代表已经查找到了key值对应的元素
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                //如果node属于红黑树,调用树的删除方法
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                //如果node等于p,则代表是index当前位置的第一个元素被查找到,直接将index位置的元素后移
                else if (node == p)
                    tab[index] = node.next;
                //否则将查找到元素的前一个元素的next指针指向被查找到的元素的next元素
                else
                    p.next = node.next;
                //操作数加一
                ++modCount;
                //map中元素数减一
                --size;
                //回调以允许LinkedHashMap后操作
                afterNodeRemoval(node);
                //返回被删除的元素
                return node;
            }
        }
        return null;
    }

hashMap的remove方法总结:

  • 1、它会先去寻找被删除的结点
    • 当前index位置上第一个元素,查看它就是需要被删除的元素
    • 如果第一个元素不为需要被删除的元素,则去红黑树或者链表里去找,获取node结点
  • 2、如果node结点不为空,那么就去删除
    • 如果是红黑树就调用树的删除方法
    • 如果是第一个元素就直接将index位置的元素后移
    • 否则将查找到元素的前一个元素的next指针指向被查找到的元素的next元素

3.6 其他方法

//Returns the number of key-value mappings in this map
//返回map中k-v的数量
 public int size() {
        return size;
    }

//Returns true if this map contains no key-value mappings
//如果map的size为0说明这个map是空的,返回true
public boolean isEmpty() {
        return size == 0;
    }
    
//Returns true if this map contains a mapping for the specified key
//如果map中包含这个key则返回true,会调用getNode获取出来是否为空
public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }

//Copies all of the mappings from the specified map to this map. These mappings will replace any mappings that this map had for any of the keys currently in the specified map
//将指定map中的所有k-v映射复制到此map。 这些映射将替换此map对当前在指定map中的任何键所具有的任何映射
//相当于原来map中的k会被参数中的map覆盖一遍,如果原map存在映射就被覆盖,否则就会在原map中添加参数map对应的映射
public void putAll(Map<? extends K, ? extends V> m) {
        putMapEntries(m, true);
    }

//Removes all of the mappings from this map. The map will be empty after this call returns
//从该map中删除所有映射。 此调用返回后,map将为空
public void clear() {
        Node<K,V>[] tab;
        //操作数加1
        modCount++;
        //如果表不为空并且元素数大于0,设置元素数为0,所有表元素都设置为空
        if ((tab = table) != null && size > 0) {
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }

/*
Returns true if this map maps one or more keys to the specified value.
Params:
value – value whose presence in this map is to be tested
Returns:
true if this map maps one or more keys to the specified value
*/
//如果map里的所有值里一旦包含这个value,则返回true
public boolean containsValue(Object value) {
        Node<K,V>[] tab; V v;
        //如果表不为空并且元素数大于0
        if ((tab = table) != null && size > 0) {
        	//遍历表
            for (int i = 0; i < tab.length; ++i) {
            	//当表中的元素不为空继续遍历
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                	//如果有值相等,直接返回true
                    if ((v = e.value) == value ||
                        (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }

/*
Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. 
If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. 
The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations. It does not support the add or addAll operations.
Returns:
a set view of the keys contained in this map
*/
/*
返回此map中包含的键的集合。 集合由map支持,因此对map的更改反映在集合中,反之亦然。 如果在对集合进行迭代时修改了map(除了通过迭代器自己的移除操作),则迭代的结果是未定义的。 该集合支持元素移除,通过迭代器从map中移除相应的映射。remove、removeAll、retainAll和clear操作。 它不支持add或addAll操作。
*/
public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }

//HashMap的keySet实现了AbstractSet,这边就不详细分析了,总之知道这个方法会返回一个set集合就好(不会重复)
  final class KeySet extends AbstractSet<K> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<K> iterator()     { return new KeyIterator(); }
        public final boolean contains(Object o) { return containsKey(o); }
        public final boolean remove(Object key) {
            return removeNode(hash(key), key, null, false, true) != null;
        }
        public final Spliterator<K> spliterator() {
            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super K> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.key);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

/*
Returns a Collection view of the values contained in this map. The collection is backed by the map, so changes to the map are reflected in the collection, and vice-versa. 
If the map is modified while an iteration over the collection is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. 
The collection supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Collection.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.
Returns:
a view of the values contained in this map
*/
/*
返回此map中包含的值的集合。 集合由map支持,因此对map的更改会反映在集合中,反之亦然。 如果在对集合进行迭代时修改了map(除了通过迭代器自己的移除操作),则迭代的结果是未定义的。 集合支持元素移除,通过迭代器从map中移除相应的映射。remove、removeAll、retainAll和clear操作。 它不支持add或addAll操作。
*/
public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new Values();
            values = vs;
        }
        return vs;
    }

//HashMap的values方法同样实现AbstractCollection,知道这个方法会获取map中的所有值就可以了(注意这个方法会重复)
final class Values extends AbstractCollection<V> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<V> iterator()     { return new ValueIterator(); }
        public final boolean contains(Object o) { return containsValue(o); }
        public final Spliterator<V> spliterator() {
            return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super V> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.value);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

/*
Returns a Set view of the mappings contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. 
If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation, or through the setValue operation on a map entry returned by the iterator) the results of the iteration are undefined. 
The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll and clear operations. It does not support the add or addAll operations.
Returns:
a set view of the mappings contained in this map
*/
/*
返回此map中包含的映射集合。 集合由map支持,因此对map的更改反映在集合中,反之亦然。 如果在对集合进行迭代时修改了map(除了通过迭代器自己的remove操作,或者通过迭代器返回的map条目上的setValue操作),迭代的结果是未定义的。 
该集合支持元素移除,通过迭代器从map中移除相应的映射。remove、removeAll、retainAll和clear操作。 它不支持add或addAll操作
*/
public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

//HashMap的entrySet方法实现AbstractCollection,会获取到每一组的kv值,通常用于遍历map
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator();
        }
        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Node<K,V> candidate = getNode(hash(key), key);
            return candidate != null && candidate.equals(e);
        }
        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>) o;
                Object key = e.getKey();
                Object value = e.getValue();
                return removeNode(hash(key), key, value, true, true) != null;
            }
            return false;
        }
        public final Spliterator<Map.Entry<K,V>> spliterator() {
            return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

//Overrides of JDK8 Map extension methods
//JDK8Map扩展方法的复盖,如果这个map里没有获取到key对应的结点,则返回defaultValue,否则获取对应node的值(此方法不会将defaultValue保存到map中)
 @Override
    public V getOrDefault(Object key, V defaultValue) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
    }

//如果map中存在对应的结点则不更新值,如果不存在对应的结点将节点和值放入map中
 @Override
    public V putIfAbsent(K key, V value) {
        return putVal(hash(key), key, value, true, true);
    }

//remove的重载方法,如果map中存在key对应的node且node对应的值要等于参数传的value,才会删除这个结点
@Override
    public boolean remove(Object key, Object value) {
        return removeNode(hash(key), key, value, true, true) != null;
    }

//map的替代方法
 @Override
    public boolean replace(K key, V oldValue, V newValue) {
        Node<K,V> e; V v;
        //如果key的node结点不为空 且 节点的值等于oldValue且不为空
        if ((e = getNode(hash(key), key)) != null &&
            ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
            //将新的值赋值给node结点的值
            e.value = newValue;
            //回调操作
            afterNodeAccess(e);
            return true;
        }
        return false;
    }

//同一个重载方法,取消了oldValue的限制,只要key对应的node结点存在就赋值
 @Override
    public V replace(K key, V value) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) != null) {
            V oldValue = e.value;
            e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
        return null;
    }

//computeIfAbsent是java8的新方法,有着和putIfAbsent类似的功能(有着更全面的功能,java8中引入了函数式接口,在各大底层普遍使用,不然作者也不会特意发布这个方法)
@Override
    public V computeIfAbsent(K key,
                             Function<? super K, ? extends V> mappingFunction) {
        //函数式接口为空的直接抛出异常
        if (mappingFunction == null)
            throw new NullPointerException();
        //计算key的hash值
        int hash = hash(key);
        Node<K,V>[] tab; Node<K,V> first; int n, i;
        int binCount = 0;
        TreeNode<K,V> t = null;
        Node<K,V> old = null;
        //如果元素数超过临界值 或 表为空 或表的长度等于0
        if (size > threshold || (tab = table) == null ||
            (n = tab.length) == 0)
            //进行初始化,n等于初始化的长度
            n = (tab = resize()).length;
        
        if ((first = tab[i = (n - 1) & hash]) != null) {
            if (first instanceof TreeNode)
                old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
            else {
                Node<K,V> e = first; K k;
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) {
                        old = e;
                        break;
                    }
                    ++binCount;
                } while ((e = e.next) != null);
            }
            V oldValue;
            if (old != null && (oldValue = old.value) != null) {
                afterNodeAccess(old);
                return oldValue;
            }
        }
        V v = mappingFunction.apply(key);
        if (v == null) {
            return null;
        } else if (old != null) {
            old.value = v;
            afterNodeAccess(old);
            return v;
        }
        else if (t != null)
            t.putTreeVal(this, tab, hash, key, v);
        else {
            tab[i] = newNode(hash, key, v, first);
            if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
        }
        ++modCount;
        ++size;
        afterNodeInsertion(true);
        return v;
    }

持续更新中。。。

四、实现一个简单的HashMap

持续更新中。。。

你可能感兴趣的:(数据结构,哈希算法,散列表,链表)