一:HashMap概念:
HashMap基于哈希表的Map接口的实现,此实现提供所有可选的映射操作,并允许使用null值和null键。Hashmap不是线程安全的,如果想要线程安全的HashMap可以通过Collections类的静态方法synchronizedMap获取线程的HashMap。
二:HashMap的数据结构及其hash冲突解决:
HashMap的底层主要是数组和链表来实现的,它查询速度相当快的主要原因就是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashcode来计算hash值,只要hashcode的值相同,hash值就一样。加入存储的对象很多,那么就有可能出现相同的hash值,也就是出现所谓的hash冲突。为了解决hash冲突,HashMap底层应用链表来解决的。
(需要注意的是:
jdk1.7的时候 java用链表+数组解决hash冲突。而
1.7的示意图
jdk1.8的时候 java以链表+数组+红黑树解决hash冲突。
1.8的示意图)
其一个结点的数据结构是一个Entry类,里面的数据结构类似于c语言的指针,Entry类的如下图:
所以每个结内含有四个值,key、value、next、hash。
三:HashMap的一些名词简介
1.capacity:
容量,java语言将它的默认值设置为16,第一次扩容之后将变为64,之后每次扩容都将变为原来的2倍。capacity的容量均为2的幂。(capacity可以扩容,反之考虑我感觉也可以缩容。)
2.loadFactor:
装载因子,用来衡量hashmap满的程度。loadFactor初始值为0.75,装载因子的计算方法为,size/capacity。
3.size:
数量,表示HashMap中存放的所有结点的个数。
4.threshold:
阀值,表示当前HashMap的size大小,当threshold大于size时java语言便会执行resize操作,resize为扩容操作。阀值的计算方法为,阀值=容量*装载因子,即threshold=capacity*loadFactor。
四:HashMap的四种构造方法。
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);
}
2.自定义容量,装载因子使用java语言默认的0.75。
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
3.容量和装载因子都是用java语言默认的初始值。
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
4.直接传入HashMap源数据对象。
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
五:HashMap中的get,put数据方法
1.put操作,在给数组插入元素时,jdk1.7使用头插法,jdk1.8因为使用红黑树存储,所以使用尾插法。
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;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
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 {
//在这时已经遍历完链表来检查是否符合转化为红黑树操作所以1.8用尾插。
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;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
2.get方法:
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;
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;
}