HashMap子类
HashMap是Map接口之中最为常见的一个子类,该类的主要特点是无序存储,通过Java的文档首先来观察一下HashMap子类的定义形式:
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
该类的定义继承形式符合之前的集合定义形式,依然提供有抽象类并且依然需要重复实现Map接口。
范例:观察Map集合的使用
import java.util.HashMap;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
Map<String,Integer> hashMap = new HashMap<String,Integer>();
hashMap.put("one",1);
hashMap.put("one",100); //重复数据
hashMap.put("two",2);
hashMap.put(null,0); //key为空
hashMap.put("zero",null); //value为空
//根据key找value
System.out.println(hashMap.get("one"));
System.out.println(hashMap.get(null));
System.out.println(hashMap.get("ten")); //不存在的key
}
}
100
0
null
以上的操作形式为Map集合使用的最标准的处理形式,通过代码可以发现,通过HashMap实例化的Map接口里面可以发现:
- key或者value可以保存null的数据;
- 相同的key名字重复保存,之前的key会被后面保存的key替换。
但是对于Map接口中提供的put()方法本身是提供有返回值的,那么这个返回值指的是在重复key的情况下返回旧的value;
//put方法
public V put(K key, V value)
范例:观察put()方法的返回值
import java.util.HashMap;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
Map<String,Integer> hashMap = new HashMap<String,Integer>();
System.out.println(hashMap.put("one",1)); //key不重复
System.out.println(hashMap.put("one",100)); //key重复
}
}
null
1
清楚了HashMap基本功能之后,下面研究一下HashMap之中的源代码:
//无参构造
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//里面的参数
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//put()方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true); //调用了putVal,对key进行hash处理(生成独一无二的hash码代表它)
}
//putVal方法,提供了Node的节点类进行数据的保存
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 {
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(); //这里调用了resize()方法,可以进行容量的扩充
afterNodeInsertion(ev ict);
return null;
}
面试题:在进行HashMap的put()操作时如何实现容量扩充?
- 初始化容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 在HashMap类里面有一个“DEFAULT_INITIAL_CAPACITY ”常量作为初始化的容量配置,这个常量的容量配置为16个元素,默认可以保存最大内容是16。
- 当保存的内容的容量超过了一个阈值(static final float DEFAULT_LOAD_FACTOR = 0.75f;),相当于“容量*阈值”(如果容量是100,那么阈值就是75),当容量超过了结果时就会进行容量的扩充。
- 在进行扩充的时候HashMap采用的是成倍的扩充模式,即每一次扩充两倍的容量。
面试题:请解释HashMap的工作原理
- 在HashMap之中进行数据存储的依然是利用了Node类完成的,那么这种情况下就证明可以使用的数据结构只有两种:链表(时间复杂度:“O(n)”)、二叉树(时间复杂度:O(logn)“”);
- 从JDK1.8开始,HashMap的实现发生了改变,因为其要适应于大数据时代的海量数据问题,所以对于其存储发生了变化,并且在HashMap类的内部提供了一个重要的常量:( static final int TREEIFY_THRESHOLD = 8;)在使用HashMap进行数据保存的时候如果保存的数据个数没有超过阈值8,那么会按照链表的形式存储,如果超过了则将链表转为红黑树以实现树的平衡,并且利用左旋右旋保证数据的查询性能。