zoukankan      html  css  js  c++  java
  • 【数据结构】之散列链表(Java语言描述)

      散列链表,在JDK中的API实现是 HashMap 类。

      为什么HashMap被称为“散列链表”?这与HashMap的内部存储结构有关。下面将根据源码进行分析。

      首先要说的是,HashMap中维护着的是一个数组: transient Node<K,V>[] table; ,数组中的每个元素都是一个 Node 对象。这里的Node是HashMap的一个内部类,代码如下:

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
    
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
    
        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }
    
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
    
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
    
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

      可以看出来,这个Node类明显是一个链表的结构。也就是说,HashMap是一个链表的数组,这样一来,HashMap作为“链表”的部分就清楚了。

      那么HashMap为什么还被定义为“散列”呢?我们来看HashMap中的 put() 方法。put()方法中直接调用了 putVal() 方法,putVal()方法代码如下:

    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分析某个节点应该存储到哪条链中的依据是:先通过HashMap中的 hash() 方法获取到当前元素的key的哈希码,然后通过 (n - 1) & hash 运算,确定该节点具体应该位于哪条链上。这样一来,就相当于在HashMap中创建了一个“索引”,如果要取元素,只需要通过key的哈希值,就可以轻松地索引到节点所在的链,从而遍历链中的所有元素,最终得到目标节点,大大提高了查找的效率。通过这种方式,可能会导致数组中某些下标没有存储任何数据,HashMap的“散列”就体现在这里。

      HashMap的这种“散列”的思想,使得HashMap的查询效率比其他的任何数据结构都要高。但是,我们都知道,最好的散列,就是数组中的每个元素都是一个只有一个节点的链,即数组中的每个下标都只对应一个节点,这样,就可以保证HashMap的查询效率达到最高。当然,这种情况是很难实现的,但是HashMap给我们提供了一个能极大限度地达到这种效果的机制,那就是HashMap的扩容机制。

      HashMap的扩容机制体现在putVal()方法中: if (++size > threshold) resize(); 。 resize() 方法就是HashMap的扩容方法,后面详述; threshold 是HashMap的扩容临界值,默认是当前HashMap中数组长度的0.75倍; size 是当前HashMap中存储的节点的数量。也就是说,当HashMap中存储的节点数量超过扩容临界值的时候,就要开始扩容。通过这样方式,使HashMap能够极大地实现最佳散列效果。简单来说,如果不扩容,那么所有元素都会挤在那个数组中,难免会导致数组中某个下标对应的链过长;而通过扩容,增大了 (n - 1) & hash 操作中的基数n,因此各个元素可以重新散列,铺得更散。HashMap就是通过这种方式达到散列的。

  • 相关阅读:
    GSON -JSON 反序列化-多节点名称支持
    Jedis 分片原理
    闭锁-CountDownLatch
    XML序列化及反序列化
    用GIT操作SVN
    报表worker-CPU使用率过高原因排查
    二.PlantUML 之活动图
    一.PlantUML 与 IDEA 集成
    ArrayList
    VI常用命令
  • 原文地址:https://www.cnblogs.com/itgungnir/p/7309171.html
Copyright © 2011-2022 走看看