HashMap源码分析(一)

1、hashmap简介

hashmap是一个key-value键值对形式的集合,用于存储键值对数据。默认容量为16,每次扩容是在原有容量基础上扩大一倍。jdk1.7时底层采用数组+链表形式存储,且因为头插法,在并发扩容时容易产生循环链表。jdk1.8时底层采用数组+链表+红黑树形式存储,且数据插入时采用尾插法,另外转变红黑树需要满足两个条件,一是链表长度大于等于8,二是数组容量达到64。

2、hashmap整体查看

先来看下HashMap在集合框架中的位置,可以看到HashMap继承自AbstractMap并实现了Map接口,且能序列化和克隆。所以HashMap肯定具备Map所定义的键值对存储能力,中间的AbstractMap抽象类定义了可公用的Map方法实现,使得继承自它的子类在实现Map功能时只需关注自定义的那部分即可。

表面来看hashmap功能可能比较简单,仅用于存储键值对数据集合,但是其内部实现比较复杂,这里先整体看一下内部的组成,便于我们查看源码前对其有个整体的认识:

通过上图可以看到,HashMap内部类大概由HashMapSpliterator、Node、KeySet、HashIterator、EntrySet、Values六部分组成,各部分功能如下:
HashMapSpliterator:是java1.8新增的接口,主要用于并行处理数据。这一块使用较少,所以不多做介绍,感兴趣的可以单独搜索查看。
Node:继承Entry接口,封装成链表节点,内部含有key、value、hash,且重写了hashcode与equals方法,TreeNode则是转变红黑树后的节点类型
KeySet、Values、EntrySet:key、value、entry的集合,表面是集合,其实并没有单独存储一份数据,底层还是hashmap那一套数据,只不过重写了set集合的相关方法。
HashIterator:其并没有实现Iterator接口,但是却编辑了Iterator中除next方法外的所有的同名方法。然后KeyIterator、ValueIterator、EntryIterator继承HashIterator和实现Iterator接口,由于HashIterator封装有Iterator接口除next()方法外的所有同名方法,所以key、value、entry迭代器只需要实现各自的next方法即可。这是模板方法体现之一,即父类封装共用的方法,子类仅实现需要自定义的方法。只不过模板方法的父类一般是一个抽象类,hashmap中则由HashIterator和Iterator共同组成。

3、内部类介绍
由于hashmap键值对存储和遍历的功能离不开内部类的支持,所以在正式介绍源码前讲一下主要的内部类,便于后面理解源码:
1)Node

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash; //每个键值对节点对应的唯一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; }

        //Node节点的hashcode为key与value的hashcode异或值
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        //重写equals方法,判断两个Node是否相同,只有在key与value都相同的情况下才相同
        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实现了Map的Entry接口,并实现了Entry接口中key、value值获取的方法。注意这里有一个小知识点,就是jdk1.8之后,支持接口定义静态方法。所有Node并没有全部重写Entry的方法。Node封装了基本的节点信息,无论是链表还是后续的红黑树,基础存储单元都是Node节点。

2)HashIterator(KeyIterator、ValueIterator、EntryIterator)

HashIterator实现了迭代器的除next外的所有方法,KeyIterator、ValueIterator、EntryIterator都继承自HashIterator,且实现了迭代器Iterator接口,由于HashIterator实现了Iterator接口除next方法外的所有方法,所以KeyIterator、ValueIterator、EntryIterator只需要关注具体的next方法实现即可。其源码结构也比较简单,大致如下:

abstract class HashIterator {
        Node<K,V> next;        // 下一个节点引用
        Node<K,V> current;     // 当前节点引用
        int expectedModCount;  // 快速失败机制使用的属性,用于监测并发修改
        int index; // 遍历数组中的第几个链表或者红黑树

        HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // 给next引用赋值第一个找到的非空链表或红黑树
                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;
            //1、当前修改书与预期修改数不同,说明遍历期间hashmap被并发修改,故抛异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            //2、如果next引用为空,抛异常
            if (e == null)
                throw new NoSuchElementException();
            //3、这一步主要是记录当前节点以及在当前链表或红黑树遍历结束后,将next引用指向下一个链表或红黑树,从而遍历完所有数据。
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        public final void remove() {
            //1、记录当前节点信息
            Node<K,V> p = current;
            //2、如果当前节点为空,或者这个过程中hashmap被并发修改,则抛异常
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            //3、释放当前引用
            current = null;
            //4、获取当前节点的key信息,并调用外部类中的移除方法(具体的后面讲)
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            //5、因为修改了hashmap集合,此时修改次数变了,所以重新赋值
            expectedModCount = modCount;
        }
    }

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

    final class ValueIterator extends HashIterator
        implements Iterator<V> {
        public final V next() { return nextNode().value; }
    }

    final class EntryIterator extends HashIterator
        implements Iterator<Map.Entry<K,V>> {
        public final Map.Entry<K,V> next() { return nextNode(); }
    }

迭代器源码中比较重要的是index索引的切换,它是一个递增值,用于顺序遍历数组中所有链表或红黑树。

3)KeySet(Values、EntrySet)

KeySet是一个key值的集合对象,但是并不是说真有一个存储key的set集合,其底层还是hashmap,在hashmap中通过方法获得该对象。通过源码查看可以知道set方法实现基础还是调用外部类或者迭代器实现。Values、EntrySet情况类似,这里就不贴代码浪费空间了。

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();
            }
        }
    }

至此,hashmap在map框架中的位置以及其内部类大致都了解了,下面来看一下hashmap常用的方法源码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值