1、HashMap一般使用场景:
HashMap为key-value对存储方式,允许key或者value的值为null,这也是与HashTable的区别之一,在本人所做的项目中,一般使用HashMap来做系统内部数据缓存,例如:需要通过一个设备的唯一标识来快速找到到设备的实体对象,或者需要快速定位到某一对象进行操作,如果您也有这样的需求,在合适的场景下可以考虑使用HashMap来处理。当然这不只是HashMap的唯一用处。
2、HashMap使用疑问:
HashMap是如何快速根据key值定位到value值的?在使用HashMap的时候,如果多次调用put方法的key值重复,HashMap的底层又是如何处理的?下面逐一分析。
3、HashMap结构:
根据源码分析,HashMap内部其实是由Entry[] table构成,在新建一个HashMap的时候JDK代码如下:
transient Entry[] table;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
由此可见,HashMap的内部其实维持了一个Entry数组,那咱们就来看看Entry的结构:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
从上述代码可以看出,Entry有一个next属性指向下一个Entry实体,咱们可以将其理解为链表结构。整个HashMap类似如此结构:
众说周知,java的每一个对象均有一个hashCode方法,调用该方法可以活得对象的散列值,HashMap内部通过以下代码获取需要存储的对象应该存储在Entry[] table的哪个位置:
int hash = hash(key.hashCode());
4、HashMap的put操作:
整个put操作基本可以分为三个步骤:
获取存储数组索引位置。
遍历特定位置上的Entry链表,是否已经存在key值一样的数据,有则覆盖value值,完成put操作。
向特定位置上的Entry链表添加新put数据,注意,在链表表头添加。具体实现代码如下:
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
------------------以上为第一步骤执行代码------------------------------------
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
----------------以上为第二步骤代码,根据以上代码也就解释了为什么put的时候key一样,value值会被最新的值覆盖掉。通过equals方法判断两个key值是否相等,如果执行了覆盖,则不执行第三步操作。-------------------------
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
--------------以上为第三步骤代码,可以看出,新增了一个Entry实体,并将其next属性指向了操作之前的链表头元素,相当于将新的Entry实体加入到了链表头部--------------
5、HashMap的get操作-- (xhEditor不知道怎么回事,不能选中文字改变字体颜色了。。。):
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;
}
从上面的代码可以看出,与put方法的第一二步类似,首先找到Entry数组索引,再根据key值来获取value值,也是通过key的equals来判断。
6、总结:
HashMap为何可以快速根据key定位到value值,其实就是通过key的hashCode方法快速找到了value值所在的Entry链表,只需要在定位后的Entry链表中寻找value值就可以了,不需要全局全量定位,大大提高了操作效率。
其实个人认为可能HashMap是这种结构有如下原因:
数组本来就是寻址容易,操作困难,包括插入和删除。
链表本来就是寻址困难,操作容易。
HashMap就是用数组来寻找再用链表来进行操作,使二者优势结合。