• 【深入Java基础】HashMap源码分析(二)


    HashMap源码分析(二)

    resize重置大小

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//旧容量
        int oldThr = threshold;//旧阈值
        int newCap, newThr = 0;
        if (oldCap > 0) {//如果旧容量大于0
            if (oldCap >= MAXIMUM_CAPACITY) {//是否大于最大容量
                threshold = Integer.MAX_VALUE;//阈值设置为最大整数
                return oldTab;//因为已经达到最大容量,所以直接返回旧tab
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&//如果旧容量大于0且小于最大容量,新容量=旧容量*2
                     oldCap >= DEFAULT_INITIAL_CAPACITY)//如果旧容量大于等于默认的容量
                newThr = oldThr << 1; // double threshold  //新阈值扩大一倍
        }
        else if (oldThr > 0) // initial capacity was placed in threshold  //如果oldCap<=0即tab为空,则初始化容量为阈值
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults  //如果oldCap和oldThr都为0
            newCap = DEFAULT_INITIAL_CAPACITY;//新容量=默认初始化容量
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//新阈值=默认装载因子*默认初始化容量
        }
        if (newThr == 0) {//如果新阈值=0
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);//新阈值=ft或者最大值
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;//置为null便于GC回收
                    if (e.next == null)//如果e.next=null即到了尾节点
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)//不是尾节点且是按照树存储的
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order  //
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
    

    重置容量的操作还是比较复杂的。并未完全理解。

    删除操作

    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
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to match if matchValue, else ignored
     * @param matchValue if true only remove if value is equal
     * @param movable if false do not move other nodes while removing
     * @return the node, or null if none
     */
    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;
    }
    

    分析:

    final Node < K , V > removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable)

    参数:

    hash : key的hash值

    key : 键值

    value : 值

    matchValue : 是否匹配特定值(remove(key,value)中为true)

    movable : 按树的方式移除时的参数,作用暂时不知

    删除思路:

    对于hashmap的节点删除是这样的:首先进行hashmap的非空判断,不为空则根据hash值判断是否要删除的是头节点(头节点存储在tab[]数组中),如果是则用node存储待删除的节点。如果要删除的不是头节点,则判断存储方式(是链表还是树?),如果是树则按照树的方式删除,如果是链表则按链表的方式删除。在按照链表的方式删除时,遍历链表找到要删除的节点,然后用node存储待删除的节点。这要找到要删除的节点node时就执行删除操作:首先判断node是否为空,并且判断是否需要匹配value,然后判断node是不是TreedNode的实例,如果是则按照树删除,否则按照链表删除,(注意:如果删除的链表的头节点则需要更改tab[]数组里的头节点值)链表的删除操作比较简单:断开带删除节点bnode的两侧链,然后node的前驱的next指向node的后继节点即可。

    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;//移动指针(p是node的前驱结点)
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {//如果node不为null,并且不用匹配值(或者匹配值,如果匹配值,则在使用的时候就是remve(key,value))
                if (node instanceof TreeNode)//如果node是TreedNode的实例
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);//按照树的方式移除
                else if (node == p)//如果移除的是头节点
                    tab[index] = node.next;//则tab内的头节点改为被移除的node节点的下一个节点
                else
                    p.next = node.next;//p的前驱指向node的后继,重新将链表连起来
                ++modCount;//操作次数加1
                --size;//map大小减1
                afterNodeRemoval(node);//移除后的操作
                return node;//返回移除的节点
            }
        }
        return null;
    }
    

    清空操作

    情况操作比较简单,就是对table[]数组赋值为null即可

    public void clear() {
        Node<K,V>[] tab;
        modCount++;
        if ((tab = table) != null && size > 0) {
            size = 0;
            for (int i = 0; i < tab.length; ++i)
                tab[i] = null;
        }
    }
    

    判断key是否存在

    因为调用的getNode()方法,直接计算hash值,时间复杂度为O(1)。
    public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
    }

    判断value是否存在

    先进行非空判断,然后遍历table[]数组,获取到头节点,然后再根据头节点遍历链表即可。时间复杂度为O(n)

     public boolean containsValue(Object value) {
        Node<K,V>[] tab; V v;
        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) {
                    if ((v = e.value) == value ||
                        (value != null && value.equals(v)))
                        return true;
                }
            }
        }
        return false;
    }
    

    KeySet

    获取key的set集合。这里有一个KeySet类,里边有常用的iterator()方法获取迭代器。

    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }
    
    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();
            }
        }
    }
    

    entrySet

    和keySet类似。

     public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }
    
    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();
            }
        }
    }
    

    可以看到这两个类里的forEach()方法里都判断了mc和modCount是否相等,如果不想等则说明在遍历的时候map的数据被修改了,会抛出ConcurrentModificationException异常

    1.8之后新加的

    //获取值,如果key不存在则返回dafaultValue
        @Override
    public V getOrDefault(Object key, V defaultValue) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
    }
    
    //仅当key不存在或者value=null时插入,如果key存在且value不为null则什么都不干(不会覆盖)
    @Override
    public V putIfAbsent(K key, V value) {
        return putVal(hash(key), key, value, true, true);
    }
    //移除键值对
    @Override
    public boolean remove(Object key, Object value) {
        return removeNode(hash(key), key, value, true, true) != null;
    }
    //替换
    @Override
    public boolean replace(K key, V oldValue, V newValue) {
        Node<K,V> e; V v;
        if ((e = getNode(hash(key), key)) != null &&
            ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
            e.value = newValue;
            afterNodeAccess(e);
            return true;
        }
        return false;
    }
    //替换
    @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;
    }
    

    迭代器

    HashIterator是HashMap的迭代器类。里边有常用的hasNext()、remove(),获取下一个节点的nextNode()方法。同样通过if (modCount != expectedModCount)来抛出异常。

        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;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }
    

    到这里HashMap的主要源码(常用的)就结束了,至于红黑苏结构的存储方式日后再论。

  • 相关阅读:
    数据库连接字符串解析的正则表达式
    .NET 2.0 中的自定义配置处理
    一个三层架构的WinForms程序的完整范例(.NET 1.1/Northwind)
    office2013破解工具
    jQuery中的$extend()介绍
    再次复习数据结构:c语言链表的简单操作
    c语言单链表,冒泡排序
    c语言中双维数组与指针的那点事儿
    CodeDom.CodeArrayCreateExpression不能生成多维数组的创建表达式
    发现Maxthon(myIE2)浏览器处理javascript脚本时的奇怪现象
  • 原文地址:https://www.cnblogs.com/cnsec/p/13286730.html
Copyright © 2020-2023  润新知