HashMap详解-HashMap 的常见操作

引言

HashMap是Java中最常用的数据结构之一,它提供了高效的插入、查找和删除操作。本篇博客将深入解析HashMap的常见操作,包括元素的插入、查找、删除以及遍历等。
关于HashMap内部实现原理的内容如下
HashMap详解-内部实现原理(1)-数组和桶
HashMap详解-内部实现原理(2)-哈希函数
HashMap详解-内部实现原理(3)-扩容机制

在浏览本章前可以了解一下以上知识,或许会给你提供一些帮助

元素比较(CompareTo)

    static int compareComparables(Class<?> kc, Object k, Object x) {
        return (x == null || x.getClass() != kc ? 0 :
                ((Comparable)k).compareTo(x));
    }

方法签名:static int compareComparables(Class kc, Object k, Object x)

函数逻辑:

  • kc参数是一个Class对象,表示要进行比较的对象的类。
    k参数是要进行比较的源对象。
    x参数是要和源对象进行比较的目标对象。
  • 方法逻辑:
    首先判断目标对象x是否为空或者其类型与指定的类kc不一致,如果是,则返回0。
    如果目标对象x的类型与指定的类kc一致,转换k为Comparable类型,并调用其compareTo()方法来比较k和x的大小。
    返回比较结果,如果k小于x,返回负整数;如果相等,返回0;如果k大于x,返回正整数。

注意事项:
该方法假设k实现了Comparable接口,并且x的类型与kc相同或是其子类。
如果x为null或其类型与kc不一致,表示无法进行比较,返回0。
这段代码通常在排序算法或需要比较对象大小的场景中使用。通过调用compareTo()方法,可以实现不同类型对象的比较操作。请注意在使用时要确保对象类型的一致性,以避免可能的ClassCastException异常。

构造方法(HashMap)

    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

这段代码是Java中HashMap类的构造函数,用于初始化HashMap对象。

构造函数签名:public HashMap(int initialCapacity, float loadFactor)

  • initialCapacity参数表示初始容量大小。
  • loadFactor参数表示负载因子。

构造函数逻辑:

  1. 首先,检查传入的initialCapacity是否小于0,如果是则抛出IllegalArgumentException异常,提示初始容量为非法值。
  2. 检查传入的initialCapacity是否大于MAXIMUM_CAPACITY(HashMap中定义的最大容量),如果是,则将初始容量设置为MAXIMUM_CAPACITY
  3. 检查传入的loadFactor是否小于等于0或者为NaN(不是一个数字),如果是,则抛出IllegalArgumentException异常,提示负载因子为非法值。
  4. 将负载因子赋值给实例变量this.loadFactor
  5. 调用上面解释过的tableSizeFor()方法,传入初始容量大小,计算得到合适的容量大小,并将其赋值给实例变量this.threshold

注意事项:

  • 构造函数对传入的初始容量和负载因子进行了合法性检查,确保它们满足HashMap的要求。
  • 初始容量会通过调用tableSizeFor()方法获得一个合适的容量大小,以保持HashMap的容量为2的幂次方。
  • 负载因子是用来控制HashMap在扩容之前的填充程度,默认值为0.75。当HashMap中的元素个数达到负载因子与容量的乘积时,会触发扩容操作。

这段代码在创建HashMap对象时会被调用,用于初始化HashMap的各项参数,并计算出合适的容量大小。

元素插入(put)

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

这段代码是HashMap类中的一个私有方法,用于向哈希表中插入键值对。具体解释如下:

方法签名:final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

  • hash参数表示键的哈希值。
  • key参数表示要插入的键。
  • value参数表示要插入的值。
  • onlyIfAbsent参数表示是否只在键不存在时插入。
  • evict参数表示是否需要进行扩容。

方法逻辑:

  1. 首先,定义了一些局部变量,包括tab(底层数组),p(桶中的第一个节点),n(底层数组长度),i(计算桶的索引),以及e(遍历桶中的节点)和k(节点的键)等。
  2. 如果底层数组tab为null或长度n为0,则调用resize()方法对底层数组进行初始化和扩容操作,并将长度赋给n。
  3. 检查桶中的第一个节点p,如果节点为null,则直接将新节点插入到该位置。
  4. 如果桶中的第一个节点不为null,继续判断:
    如果节点的哈希值和键与传入的哈希值和键匹配,则表示已存在相同的键,直接将该节点赋给e。
    如果节点类型是红黑树节点(TreeNode类型),则调用putTreeVal()方法在红黑树中插入键值对,并将返回结果赋给e。
    否则,通过循环遍历桶中的链表,直到找到哈希值和键都匹配的节点,或者链表遍历到末尾为止。在此过程中,通过不断更新p和e来遍历链表。
  5. 如果找到了相同的键(即e不为null),则表示存在已存在的映射关系:
    如果onlyIfAbsent为false,或者旧值oldValue为null,则更新节点的值为新值。
    调用afterNodeAccess(e)方法,在访问之后执行一些后续操作。
    返回旧值oldValue。
  6. 如果没有找到相同的键,则需要插入新的节点:
    创建一个新的节点,并将其插入到链表的末尾。
    如果链表长度超过了阈值TREEIFY_THRESHOLD - 1(-1是为了防止链表只有一个节点时就转换为红黑树),则调用treeifyBin()方法将链表转换为红黑树。
    跳出循环,插入完成。
  7. 更新修改次数modCount和元素个数size。
  8. 如果元素个数超过了阈值threshold,则调用resize()方法进行扩容。
  9. 调用afterNodeInsertion(evict)方法,在插入之后执行一些后续操作。
    返回null表示插入成功。

注意事项:

  • 该方法用于HashMap的内部实现,用于插入键值对到底层数组的相应桶中。
    方法中通过哈希值和键的比较来判断是否已存在相同的键。
  • 插入时,如果链表过长,会导致效率降低。当链表长度达到一定阈值时,会自动将链表转换为红黑树来提高搜索效率。
  • 插入操作可能触发底层数组的扩容操作,以及在访问和插入之后执行相应的后续操作。

元素查找(get)

    final Node<K,V> getNode(Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & (hash = hash(key))]) != 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 {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

这段代码是HashMap类中的一个私有方法,用于根据给定的 key 查找并返回对应的节点。

方法签名:final Node getNode(Object key)

  • key参数表示要查找的键。

方法逻辑:

  1. 首先,定义了一些局部变量,包括tab(底层数组),first(桶中的第一个节点),e(遍历桶中的节点),n(底层数组长度),hash(通过调用hash(key)方法计算得到的键的哈希值),以及k(节点的键)。
  2. 如果底层数组tab不为null,且数组长度n大于0,且桶中的第一个节点first不为null,则进入条件判断。
  3. 首先检查桶中的第一个节点first,如果该节点的哈希值与传入的哈希值相等,并且节点的键与传入的键相等(使用==和equals()进行比较),则返回该节点。
    如果桶中的第一个节点不满足条件,继续判断是否存在下一个节点。如果存在下一个节点e,执行以下逻辑:
  4. 如果桶中的第一个节点是红黑树节点(TreeNode类型),则调用getTreeNode()方法进行进一步查找,并返回找到的节点。
  5. 否则,通过循环遍历桶中的节点,直到找到哈希值和键都匹配的节点,然后返回该节点。
  6. 如果以上条件都不满足,则表示未找到对应的节点,返回null。

注意事项:

  • 该方法用于HashMap的内部实现,用于在底层数组的相应桶中查找指定的节点。
  • 方法中通过哈希值和键的比较来判断是否找到对应的节点。
    如果链表过长,会导致效率降低。当链表长度达到一定阈值时,会自动转换为红黑树来提高搜索效率。
  • 如果没有找到对应的节点,返回null表示查找失败。

元素删除(remove)

    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;
        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;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

这段代码是HashMap类中的一个私有方法,用于删除哈希表中的节点。具体解释如下:

方法签名:final Node removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)

  • hash参数表示键的哈希值。
  • key参数表示要删除的键。
  • value参数表示要删除的值。
  • matchValue参数表示是否需要匹配值。
  • movable参数表示是否允许移动其他节点。

方法逻辑:

  1. 首先,定义了一些局部变量,包括tab(底层数组),p(桶中的第一个节点),n(底层数组长度),index(计算桶的索引),以及node、e、k和v等。
  2. 如果底层数组tab不为null且长度n大于0,并且桶中的第一个节点p不为null,则继续执行删除操作。
  3. 判断桶中的第一个节点p与传入的哈希值和键是否匹配:
    如果匹配,则将该节点赋给node。
    否则,通过循环遍历链表中的各个节点,直到找到哈希值和键都匹配的节点,或者遍历到链表末尾。在此过程中,通过不断更新p和e来遍历链表。
  4. 如果找到了要删除的节点(即node不为null),继续判断:
    如果不需要匹配值,或者节点的值与传入的值匹配,则执行删除操作。
    如果节点类型是红黑树节点(TreeNode类型),则调用removeTreeNode()方法在红黑树中删除节点。
    如果节点恰好是桶中的第一个节点,则将桶的引用指向节点的下一个节点。
    否则,将节点的前驱节点的下一个节点指向节点的下一个节点,实现节点的删除。
  5. 更新修改次数modCount和元素个数size。
  6. 调用afterNodeRemoval(node)方法,在删除之后执行一些后续操作。
  7. 返回被删除的节点,如果没有找到要删除的节点,则返回null。

注意事项:

该方法用于HashMap的内部实现,用于删除哈希表中的节点。
删除操作需要经过哈希计算、键比较等步骤来找到要删除的节点。
删除节点可能涉及到链表的重组或红黑树的调整操作,以及在删除之后执行相应的后续操作。

总结:

HashMap是一个高效的数据结构,适用于存储大量的键值对。

在使用HashMap时,合理设置初始容量和负载因子,以及避免频繁的扩容操作,将有助于提高性能。

你可能感兴趣的:(java,hash)