1 HashMap和HashTable区别:a hashmap允许空值、空键; b hashmap没有进行同步,线程不安全。
2 装载因子过小或者capacity(表示桶数量)过大会影响迭代性能(通俗说法是过于稀疏),迭代性能的时间复杂度为O(capacity+size),size表示条目数量。load factor=size/capacity
3 经过工程实践得出的最优时间复杂度和空间复杂度的装载因子是0.75。相当于10个桶装7.5个条目。
装载因子太小,那么空间复杂度大,并且影响迭代性能;装载因子太大,hash碰撞率高,影响查询的时间复杂度。
4 对hashmap进行结构性修改,如put,remove,需要自己进行同步,否则会出不可预见问题。
5 进行迭代的时候,如果有其他线程对map进行结构性修改,那么会快速失败,抛出ConcurrentModificationException。除非是迭代子自己的remove操作。
?
6 源码解读
6.1接口
实现map接口,继承AbstractMap
?
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
?表示map中的一个元素或条目。
?
?
interface Entry<K,V> { K getKey(); V getValue(); V setValue(V value); boolean equals(Object o); int hashCode(); }
?
?
?
public interface Map<K,V> { int size(); boolean isEmpty(); boolean containsKey(Object key); V get(Object key); V put(K key, V value); V remove(Object key); void putAll(Map<? extends K, ? extends V> m); void clear(); Set<K> keySet(); Collection<V> values(); Set<Map.Entry<K, V>> entrySet(); boolean equals(Object o); int hashCode(); }
?
?
?
6.2?
HashMap实现的两大关键数据结构:
a ??table = new Entry[capacity],table表示桶,实现为条目数组。
b ??static class Entry<K,V> implements Map.Entry<K,V> , ??HashMap.Entry<K, V>是条目的实现。
他的特点是每个条目都指向下一个条目,相当于每个桶都引用了一个条目链表,使用链式法解决hash碰撞。
?
?
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash;?
?
6.3 关键操作
put
?
?
public V put(K key, V value) { if (key == null) return putForNullKey(value); 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; } } modCount++; addEntry(hash, key, value, i); return null; }?
?
首先对key进行hash找到桶(hash函数为hashcode & (capacity-1)),遍历桶的条目链表,如果有,直接替换返回旧值;否则修改结构性修改次数modCount,并添加条目。
?
?
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }
?
?
addEntry直接将新条目插到桶的链表首节点。如果size大于阀值threshold(threshold=load factor* capacity),那么扩容。扩容将对hashmap进行重新散列,比较消耗性能,所以使用hashmap要考虑使用场景,设置合理的capacity(默认为16),尽可能减少扩容发生。
关于addEntry的线程安全性,从代码可以明显的看出高并发下会出现的问题。那就是同时put的时候还有数据丢失,链表头节点会相互覆盖。
?
?
?
?
?
?
?
?