java8中HashMap的主要结构由数组,单链表和红黑树组成。
HashMap有一个属性load_factor
(加载因子),它控制hashmap在容量达到多满时(加载因子*容量)要进行扩容,默认为0.75。加载因子大可以节省空间,但会增加查询成本。
它的主要属性:
// 默认容量为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//当链表中的节点个数大于这个值时,会变成红黑树。默认值为8
static final int TREEIFY_THRESHOLD = 8;
//当进行resize()时,假如树中节点小于这个值时,用链表代替树,默认值为6
static final int UNTREEIFY_THRESHOLD = 6;
//当链表要被树化时,hashmap的最小容量,否则会被resize()。这个值至少是TREEIFY_THRESHOLD的四倍
static final int MIN_TREEIFY_CAPACITY = 64;
HashMap最底层的结构,每个节点有hash值,key, value, next(用来产生单链表)属性,getKey, getValue, toString, hashCode, setValue, equals方法,equals(Object o)
方法要判断key
和value
都相同才返回true
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 int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
...
}
根据key
拿到对应hash值的方法:
static final int hash(Object key){
int h;
return (key == 0) ? 0 : (h = key.hasCode())^(h>>>16);
}
数组结构:
transient Node<K, V>[] table;
根据所给的capacity返回大于这个capacity的最小的二次幂size,具体解释见http://blog.csdn.net/fan2012huan/article/details/51097331:
为什么容量一定要2的n次幂?
因为在之后的put()
,get()
方法中,会通过(size-1)&hash(对应给定key的hash值)
来找到索引然后再进行操作,这个算式是固定,用这个算式可以确保索引在数组范围之内。当size是2的n次幂时,size-1就是奇数,这样它的最后一位是1,所以最后算出来的索引值最后一位可能为0,也可能为1,否则的话索引的最后一个值只能是0,这样会导致只能利用一半的空间。
static final int tableSizeFor(int cap) {
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, 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);
//本来threshold应该等于size*loadFactor表示结构该扩容的阈值,不过,在构造方法中,并没有对table进行初始化,所以threshold也会在后面的方法中重新赋值
}
//指定容量的构造函数
public HashMap(int initialCapacity){
this(initialCapacity, DEFAULT_LOAD_FACTOR);
//无参构造函数
public HashMap(){
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
//直接放入一个Map,具体见
http://blog.sina.com.cn/s/blog_9b6eb6f90102wvft.html
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}