深入JDK源码系列:HashMap详解

继续挖掘 HashMap 背后的故事。

1. 特性

HashMap 是一个双列集合,存储的元素都是 key - value 形式的键值对,使用哈希表作为内部数据结构,不保证存入和取出元素的顺序,允许使用 null 键和 null 值,方法上没有使用 synchronized ,线程不安全,继承自 AbstractMap 类,实现了 Map 接口。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

    private static final long serialVersionUID = 362498820763181265L;
	// ...省略...   
}

2. 内部数据结构

内部是一个 Node 数组,而数组的每一项又是一个单向链表,这个链表用来存储发生哈希冲突的元素,在 Java8 中,如果发生哈希冲突的次数超过了 8 次,就会将链表转化成一颗红黑树

/**
 * 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;

/**
 * Basic hash bin node, used for most entries.  (See below for
 * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
 * 
 * HashMap 中所存储的元素的类型,可以看出我们存储的 key 和 value 都是作为这个 Node 类的两个属性
 * 这个 Node 中由于有一个 next 属性用来指向发生哈希冲突的下一个节点,所以构成了一个单向链表
 */
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;
    }
}

/**
 * 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
 * 
 * 一旦哈希冲突的次数达到了这个门限值(8),会将单向链表转化成一颗红黑树
 * 遍历单链表的时间复杂度是O(n),而遍历一颗二叉树的时间复杂度是O(logn)
 * 这就会将最坏的情况下 HashMap 的查找的时间复杂度从O(n)提高到O(logn)
 */
static final int TREEIFY_THRESHOLD = 8;

/**
 * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
 * extends Node) so can be used as extension of either regular or
 * linked node.
 * 
 * 频繁的发生哈希冲突时所采用的红黑树的类型定义
 */
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;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }
}

3. 一些重要的属性

/**
 * The default initial capacity - MUST be a power of two.
 * HashMap 默认的容量,必须是 2 的整数倍
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
 * 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.
 * HashMap 中 Node 数组的大小最大是 2 的 30 次方
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

/**
 * The load factor used when none specified in constructor.
 * 默认的装载因子,当 HashMap 中元素的个数已经达到了容量 * 0.75f 时,对哈希桶(形象的形容 Node 数组)进行扩容,容量是原来的 2 倍
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

/**
  * The load factor for the hash table.
  *上面是默认的装载因子的大小,这个是实际值
  * @serial
  */
 final float loadFactor;

/**
 * Holds cached entrySet(). Note that AbstractMap fields are used
 * for keySet() and values().
 * 存放的是键值对对象 Entry (其实 Node 节点实现了这个 Map.Entry 接口)
 */
transient Set<Map.Entry<K,V>> entrySet;

/**
 * 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.
 * 
 * 将哈希桶的数据结构由红黑树装换成单向链表的条件:
 * 哈希桶的中的节点树最大为6个的时候,将其装换成单向链表
 */
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.
  * 如果哈希桶的数据结构要转换为红黑树,那么哈希桶最少的数量应达到这个值
  */
 static final int MIN_TREEIFY_CAPACITY = 64;

/**
  * The number of key-value mappings contained in this map.
  * 
  * HashMap 中所存储的键值对的数量,并不是哈希桶的容量
  */
 transient int size;

/**
  * The next size value at which to resize (capacity * load factor).
  * 当数组中下一个元素的个数达到这个门限值的时候,将会 rehash (重新调整数组的大小,并把原数组中的元素都存放到新数组中)
  * 
  * 门限值 = 数组容量 * 装载因子的大小
  * @serial
  */
 int threshold;

4. HashTable 和 HashMap的区别

4.1 继承的父类不同

Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    ...
}

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
	...
}
4.2 线程的安全性不同

Hashtable 中的方法是Synchronize的,而HashMap中的方法在默认的情况下是非Synchronize的。
例如:

/**
 * Returns the number of keys in this hashtable.
 * 
 * 这是 Hashtable 的 size 方法
 * @return  the number of keys in this hashtable.
 */
public synchronized int size() {
    return count;
}

/**
  * Returns the number of key-value mappings in this map.
  * 
  * 这是 HashMap 的 size 方法
  * @return the number of key-value mappings in this map
  */
 public int size() {
     return size;
 }
4.3 是否提供 contains 方法

HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsValue 和 containsKey,因为 contains 方法容易让人引起误解。

Hashtable 则保留了 contains,containsValue 和 containsKey 三个方法,其中 contains 和 containsValue 功能相同。

/**
 * 这是 HashTable 的 containsValue 方法,可以看出它调用的就是 contains 方法,但是这里并没有加 synchronized 同步,是因为...
 */
public boolean containsValue(Object value) {
    return contains(value);
}

/**
 * 是因为 contains 方法上已经加了同步
 * 
 */
public synchronized boolean contains(Object value) {
    if (value == null) {
        throw new NullPointerException();
    }

    Entry<?,?> tab[] = table;
    for (int i = tab.length ; i-- > 0 ;) {
        for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
            if (e.value.equals(value)) {
                return true;
            }
        }
    }
    return false;
}
4.4 key 和 value 是否允许 null 值

其中 key 和 value 都是对象,并且不能包含重复 key,但可以包含重复的 value。
Hashtable 中,key 和 value 都不允许出现 null 值。

HashMap 中,null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null 。当 get() 方法返回 null 值时,可能是 HashMap 中没有该键,也可能使该键所对应的值为 null 。因此,在 HashMap 中不能由 get() 方法来判断 HashMap 中是否存在某个键, 而应该用 containsKey() 方法来判断。

4.5 :哈希值的计算方法不同

Hashtable 直接使用的是对象的 hashCode ,而 HashMap 则是在对象的 hashCode 的基础上还进行了一些变化。

4.6 内部实现使用的数组初始化和扩容方式不同

HashTable 初始大小是11,而 HashMap 初始大小是16。
Hashtable 采用的是 2 * old + 1 ,而 HashMap 是 2 * old。

/**
 * Constructs a new, empty hashtable with a default initial capacity (11)
 * and load factor (0.75).
 * 初始容量默认为 11 ,装载因子是 0.75
 */
public Hashtable() {
    this(11, 0.75f);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值