一、基本介绍
- HashMap是一个散列表,其主要作用是来存放键值对
的映射。 - 其实现了Map、Cloneable、Serializable接口,继承了AbstractMap类
- HashMap是线程不安全的,线程安全的有HashTable和ConcurrentHashMap。但是HashTable基本被淘汰,因为它内部的方法都被synchronized修饰过了,所以其效率会比HashMap低很多。同时HashTable的并发性也不如ConcurrentHashMap,因为ConcurrentHashMap采用的是分段锁的思想。
HashMap的key跟value都可以为null,但是key只能有一个。
二、底层数据结构
数组 + 链表
数组结构的声明:transient Node<K,V>[] table;
Node类含有的属性:
final int hash; //key的hash值
final K key;
V value;
Node<K,V> next;//指向下一个节点的指针
三、HashMap的参数介绍
int initialCapacity
:初始化容量
默认初始化容量为16,之后每次扩容都会扩容为原来的两倍
如果指定了初始容量,
final float loadFactor
:负载因子
默认值是0.75
int threshold
:阈值
threshold = loadFactor * initCapacity
int size
:当前map中实际键值对对数量
当size > threshold时,就会调用resize()方法进行扩容
四、初始化过程
调用无参构造器初始化
public HashMap() {
//负载因子设置为默认值
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
调用有参构造器初始化 ```java //初始化容量 public HashMap(int initialCapacity) { //会调用另外一个构造器初始化 this(initialCapacity, DEFAULT_LOAD_FACTOR); } //初始化容量和负载因子 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//容量大于2^30时,会将容量设置为2^30 if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//isNaN,NaN = Not a Number,传入的float可能不是一个数字 //float = 0.0f / 0.0f时不会报错 if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor; //此处将阈值设为了大于等于initialCapacity的最小的2的幂次方 //整个初始化过程,并没有设置容量参数 this.threshold = tableSizeFor(initialCapacity); } /**
- Returns a power of two size for the given target capacity.
*/
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;
}
```
五、插入过程
```java //put方法 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
//hash方法,目的是为了防止传入对象的hash方法实现较差,这里做一个扰动 static final int hash(Object key) { int h; //会调用对象的hashCode方法,再进行扰动 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
//putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node
六、扩容过程
- HashMap每次扩容都会将容量扩大为原来的2倍,HashMap的容量最大为2^30,因为int型的最大值为2^31 - 1。
由于通过构造函数初始化时,并没有设置初始容量,而是将阈值设为了我们传入的初始容量参数。这些都会在第一次扩容操作时,设置好容量,并将阈值设置为capacity * loadFactor
七、使用注意点
如果提前知道了会往HashMap中存放的元素数量,可以事先定义好HashMap的容量,可以避免频繁扩容导致的性能下降。
- 如果知道预期会使用的容量大小expectedCapacity,可以通过(expectedCapacity / loadFactor) + 1计算出应该将初始容量设置为多少,如果不确定初始值大小,就设置16。
例如想放12个元素,那么(12 / 0.75)+ 1 = 17,HashMap最终会创建一个容量为32的数组,虽然浪费了空间,但是不会因为扩容而损失性能,不过HashMap本身就是一个空间换时间的结构。