好久没有写博客来总结了,这两天准备花点时间写写这几天看的些框架,谈谈自己的理解。
HashMap 使用的理解
HashMap 以前的理解就是一个集合,里面存放着键值对(key-value),然后键不能重复,值可以重复。可以用它来存放 put(Key ,Value)
,取出get(key)
。
HashMap 源码理解
我认为学习一个东西最好把他的原理理解好,这样不就是对记忆和使用的有莫大的用处。所以我们就开始看看这神秘的HashMap 吧。
首先,我们知道HashMap 是用来存储数据的,那么我们了解它,就应该从数据结构开始。HashMap 内部采用的是数组+链表的形式进行数据的存储。
可以看见 HashMap 中我们首先是有一列数组,然后每个数组又对应了一个Entry的链表(这里的Entry其实就是我们的键值对的包装,当我们获取map的size时,有多少个Entry,size就为多少了)。那我们知道了内部存放情况后就可以开始真正的看源码了。
public HashMap() {
table = (HashMapEntry<K, V>[]) EMPTY_TABLE;
threshold = -1}
这里可以看到初始化的时候我们用默认的size创建了一个数组,当然你也可以自己确定数组大小public HashMap(int capacity), public HashMap(int capacity, float loadFactor)
以及它的加载因子。这个数组就是我们开始所说的数组。
那好,现在我们就开始看看是如何存数据进去的,毕竟这才是我们最想了解的。
public V put(K key, V value) {
//判断key是否为null,如果为null则直接调用 putValueForNullKey(value)
if (key == null) {
return putValueForNullKey(value);
}
//生成key的hashcode
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
//把hashcode的值与 (tab.length - 1)经行位运算,然后通过映射找到该Entry应该位于数组的哪一个上
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
//链表的查找,如果找到的话,就会用传进来的新的value换掉旧的value
if (e.hash == hash && key.equals(e.key)) {
preModify(e);
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
// No entry for (non-null) key is present; create one
//如果在对应的数组元素中没有该key的Entry,就会在链表中添加填一个。
modCount++;
if (size++ > threshold) {
tab = doubleCapacity();
index = hash & (tab.length - 1);
}
addNewEntry(key, value, hash, index);
return null;
}
这里我们就主要看看put(key,value)
,其实主要的我的注释都解释了。不过这里,我想在强调两个地方。一个就是putValueForNullKey(value)
这里比较简单,我也没有贴源码,就是把它添加在数组的第一个元素上。当然,再添加之前他会先查看以前是否又Entry,有的话就会换掉再把旧的返回回去。另一个就是if (e.hash == hash && key.equals(e.key))
,在这里,就是我们判断key是否在map中的逻辑了。 不过值得注意的是这里有两个限定条件,一个是hash值,另一个是equals。如果以前有接触的,我们知道java官方推荐我们复写equakls的时候是需要保证返回的hashcode的值是相同的。即在这里,我们是需要hash值相同的key对象才是相同的key。
不过还值得提起的是,就是当我们 put(key,value)
时候,其实是把hash值的高字节和数组长度-1就行位运算,以保证返回的值在数组中。
我想put差不多就是这样,接着我们就看看HashMap是如何取出的吧。
public V get(Object key) {
//首先还是判断key是否为null,如果为null,则返回开始存在0位置的Entry
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
return e == null ? null : e.value;
}
//不为null,则获取hash值,在位运算找到所属的那个数组元素。接着对链表进行遍历,根据hash值寻找,找到的话就返回value,否则返回null。
int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
return e.value;
}
}
return null;
}
话说要是理解了put(key,value)
,那么 get(key)
的理解就是很容易的了,同理,remove(key)
等方法也是大同小异的。
总结
HashMap 是可以存放<null,value>
的,不过这里存了null,也是只能存一个,再次存null就会覆盖过以前的。HashMap 在根据key生成hash之后,是根据某种算法来选择数组中的元素的。让后的存储和取出则是根据key值是否相同再经行存储或者返回的。