HashMap内部原理的分子级解读

30 篇文章 0 订阅

解读HashMap<K,V>内部原理

纵然到处都有关于HashMap原理解析,但我还是要对HashMap做一个内部原理的探索,因为每个人看待问题的角度是不同,就像一千个人眼中有一千个哈姆雷特,看似相同的事物,却亦可以给予你更多的惊喜。

文章结构:

  • HashMap的整体结构
    • 简介
    • 应用
    • 缺陷
  • HashMap的增删查改
    • Put
    • Remove
    • Get
    • Set
  • HashMap的内部结构
    • Node
    • TreeNode
    • Iterator与KeySet、EntrySet的联系
  • 总结

一、HashMap的整体结构

1、简介

HashMap 是基于hashing 与 mapping 即(哈希映射)构造的一个存储Key—Value的数据结构。它基于哈希表的Map接口实现。其主要的特征是:

  • hash算法
  • collision 哈希冲突的解决
    • Java 8 以前采用简易的链式法(复杂度为O(N))
    • Java 8 以后采用链式+红黑树实现(红黑树的复杂度为O(logN))
  • 拥有调优性(拥有多个调优参数,如负载因子,可以根据自己使用场景的数据模型进行更好地调优)

2、应用

  • 适合key-value的存取场景
  • 其value可以存储null空值

3、缺陷

  • 并非线程安全,并发修改可能出现问题
  • 极端问题:
    • 每次计算hash值都是同一个,造成单桶(bucket)过大,效率减低(采用红黑树优化)
    • 每次计算hash尽皆不同,造成桶数组( [ ] table)过大,不断扩容

整体结构例图:

在这里插入图片描述


二、HashMap的增删查改

在讲解增删查改之前,必须明白一点,在hashMap中,key相同指的是:hash(key)相同并且key.equals()相同

1、Put

分为单个put与批量put,接下来就从源码开始解析~~

单一put的源码:

	 /**
     * 添加时候,如果已经存在相同的key,则会替代掉原有的value
     * 调用putVal()方法
     * onlyIfAbsent=false;如果存在相同的key就会替换value。
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
	//onlyIfAbsent=true,如果存在相同的key,只有value==null才会添加
  	public V putIfAbsent(K key, V value) {
        return putVal(hash(key), key, value, true, true);
    }
	/**
     * put的底层实现
     * evict:是否需要删除,实现为空
     * onlyIfAbsent:只有缺失时才写入?
     */
    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)
          //指定的数组位置的桶没有元素,则新构建一个node节点
            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) {
                //已经存在key的情况,onlyIfabsent=true就只允许覆盖原为null的值
              	//onlyIfabsent=false则允许全方位覆盖
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
              //直接跳出
                return oldValue;
            }
        }
        //结构修改次数+1
        ++modCount;
        //达到阈值,则进行扩容
        if (++size > threshold)
            resize();
        //调用的实现是linkedHashMap里面的实现
        afterNodeInsertion(evict);
        return null;
    }

对于单一put操作小结:

  • 允许存在相同的key,对于存在相同的key,put方法会直接覆盖value;putIfAbsent则只允许覆盖null的value
  • 底层实现都是putVal(),该方法的实现:
    • 判断要添加的键值对的key的映射到数组的槽位上,是否存在节点。
      • 如果不存在,则构造新节点 new Node添加到槽位上,即桶的首元素。
    • 如果key映射的槽位已经存在根节点,则判断是首节点是树形节点还是链式节点。
      • 树形节点,调用 putTreeVal方法,进行红黑树的插入。
      • 链式节点,进行链表的遍历,如果已经存在,跳出循环,再判断是否进行value的覆盖。
      • 如果不存在,则构造新节点,添加到链的尾部,并且判断是否达了树化的条件,再决定是否进行树化
    • 自增modCount参数,保证hashmap的fail-fast机制能够正常运行。
    • 判断自增后的size是否达到了阈值(threshold),进而执行resize()

批量put~~

 	/**
     * 将一个map合并进来。
     */
    public void putAll(Map<? extends K, ? extends V> m) {
        putMapEntries(m, true);
    }
 	/**
     * 遍历参数map 
     */
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
      //获取参数的map
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
              	//补充精度
                float ft = ((float) s / loadFactor) + 1.0F;
                int t = ((ft < (float) MAXIMUM_CAPACITY) ?
                        (int) ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    //重新获取阈值 re get threshold
                    threshold = tableSizeFor(t);
            } else if (s > threshold)
                //如果构造参数的hashMap的容量大于当前hashMap的阈值,那么就会重新扩容
                resize();
            //以下是必执行的
            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);
            }
        }
    }

批量put的小结:

  • 如果原先map为空,且参数map的容量大于0,则重新计算threshold
  • 如果原先map不为空,则判断参数map是否大于阈值,若大于阈值,则执行resize
  • 最后遍历map进行一个个地putVal(本身会进行resize判断)

以上便是单一添加与批量添加的原理,当然,如果桶是链式的,那么就进行遍历链表添加,如果添加后达到阈值,则会进行桶的树化;如果桶是树形的,那就调用树形的插入方法: putTreeVal(this, tab, hash, key, value) 后续会讲。这里再附上put操作的图例:

在这里插入图片描述


2、Remove

*再讲解完put操作之后,接下来就是Remove方法的解析了!*先上代码:

/**
 * 如果key存在,则从此映射中删除指定键的映射。
 */
public V remove(Object key) {
    Node<K, V> e;
  //调用的实现是  removeNode方法
  //value=null;matchValue=false;代表无需用值去匹配,只要键相同即可删除
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
}
//根据   键——值 进行删除,value与matchValue=true
 public boolean remove(Object key, Object value) {
        return removeNode(hash(key), key, value, true, true) != null;
   }
	/**
     *
     * @param hash                         for key
     * @param key                          key
     * @param matchValue (如果为真)只在值相等时删除
     * @param movable 如果为false,则在删除时不移动其他节点
     * @param value 如果匹配值,则忽略该值
     * 返回节点,如果没有,返回null
     */
    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 {
                  //以下是链式桶的节点查找,找到则赋值给node,然后跳出循环
                    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)
                  //如果删除节点是桶的根节点,那么,把next节点存到桶的根位置
                    tab[index] = node.next;
                else
                  //普通的链式移除,node的父节点p的next指向node的next节点
                    p.next = node.next;
                ++modCount;
                --size;
              //hashMap的这个实现是空。linkedHashMap有实现
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

对remove操作做一个总结:

  • 分别有根据key进行remove;也有根据key-value进行remove,但是底层实现都是 removeNode
  • removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)方法根据传入的参数进行决定执行怎样的操作:
    • 如果value=null;matchValue=false;只要key相同即删除
    • 如果value!=null;matchValue=true;那么,只有key-value都相同才执行删除
  • 对于树形的操作后续再讲。

接下来讲解Get方法,Get方法相比以上的两种方法会相对简单~~

3、Get

一如既往,先上代码~~

 /**
     * 根据key获取对应的value
     * 可能返回null
     */
    public V get(Object key) {
        Node<K, V> e;
        //guess getNode is :by hashValue of key and adjust key == Node.key()
        //this is why can be only key in the hashMap,if not only, in the linked collision may         find lots of value!!!
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
/**
     * 根据hash值以及key查找返回对应的Node节点
     * note: guess is true!!!
     */
    final Node<K, V> getNode(int hash, Object key) {
        Node<K, V>[] tab;
        Node<K, V> first, e;
        int n;
        K k;
        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 {
                    //如果是链式节点,则进行遍历
                    if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
//以下方法是在JDK8中重写了继承自Map的方法;即根据Key找不到Node,返回默认的value
	@Override
    public V getOrDefault(Object key, V defaultValue) {
        Node<K, V> e;
        return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
    }

get方法比较简单,通过注释就可以完全明白get的实现,其实在put、remove中也可见,这里就不重复解读了!

4、Set

*Set操作在hashMap中并没有找到对应方法,其实HashMap相比List,把Set、Add整合为一个操作,就是Put,而put操作实现的功能就是:添加或者替换。*增删查改部分的内容到此结束,接下来要讲的是它的内部结构!


三、HashMap的内部结构

1、Node implements Map.Entry

Node就是HashMap实际存储Key、Value的数据结构,它是一个单向节点,实现了Map.Entry的getter setter,代码如下:

  static class Node<K, V> implements Map.Entry<K, V> {
        //哈希值
        final int hash;
        //键值对,注意Key为Final,这是为什么在hashMap中,键不可变而值可变的原因
        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;
        }
    	//这也是在打印hashMap节点的时候输出: key = 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;
                //判断节点相等的条件是:都是判断引用
                //1、key相等 ==
                //2、value相等 ==
                if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

小结:

  • HashMap中的Node实现Map.Entry,且为单向节点,只有next指针。
  • 该Node的key字段用final修饰,这也是为何hashMap中只能改变指定键值对的value,而不能改变键的原因
  • 补充:该Node在桶树化的时候,会转化成为treeNode。

2、TreeNode extends LinkedHashMap.Entry

*顾名思义,TreeNode就是树节点,该内部类是在JDK1.8之后引入的,主要用于hashMap的桶的链式转化成红黑树,提高性能。*上代码:

//可以看到,是通过继承LinkedHashMap.Entry获取它的一些属性
static final class TreeNode<K, V> extends LinkedHashMap.Entry<K, V> {
        TreeNode<K, V> parent;  // 红黑树的链接
        TreeNode<K, V> left;
        TreeNode<K, V> right;
        TreeNode<K, V> prev;    // 删除后需要断开next链接
        boolean red;
}

上述是TreeNode的内部结构,单一的红黑树是只有value、parent、left、right、color字段,但是,hashMap在原先基础上,增加了prev,并且继承了LinkedHashMap.Entry。我们接下来跳进LinkedHashMap.Entry看看源码

static class Entry<K,V> extends HashMap.Node<K,V> {
  //定义了前、后指针。
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

以上内容的小结:

  • 通过继承LinkedHashMap.Entry,添加了红黑树需要的属性,同时多加了prev指针,姑且不讨论作者添加了prev指针的目的;但是LinkedHashMap.Entry其实是继承了HashMap.Node,因此,TreeNode的真实内部结构应该是如下:
    • value
    • parent、left、right、color
    • prev、next

OK!我们已经小结完结构部分,对于TreeNode的内部树形操作,我想有必要简单地进行讲解,当然,具体的红黑树增删查改,有兴趣的朋友可以关注我后面的博文,我将会发布红黑树的讲解博文!

2.1、prev、next指针的妙用

HashMap的设计者很巧妙地利用了next指针,并且在实现TreeNode的时候,补充了prev指针,下面我将展示它的妙处:

  • 通过这两个指针,将链式节点与树形节点联系起来,方便链式转红黑树、红黑树退化成链式的转换。
  • 在处理树形与链式的节点时,提高了遍历的速率。
2.2、TreeNode的操作合集

贴出操作方法的代码:

//删除平衡
static <K, V> TreeNode<K, V> balanceDeletion(TreeNode<K, V> root,TreeNode<K, V> x);
//插入平衡
static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root,TreeNode<K, V> x);
//右旋
static <K, V> TreeNode<K, V> rotateRight(TreeNode<K, V> root,TreeNode<K, V> p);
//左旋
static <K, V> TreeNode<K, V> rotateLeft(TreeNode<K, V> root,TreeNode<K, V> p);
//将一个树桶分裂出两个树桶,如果太小,就链化
final void split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit);
//树节点的移除
final void removeTreeNode(HashMap<K, V> map, Node<K, V>[] tab,boolean movable)//树节点的添加
 final TreeNode<K, V> putTreeVal(HashMap<K, V> map, Node<K, V>[] tab,int h, K k, V v)
//树化
 final void treeify(Node<K, V>[] tab)
 //链化
final Node<K, V> untreeify(HashMap<K, V> map)

以上就是树的相关操作的合集,详细的操作感兴趣的同学可以去看看源码,这里就不多一一讲解,后续我可能会退出专栏,对hashMap的树化、链化做一个讲解。

这里需要注意一点:树化的时候,会判断一些参数:

  • 最小执行树化阈值 static final int MIN_TREEIFY_CAPACITY = 64;
    • 只有hashMap的table[]数组长度达到这个数,才有可能执行树化,table的桶的数组。
  • 最小桶树化阈值 static final int TREEIFY_THRESHOLD = 8;
    • 只有在达到 最小执行树化阈值且满足桶内达到这个阈值时,才会执行该桶的树化。
  • 还原阈值 static final int UNTREEIFY_THRESHOLD = 6;
    • 一个桶内的红黑树如果节点小于6,则会执行退化操作,退化成链式结构。

好了!以上树结构的总结到此结束,关于红黑树与树化、链化,后期会退出专栏进行讲解!敬请期待。


3、Iterator与KeySet、EntrySet

在HashMap中,除去核心的内部数据结构,还有一些巧妙的设计,如我们常用的keySet、entrySet,而这些集合的操作其实都离不开 Iterator迭代器,接下来让我们一起感觉大师们的巧妙设计吧。

3.1、KeySet

keySet即键集,继承了 abstractSet间接实现了Set接口,拥有了对应的操作规范。以下围绕它的结构代码进行讲解:

final class KeySet extends AbstractSet<K> {
        public final int size() {
            return size;
        }
        //清楚map中的keySet对象
        public final void clear() {
            HashMap.this.clear();
        }
        //返回key的迭代器
        public final Iterator<K> iterator() {
          //键迭代器
            return new KeyIterator();
        }
        //判断是否包含某key
        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);
        }
        //foreach调用的实现
        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 (Node<K, V> e : tab) {
                    for (; e != null; e = e.next)
                        action.accept(e.key);
                }
              //判断是否进行了并发的结构化修改
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

KeySet的小结:

  • keySet通过继承 AbstractSet 与 间接实现Set,从而让自己成为Set家族的成员。
    • 但是,KeySet并没有重写抽象Set的方法,因此,它并不支持Set的增删查改,下面会贴出代码说明。
  • KeySet的迭代只有两种方法:foreach与iterator,foreach实现如上,iterator是调用自己实现的keyIterator,后面会列出源码。

这是一个说明KeySet并非真的Set的一个Demo:

public class TestHashMap {
    private static final HashMap map = new HashMap();
    public static void main(String[] args) {
        map.put("123", "abc");
        map.put("1234", "abcd");
        Set set = map.keySet();
        set.add("newKey");
        for (Object o : set) {
            System.err.println(o);
        }
    }
}
//以上程序运行会抛出:java.lang.UnsupportedOperationException异常
3.2、Values

在讲解完KeySet之后,其实Values的原理也并不难懂,如出一辙。但是,它的命名时Values,而非ValueSet,我想,可能是在命名KeySet之后,发现命名为KeySet但是从实现上又没有进行支持有点不妥,因此命名为Values。

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) {
                  //我们可以看到,节点普通遍历是采用next指针,不论是树形还是链表形
                  //因此,某些查找功能调用hashMap的get方法会更快。
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.value);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

总结:

  • values,即值集的内部实现与键集如出一辙。
    • 但是值集继承的是 AbstractCollection,更加规范,它是集合的一个家族成员,而不是Set家族的成员;可用性更好。
  • 与键集的遍历一样,都是对每个桶的链式遍历!

3.3、EntrySet与迭代器

*在我个人看来,EntrySet其实可以改名为NodeSet,因为JDK1.8已经把Entry改为Node,现在的EntrySet其实就是Node的集合。*由于内部的结构与keySet等一般无二,所以直接上这个的总结:

  • entrySet其实就是hashMap中node的集合,即nodeSet。
  • entrySet是keySet与values的合集。

讲完hashMap中几个重要的集合之后,就来讲讲迭代器与这些集合的关系,毕竟forEach遍历已经讲完。

3.3.1、迭代器

在hashMap中,迭代器有很多种,这里只讲迭代器的实现与内部集合的关联,直接上源码:

//hashIterator 
abstract class HashIterator {
        Node<K, V> next;        // next entry to return
        Node<K, V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot
        HashIterator() {
            expectedModCount = modCount;
            Node<K, V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {
                } while (index < t.length && (next = t[index++]) == null);
            }
        }
        public final boolean hasNext() {
            return next != null;
        }
        final Node<K, V> nextNode() {
            Node<K, V>[] t;
            Node<K, V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {
                } while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }
        public final void remove() {
            Node<K, V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            removeNode(p.hash, p.key, null, false, false);
            expectedModCount = modCount;
        }
    }
final class EntryIterator extends HashIterator
            implements Iterator<Map.Entry<K, V>> {
        public final Map.Entry<K, V> next() {
            return nextNode();
        }
    }
    //ketIterator与EntryIterator类似。

以上便是普通迭代器:

  • 都是继承于 HashIterator 哈希迭代器进行拓展,实现对应的迭代功能。

那么有普通迭代器,自然有与众不同的迭代器,就是分裂迭代器,如下:

static class HashMapSpliterator<K, V> {
        final HashMap<K, V> map;
        Node<K, V> current;          // current node
        int index;                  // current index, modified on advance/split
        int fence;                  // one past last index
        int est;                    // size estimate
        int expectedModCount;       // for comodification checks
        HashMapSpliterator(HashMap<K, V> m, int origin,
                           int fence, int est,
                           int expectedModCount) {
            this.map = m;
            this.index = origin;
            this.fence = fence;
            this.est = est;
            this.expectedModCount = expectedModCount;
        }
        final int getFence() { // initialize fence and size on first use
            int hi;
            if ((hi = fence) < 0) {
                HashMap<K, V> m = map;
                est = m.size;
                expectedModCount = m.modCount;
                Node<K, V>[] tab = m.table;
                hi = fence = (tab == null) ? 0 : tab.length;
            }
            return hi;
        }
        public final long estimateSize() {
            getFence(); // force init
            return (long) est;
        }
    }

分裂迭代器在这里并没有使用。


四、总结

  • 以上hashMap的解读分为3个部分,通过以上几个部分的讲解,相信读者对hashMap有比较好的理解了。
  • 当然,存在一些具体的实现,如扩容、树的操作等,之后我会推出专栏进行讲解。
  • 以下再给大家分享一下hashMap的一个扩容算法,很优秀,大师的杰作:
 /**
     * 运算得出的结果比当前capacity大的一个由2^N得出的一个数值
     * 如果当前数值为2^,那么结果就等于原数值
     * 简化说法:就是使得最高位为1的位之后的全部位的数都是1
     */
    static final int tableSizeFor(int cap) {
        //对cap-1的目的就是实现等于原数值的操作
        int n = cap - 1;
        //>>>标识无符号右移,忽略符号位,空位都以0补齐
        //标识n=n|(n>>>1)
        n |= n >>> 1;//得到2个1
        n |= n >>> 2;//得到4个1
        n |= n >>> 4;//得到8个1
        n |= n >>> 8;//得到16个1
        n |= n >>> 16;//得到32个1
      	//由于最低位肯定会变为1,因此是单数,但是hashMap要求容量为2^,因此,需要判断执行+1.
        //返回值等于N+1,N+1等于2^
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

好了!本期就到此结束,请关注我后续对链化树、扩容、树化链的讲解,也会推出红黑树专栏!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值