美文网首页
HashMap 数据结构

HashMap 数据结构

作者: GDHuo | 来源:发表于2019-12-05 15:09 被阅读0次

    有个疑问,hashmap是数组+链表的数据结构,那么它是怎么遍历的,为什么下面的代码遍历出来结果是有序的?是不是会有个keyset缓存着有序的key呢,答案是否定的
    HashMap 遍历如下

            HashMap aa = new HashMap<String,String>();
            aa.put("c", "aaa");
            aa.put("b", "bbb");
            aa.put("a", "ccc");
            Iterator it = aa.keySet().iterator();
            while(it.hasNext()) {
                String key = (String) it.next();
                System.out.println("key "+key+" value "+aa.get(key));
            }
            Iterator it2 = aa.entrySet().iterator();
            while(it.hasNext()) {
                Map.Entry entry = (Entry) it.next();
                System.out.println("key "+entry.getKey()+" value "+entry.getValue());
            }
    
    结果
    key a value ccc
    key b value bbb
    key c value aaa
    

    从头看下,new HashMap() 的时候并没有创建数据结构

        public HashMap() {
            this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
        }
    

    添加数据的时候才会创建

        public V put(K key, V value) {
            return putVal(hash(key), key, value, 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;
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;   >>> 首次put数据会调用resize初始化槽数组
            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; >>>如果槽上第一个节点的key就是这个key的Entry
                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); >>> 新建Node
                            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))))  >>>如果链上找到了这个key对应的Entry
                            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;
        }
    
        public V get(Object key) {
            Node<K,V> e;
            return (e = getNode(hash(key), key)) == null ? null : e.value;
        }
        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) {   >>> first设置为映射到的槽位上
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))  >>>如果第一个key就equals,那么返回第一个
                    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);   >>>遍历链表查找key比较equals
                }
            }
            return null;
        }
    

    那么遍历时候it = aa.keySet().iterator()会调用到KeyIterator

        final class KeyIterator extends HashIterator
            implements Iterator<K> {
            public final K next() { return nextNode().key; }
        }
    

    它是继承自HashIterator

        abstract class HashIterator {
            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);>>>遍历指向第一个不为空的槽
                }
            }
            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) {  >>>next指向链表上的下一个
                    do {} while (index < t.length && (next = t[index++]) == null); >>>如果链表遍历完了,那么指向下一个不为空的槽
                }
                return e;
            }
        }
    

    现在问题解决了,KeyIterator只是个遍历工具,每次都是从一个非空槽位遍历整个链表,然后遍历下一个非空槽位的链表。
    而Key是按hash算出来的,以上算出来hash值 a b c 就有顺序了,所以出现了keyset有序的假象,但其实是散列的。

    另外说下HashSet,HashSet内部完全的HashMap,属桥接模式,那它怎么做到保存的数据不重复的,其实很简单,主要代码map.put(e, PRESENT) 而PRESENT是Object,HashMap里的key可不就是不重复的。So easy。

    相关文章

      网友评论

          本文标题:HashMap 数据结构

          本文链接:https://www.haomeiwen.com/subject/ncvdgctx.html