HashMap(1.8)源码分析

目录

 

一、HashMap简介

1.1、简述

1.2、继承结构

1.3、数据结构

二、源码分析

2.1、常量

2.2、属性

2.3、方法


一、HashMap简介

1.1 简述

在api文档中,其大致定义为

基于哈希表实现的Map接口。 此实现提供了所有可选的map操作,并允许key和value为null。(HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null)。这个类不能保证map中元素的顺序; 特别是,它不能保证顺序在一段时间内保持不变。

1.2、继承结构

  • public class HashMap extends AbstractMap implements Map, Cloneable, Serializable
  • public abstract class AbstractMap implements Map
  • public interface Map

其中,抽象类AbstractMap是为了提供Map接口的基本实现,以减少实现该接口所需的工作量。

1.3、数据结构

在jdk1.8以前,HashMap的数据结构采用的是数组加链表的形式。而在jdk1.8 后,为了提升HashMap的效率,引入了红黑树。图大致如下:

HashMap(1.8)源码分析_第1张图片

二、源码分析

2.1、常量

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

DEFAULT_INITIAL_CAPACITY:初始容量,初始的数组大小,默认为16。

static final int MAXIMUM_CAPACITY = 1 << 30;

MAXIMUM_CAPACITY:数组的最大容量,当数组扩容扩至该值时就无法再扩大了。

static final float DEFAULT_LOAD_FACTOR = 0.75f;

DEFAULT_LOAD_FACTOR:默认的负载因子,当数组中数据的数量超过负载因子和当前容量的乘积时,数组将会进行扩容至原来的2倍。即默认情况下,数据量达到数组大小的3/4时,数组就会扩容。

static final int TREEIFY_THRESHOLD = 8;

TREEIFY_THRESHOLD:链表转红黑树的阈值。即当链表长度大于等于8时,数据结构会转变成红黑树。

static final int UNTREEIFY_THRESHOLD = 6;

UNTREEIFY_THRESHOLD:红黑树转链表的阈值。即当红黑树节点小于等于6树,数据结构转为链表。

static final int MIN_TREEIFY_CAPACITY = 64;

MIN_TREEIFY_CAPACITY:链表树化的最小值。在调用HashMap中生成红黑树的treeifyBin(Node[] tab, int hash)方法中,会先进行一次判断,如果元素的数量少于MIN_TREEIFY_CAPACITY,会进行数组扩容,而不是树化。

 final void treeifyBin(Node[] tab, int hash) {
        int n, index; Node e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
        //……
        }
    }

2.2、属性

transient Node[] table;

table:用来保存key-value的散列数组。其中,值得一提的是,table数组能引用的对象有两种:Node和TreeNode,分别用来保存链表和红黑树的。他们都是HashMap的静态内部类,继承结构如下:

Node

Hash$Node -- > Map$Entry,详情如下

  • static class Node implements Map.Entry 
  • Map接口中: interface Entry

TreeNode

 HashMap$TreeNode -- > LinkedHashMap$Entry -- > Hash$Node -- > Map$Entry,详情如下

  • static final class TreeNode extends LinkedHashMap.Entry
  • LinkedHashMap类中: static class Entry extends HashMap.Node
  • static class Node implements Map.Entry
  • Map接口中: interface Entry

显然,Node是TreeNode的父类,所以tables数组才能保存TreeNode的引用。

transient Set> entrySet;

entrySet:保存entrySet()的缓存。在调用entrySet()时,会进行判断,若entrySet不为空,则返回entrySet;若为空,则为entrySet创一个EntrySet

    public Set> entrySet() {
        Set> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }

transient int size;

size:map中元素的个数

transient int modCount;

modCount:保存该HashMap对像被修改的次数。每当该HashMap对象的key-value被修改时(put,remove,clear等),modCount ++;modCount是实现HashMap快速失败(fast-fail)的关键元素。

    abstract class HashIterator {
        Node next;        // next entry to return
        Node current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

        HashIterator() {
            expectedModCount = modCount;
            Node[] 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 nextNode() {
            Node[] t;
            Node 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;
        }

    //......
    }

当使用Iterator对HashMap进行迭代时,迭代器会先将modCount的值保存为expectedModCount,在后续使用中,如果预期值与modeCount不等,说明HashMap进行了修改,抛出ConcurrentModificationException异常。要注意的是,只是更改value的值是不会引起快速失败的,迭代时也能正确读取值。

int threshold;

threshold:衡量数组是否需要扩增的一个阈值。threshold = capacity * loadFactor,当size>=threshold的时候,对数组进行扩容。

final float loadFactor;

负载因子,不设的话为默认值(DEFAULT_LOAD_FACTOR)0.75。计算HashMap的实时装载因子的公式为:size/capacity,即默认数据量达到数组的3/4大小时数组扩容。

2.3、方法

 在上述内容中,也多少讲到了一些方法。考虑到个人精力跟篇幅等问题,在这里只简单用注释的方式讲下put方法及涉及到的putVal

 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[] tab; Node p; int n, i;
        // 数组初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 若该桶为空,则创建Node
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {    // 存在节点的情况
            Node e; K k;
            // key为null或key相等,e引用该节点,在后面代码中直接修改value值
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 判断数据结构是否为红黑树
            else if (p instanceof TreeNode)
                e = ((TreeNode)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;
                    }
                    // key为null或已存在,e引用该节点,在后面代码中直接修改value值
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // key已存在(null也可作key),修改value值,并返回旧的value值
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        // 达到扩容阈值,进行扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

 

你可能感兴趣的:(java)