Java集合之HashMap源码剖析(jdk1.8)

Java集合之HashMap源码剖析(jdk1.8)

1、简介

HashMap是较为常用的一种集合,底层结构较为复杂。

由数组+链表+红黑树组成。

HashMap使用key计算出哈希值,将value存放到数组中,当出现哈希冲突时,将冲突的元素通过链表放到数组元素后,当链表或数组中元素数量过多时,转换为红黑树。

数组的查询效率为O(1),链表的查询效率是O(k),红黑树的查询效率是O(log k),所以当元素数量非常多的时候,转化为红黑树能极大地提高效率。

Hash


其继承关系如下:

HahMap

2、源码分析

1、属性

属性主要由几个容量、扩容或者变化的阈值组成。

装载因子用于处理扩容问题。

//初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;

//默认装载因子   
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//数组,也称为桶
transient Node<K,V>[] table;

//链表节点数量进化阈值,达到8进化成红黑树,进化条件①,和条件②相互约束
static final int TREEIFY_THRESHOLD = 8;

//红黑树退化阈值,降到6退化成链表
static final int UNTREEIFY_THRESHOLD = 6;

 //整个容器容量达到64才允许某个链表进化成红黑树,进化条件②
static final int MIN_TREEIFY_CAPACITY = 64;


//entrySet方法返回的结果
transient Set<Map.Entry<K,V>> entrySet;

//桶的扩容门槛,容器中元素达到多少时进行扩容:默认构造方法中为capacity * load factor
int threshold;

//装载因子
final float loadFactor;

2、内部类

HashMap中定义的内部类较多。

主要由两个节点类,几个元素集,N个自己的迭代器组成。

//链表的节点类,单向引用
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;
        }

       //方法略去
    }



//内部定义的红黑树节点、多个应用以及一个标记
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
	//方法略去
}


//元素的key组成的Set类
final class KeySet extends AbstractSet<K> {
        //内容略去
    }

//元素的value组成的Collection类
final class Values extends AbstractCollection<V> {
        //内容略去
    }

//元素的key-value组成的EntrySet类
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
     //内容略去
}


//内部定义的各种迭代器类
abstract class HashIterator
final class KeyIterator extends HashIterator implements Iterator<K>   
final class ValueIterator extends HashIterator implements Iterator<V>       
final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>>       
static class HashMapSpliterator<K,V> 
 //.......    
3、构造方法

HashMap有四个构造方法。

复杂的是这个构造方法:传入容量和装载因子。

/**
检验容量和装载因子是否合法,合法则计算扩容容量。
扩容门槛为传入的初始容量往上取最近的2的n次方
第一次put元素时会发现初始容量就是这次的扩容门槛
**/
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
    	//获取扩容门槛
        this.threshold = tableSizeFor(initialCapacity);
    	//这种构造方法得到的容器的初始容量就是扩容门槛,实际操作在resize方法中
    }

 // 扩容门槛为传入的初始容量往上取最近的2的n次方
static final int tableSizeFor(int cap) {
   //减一操作是为了或运算,避免最后得出的结果是预料结果的2倍
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

三个较为简单。

//传入容量
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

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

//传入一个map
public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
4、核心方法
1、put方法

通过put方法添加元素。

//put方法调用的putVal方法
public V put(K key, V value) {
    	//hash方法
        return putVal(hash(key), key, value, false, true);
    }

//hash方法通过key获取哈希值
static final int hash(Object key) {
        int h;
    	//调用key的hashCode方法并且进行^运算取哈希值
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

/*
五个参数:hash值、key、value。
第四个参数是如果key相同时,是否替换掉原value,ture不替换,false替换掉
第五个参数是表(数组)的创建模式,在此用不上。

返回值V,返回的是旧的value,没有则返回null.此处的旧value参考第四个参数
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K, V>[] tab;
    Node<K, V> p;
    int n, i;
    // 如果桶的数量为0,则初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        // 调用resize()初始化
        n = (tab = resize()).length;
    // (n - 1) & hash 计算元素在哪个桶中
    // 如果这个桶中还没有元素,则把这个元素放在桶中的第一个位置
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 新建一个节点放在桶中
        tab[i] = newNode(hash, key, value, null);
    else {
        // 如果桶中已经有元素存在了
        Node<K, V> e;
        K k;
        // 如果桶中第一个元素的key与待插入元素的key相同,保存到e中用于后续修改value值
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            // 如果第一个元素是树节点,则调用树节点的putTreeVal插入元素
            e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
        else {
            // 遍历这个桶对应的链表,binCount用于存储链表中元素的个数
            for (int binCount = 0; ; ++binCount) {
                // 如果链表遍历完了都没有找到相同key的元素,说明该key对应的元素不存在,则在链表最后插入一个新节点
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 如果插入新节点后链表长度大于8,则判断是否需要树化,因为第一个元素没有加到binCount中,所以这里-1
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 如果待插入的key在链表中找到了,则退出循环
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 如果找到了对应key的元素
        if (e != null) { // existing mapping for key
            // 记录下旧值
            V oldValue = e.value;
            // 判断是否需要替换旧值
            if (!onlyIfAbsent || oldValue == null)
                // 替换旧值为新值
                e.value = value;
            
            // 在节点被访问后做点什么,在LinkedHashMap中内部调整顺序时用到,此处无用
            afterNodeAccess(e);
            // 返回旧值
            return oldValue;
        }
    }
    // 到这里了说明没有找到元素
    // 修改次数加1
    ++modCount;
    // 元素数量加1,判断是否需要扩容
    if (++size > threshold)
        // 扩容
        resize();
    
    //在节点插入后做点什么,在LinkedHashMap中内部调整顺序时用到,此处无用
    afterNodeInsertion(evict);
    // 没找到元素返回null
    return null;
}

2、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) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                //老容量已经达到最大容量值则不再扩容
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                /*
                将新容量扩容为老容量的两倍,此时如果新容量小于最大容量值,
                且老容量大于等于默认的初始容量:扩容门槛也加倍
                */
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) 
            //这里是非默认构造方法得到的容器,第一次put元素时到达的地方
            //这里新容量为扩容门槛
            newCap = oldThr;
        else {               
            //这里是默认构造方法得到的容器,第一次put元素时到达的地方
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
    	// 如果新扩容门槛为0,则计算为容量*装载因子,但不能超过最大容量
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
    	//修改扩容门槛
        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;
            // 如果桶中第一个元素不为空,赋值给e
            if ((e = oldTab[j]) != null) {
                // 清空旧桶,便于GC回收  
                oldTab[j] = null;
                // 如果这个桶中只有一个元素,则计算它在新桶中的位置并把它搬移到新桶中
                if (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;
                        // (e.hash & oldCap) == 0的元素放在低位链表中
                        // 比如,3 & 4 == 0
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        } else {
                            // (e.hash & oldCap) != 0的元素放在高位链表中
                            // 比如,7 & 4 != 0
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    // 遍历完成分化成两个链表了
                    // 低位链表在新桶中的位置与旧桶一样(即3和11还在三号桶中)
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    // 高位链表在新桶中的位置正好是原来的位置加上旧容量(即7和15搬移到七号桶了)
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
3、get方法

通过key获取value

 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;
    // 如果桶的数量大于0并且待查找的key所在的桶的第一个元素不为空
    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;
}
4、remove方法

通过key删除节点。

public V remove(Object key) {
    Node<K, V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
}

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;
    // 如果桶的数量大于0且待删除的元素所在的桶的第一个元素不为空
    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变量后续删除使用
            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);
            }
        }
        // 如果找到了元素,则看参数是否需要匹配value值,如果不需要匹配直接删除,如果需要匹配则看value值是否与传入的value相等
        if (node != null && (!matchValue || (v = node.value) == value ||
                (value != null && value.equals(v)))) {
            if (node instanceof TreeNode)
                // 如果是树节点,调用树的删除方法(以node调用的,是删除自己)
                ((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
            else if (node == p)
                // 如果待删除的元素是第一个元素,则把第二个元素移到第一的位置
                tab[index] = node.next;
            else
                // 否则删除node节点
                p.next = node.next;
            ++modCount;
            --size;
            // 删除节点后置处理
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

5、replace方法

通过key更新value。

@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;
    }

6、其他方法

其他的一些处理Key和Value集合的方法。可以处理遍历问题。

public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }


public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new Values();
            values = vs;
        }
        return vs;
    }

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

3、总结

(1)HashMap底层基于数组+链表+红黑树实现,当某个桶中元素超过8且总元素数量超过64时,该桶中元素由链表进化成红黑树。

(2)默认构造方法得到的map容器,其扩容门槛是cap*loadfactor;非默认构造方法得到的容器,初始扩容门槛等于初始容量(此处初始容量等于传入的容量往上取最近的2的n次方)。

(3)HashMap允许Null值和Null键。

(4)线程不安全,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap,或者直接使用并发包下的ConcurrentHashMap.

(5)HashMap没有迭代器,但是可以通过KeySet等方法获取对应的Set,从而遍历输出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值