HashMap源码JDK1.8

尚存在的疑惑

1:类的描述中,若有多个key的hashcode相同时,会降低hashmap的效率,文档说对key排序,我没懂。
2:fail-fast不能被保证,什么意思。
HashMap在jdk1.7是数组加链表 头插法
HashMap在jdk1.8是数组加链表加红黑树 尾插法
在这里插入图片描述

类的描述

Hash table based implementation of the Map interface.
This implementation provides all of the optional map operations, and permits null values and the null key.
(The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.

  • HashMap是基于哈希表的Map接口的实现。
  • 可以存放空键和空值
  • HashMap几乎等于HashTable,区别仅在于HashMap允许空值且是不同步的。
    This implementation provides constant-time performance for the basic operations (get and put), assuming the hash function disperses the elements properly among the buckets.
    Iteration over collection views requires time proportional to the “capacity” of the HashMap instance (the number of buckets) plus its size (the number of key-value mappings).
    Thus, it’s very important not to set the initial capacity too high (or the load factor too low) if iteration performance is important.
  • get()和set()方法是耗时常数空间的操作。
  • 遍历集合视图的操作所消耗的时间与桶的个数和key-value映射的数量有关。
  • 初始容量不要设置的太大,加载因子也不要太小,不然会影响遍历的速度。
    An instance of HashMap has two parameters that affect its performance: initial capacity and load factor.
    The capacity is the number of buckets in the hash table, and the initial capacity is simply the capacity at the time the hash table is created.
    The load factor is a measure of how full the hash table is allowed to get before its capacity is automatically increased.
    When the number of entries in the hash table exceeds the product of the load factor and the current capacity, the hash table is rehashed (that is, internal data structures are rebuilt) so that the hash table has approximately twice the number of buckets.
  • 初始化一个HashMap有两个很重要的参数:“初始化容量”和“加载因子”会影响HashMap的表现。
  • 容量就是哈希表的桶的个数。初始化的容量就简单的定义为哈希表创建时的容量。
  • 加载因子是一种在HashMap容量自增之前能达到多满的标准。
  • 当键值对的个数大于加载因子和当前容量的乘积时,哈希表会rehash,扩容之后,hash表的容量是之前的两倍
    As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs.
    Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put).
    The expected number of entries in the map and its load factor should be taken into account when setting its initial capacity, so as to minimize the number of rehash operations.
    If the initial capacity is greater than the maximum number of entries divided by the load factor, no rehash operations will ever occur.
  • 默认加载因子为0.75。这个值均衡了时间和空间的复杂性。
  • 加载因子值设置的过高会降低空间开销,但是会增加查询消耗。(这种情况反映在大部分操作上,包括get()和put())。
  • 在创建HashMap设置初始容量时,就应该考虑到要存放的键值对的数目和加载因子。尽量避免rehash操作的次数。
  • 若初始容量大于最大的键值对数目/负载因子,就不会发生rehash操作。
    If many mappings are to be stored in a HashMap instance, creating it with a sufficiently large capacity will allow the mappings to be stored more efficiently than letting it perform automatic rehashing as needed to grow the table.
    Note that using many keys with the same hashCode() is a sure way to slow down performance of any hash table.
    To ameliorate impact, when keys are Comparable, this class may use comparison order among keys to help break ties.
  • 若要向HashMap实例中存放大量的键值对,最好先将初始容量设置大点。而不是一次次rehash扩容。
  • 使用许多Hash值相同的key会减慢哈希表的操作。
  • 为了改善影响,当键是可比较的时,HashMap类会比较key的顺序去打破这种束缚。
    Note that this implementation is not synchronized.
    If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally.
    (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.)
    This is typically accomplished by synchronizing on some object that naturally encapsulates the map.
  • HashMap是非同步的。
  • 如果多线程同时访问一个hashmap,并且至少一个线程结构性的改变了这个表,则必须在外部同步这些线程。
  • 结构化改变是指删除或者增加一个或多个映射关系的操作。仅仅修改Hashmap中已经存在的key对应的value这种不算结构性改变。
  • 这种同步一般由封装了该map的对象来实现。
    If no such object exists, the map should be “wrapped” using the Collections.synchronizedMap method.
    This is best done at creation time, to prevent accidental unsynchronized access to the map:
    Map m = Collections.synchronizedMap(new HashMap(…));
  • 若不存在这样的对象,这个map最好由Collections.synchronizedMap方法包装。
  • 最好在创建时就包装。
    The iterators returned by all of this class’s “collection view methods” are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator’s own remove method, the iterator will throw a ConcurrentModificationException.
  • 此类的所有集合视图方法返回的迭代器都是fail-fast得的。若迭代器创建后,map被结构性改变了,除非使用迭代器自身的remove方法,迭代器会返回一个ConcurrentModificationException异常。
    Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
    Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification.
    Fail-fast iterators throw ConcurrentModificationException on a best-effort basis.
  • 因此面对并发修改,迭代器会迅速失败,而不是冒着风险在以后的不确定时间发生错误。
  • 注意,迭代器的fail-fast行为不能得到保证。在存在非同步并发修改时,迭代器不能完全保证抛出异常
  • 但是迭代器会尽其最大的努力抛异常。
    Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.
    This class is a member of the Java Collections Framework.
  • 因此,在一个程序中,依赖该异常去保证程序的正确性是不对的。fail-fast方法只能被用在检测bug上。

静态类变量

The default initial capacity - MUST be a power of two.

  • 默认初始容量。必须是2的次方。默认值是16.
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

The maximum capacity, used if a higher value is implicitly specified by either of the constructors with arguments. MUST be a power of two <= 1<<30.

  • 最大容量 2的30次方。若构造器中指定的容量比该值还大,则容量被maximum capacity替换。
static final int MAXIMUM_CAPACITY = 1 << 30;e

The load factor used when none specified in constructor.

  • 默认的加载因子。
static final float DEFAULT_LOAD_FACTOR = 0.75f;e

The bin count threshold for using a tree rather than list for a bin.
Bins are converted to trees when adding an element to a bin with at least this many nodes.
The value must be greater than 2 and should be at least 8 to mesh with assumptions in tree removal about conversion back to plain bins upon shrinkage.

  • 链表转化为红黑树的临界值。
  • 当一个元素被放入到至少有8个元素的桶时,链表会变为红黑树。
static final int TREEIFY_THRESHOLD = 8;

The bin count threshold for untreeifying a (split) bin during a resize operation.
Should be less than TREEIFY_THRESHOLD, and at most 6 to mesh with shrinkage detection under removal.

  • 从红黑树转化为链表的临界值。
static final int UNTREEIFY_THRESHOLD = 6;

The smallest table capacity for which bins may be treeified.
(Otherwise the table is resized if too many nodes in a bin.)
Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts between resizing and treeification thresholds.

  • 从链表转换为红黑树的先天条件。hash表的容量必须最小是64,否则若桶中的元素过多,则采取扩容,而不是变为红黑树。
static final int MIN_TREEIFY_CAPACITY = 64;

静态内部类

Basic hash bin node, used for most entries.
(See below for TreeNode subclass, and in LinkedHashMap for its Entry subclass.)

  • HashMap的节点类型。
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;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

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

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

成员变量

The table, initialized on first use, and resized as necessary.
When allocated, length is always a power of two.
(We also tolerate length zero in some operations to allow bootstrapping mechanics that are currently not needed.)

  • 表,第一次使用时初始化,必要时调整大小。
  • 分配时,长度都是2的次方。
  • 有时也会容忍表的长度为0.
transient Node<K,V>[] table;

核心方法

Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
More formally, if this map contains a mapping from a key k to a value v such that (keynull ? knull : key.equals(k)), then this method returns v; otherwise it returns null. (There can be at most one such mapping.)
A return value of null does not necessarily indicate that the map contains no mapping for the key; it’s also possible that the map explicitly maps the key to null. The containsKey operation may be used to distinguish these two cases.

  • 返回key对应的value值,若表中不存在key的映射,则返回null。
  • 若get方法返回null,则有两种情况,一种是没有key,一种是有key,但是key的对应value是null。可以用containsKey区分这两种情况。
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

步骤:
1:先获取key的hash值。
2:根据key和key的hash值来通过getNode获得其对应的节点,hash值用来获取桶的位置,key用来找到桶中的元素。
3:若桶中没有对应元素,则返回null,否则返回同种对应元素的value值。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

1:先获取key的hashcode,值为h。
2:再将h的值无符号右移16位(高位补0)。
3:将h和其移位后的值异或。
4:若key为null,则返回null。否则返回异或后的值作为hashcode。

  • 表不为空,表的长度大于0并且桶中的第一个元素不为null。
  • (n-1)& hash。 数组的长度是2的n次方,这样的好处是(n-1)的二进制表达式变为…000…000111…111,最后几位都是1,这样和hash进行与操作时只会保留hash值最后面几位二进制位,大小在0-(n-1),这刚好就是数组的索引。
  • 低16位和高16位异或的原因是,若hash值的高位有变化,而低位没有变化,则与(n-1)相与时,会只取到hash值低几位,则得到同样的桶,就会使发生冲突的可能性变大。
  • 同一个同中的元素的hash值并不一定相同,只是经过取低几位后的(与(n-1)相与)数组下标值相等。
  • 若桶中第一个元素的hash相同,并且(key的值相同),就返回第一个元素。
  • 每个对象的Hash值是唯一的,这里的传入的hash值已经是高16位和低16位异或的了,不是原本的唯一hashcode了。
  • ((k = first.key) == key || (key != null && key.equals(k)))保证的是像
    String str = new String(“123”); String str2 = “123”;用等号肯定是不相等,但是用equals是相等的。
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) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            //若桶中,且接下来的节点不是null,则判断该节点是属于红黑树还是链表。
            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);
            }
        }
        //table为空,table长度为0,对应的桶为空,或者桶不空,但是就是没有想要的key。
        return null;
    }

Associates the specified value with the specified key in this map.
If the map previously contained a mapping for the key, the old value is replaced.

  • 若map中已经有了key,则替换key对应的value值。
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;
        //若表为null或长度为0,则resize()扩容,扩容后得表赋与tab,n是扩容后表的大小。
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //i是桶的位置,若桶为空,将节点插入到桶中
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        //若桶不为空
        else {
            Node<K,V> e; K k;
            //若桶中的第一个元素的值和要放入的元素相同,将e指向桶中第一个元素
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //若桶中第一个元素和要放入的元素不同,且是红黑树节点,则按红黑树的方式插入节点
            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);
                        //若插入前,桶中有8个,则插入后为9个,则救护转化为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //若找到key相同的节点,用p标记
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //若放入的节点的key和已经在map中存在的key相同,返回老值
            //onlyIfAbsent,如果是true,则不改变原来的值
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                //若要求改变原来的值,或者老值为null
                //则将新value赋值给已经存在的key对应的value
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值