首先我们知道HashMap是线程不安全的,所以要避免多个线程共享操作
HashMap的数据结构
HashMap主要是通过数组(transient Entry[] table)来存储数据的,通过key得到hash值后找到数组中相对应的位置,如果有冲突,则是用链表来解决的,请看Entry的属性
final K key;
V value;
Entry<K,V> next;
final int hash;
这里的next就是为了哈希冲突而存在的。比如通过哈希运算,一个新元素应该在数组的第10个位置,但是第10个位置已经有Entry,那么好吧,将新加的元素也放到第10个位置,将第10个位置的原有Entry赋值给当前新加的 Entry的next属性。数组存储的是链表,链表是为了解决哈希冲突的,这一点要注意。
先看几个HashMap里的字段
static final int MAXIMUM_CAPACITY = 1 << 30;
DEFAULT_LOAD_FACTOR = 0.75;
static final int DEFAULT_INITIAL_CAPACITY = 16;
:这是用来存储数据的,这个数组同样不会序列化,具体不再累述
transient int size:表示当前存储数据大小
int threshold:阈值,hashMap存放内容数量的临界点,当存放量大于这个值的时候,就需要将table进行扩张,新建一个两倍大的数组,并将老的元素移过去。threshold = (int)(capacity * loadFactor)
final float loadFactor:装载因子,在构造HashMap时指定,用来确定threshold
transient volatile int modCount:记录hashMap结构变化的次数,使用在hashMap的fail-fast机制中(当某个线程获取map的游标后,另一个线程对map做了结构修改的操作,那么原先准备遍历的线程会抛出ConcurrentModificationException异常)。这里注意volatile关键词,它代表modCount这个变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。
再来看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);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
看到注释了吗?
capacity是初始容量,而不是initialCapacity,例如执行new HashMap(9,0.75);那么HashMap的初始容量是16,而不是9
构造函数2:
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
使用指定的初始容量,装载因子为DEFAULT_LOAD_FACTOR,调用构造函数1来完成
构造函数3:
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
初始容量和装载因子均为默认值
构造函数4:
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
putAllForCreate(m);
}
从现有的map构造新的hashMap,初始容量用原map的容量和默认容量中大的那个值
下面让我们看看几个重要的功能实现
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
get函数通过输入key得到value,先通过hash,然后通过indexFor函数得到这个key在table的位置,再遍历链表得到结果
static int indexFor(int h, int length) {
return h & (length-1);
}