本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
最普通的想法
Hashtable
本身是同步的,不支持null
的键和值- 由于本身支持
synchronized
那么同步就导致的了很多的性能上的开销
- 由于本身支持
HashMap
是我们用的最广泛的哈希表实现,- 行为上大致上与HashTable一致,
- HashMap
与
Hashtable最大的区别在于HashMap不是同步的,支持
null`键和值。 - 但是正常情况下
HashMap
进行put
或者get操作,可以达到常数时间O(1)的性能 - 所以我们在绝大部分利用键值对存取场景的首选
TreeMap
是基于红黑树的一种提供顺序访问的Map
- 由于本身是基于红黑树的数据结构所以和
HashMap
不 同 TreeMap
的get、put、remove
操作都是O(log(n))
的时间复杂度,- 具体顺序可以由指定的Comparator来决定,或者根据键的自然顺序来判断。
- 由于本身是基于红黑树的数据结构所以和
> 在面试中也会经常被问到,HashMap出现无限循环占用CPU等问题bugs.java.com/bugdatabase…
有序的Map
LinkedHashMap
属于 遍历的顺序就是插入的顺序LinkedHashMap
本身内部是由一个双向链表构成的- 我们可以用它来实现一个
LRU
算法
public class test {
public static void main(String[] args) {
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>() {
@Override
protected boolean removeEldestEntry (Map.Entry<String, String> eldest){
return size() > 2;
}};
map.put("a","1");
map.put("b","2");
System.out.println("第一次遍历:");
map.forEach((k,v) -> {
System.out.println(k + " : " + v);
});
// 访问前第一个元素
map.get("b");
System.out.println("第二次遍历:");
// 第二次输出
map.forEach((k,v) -> {
System.out.println(k + " : " + v);
});
// 此时会触发删除c-3
map.put("c","3");
System.out.println("第三次遍历:");
// 第三次输出
map.forEach((k,v) -> {
System.out.println(k + " : " + v);
});
}
}
复制代码
- 输出如下
TreeMap
put
源码解析
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
复制代码
- 其中可以看到
hashcode
和equals
是保持一个约定的
HashMap
put
源码解析
- 可以看到重点在
putVal
中
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
复制代码
- 首先是判空,如果为
null
- 那么就会
resize()
进行初始化,主要是扩容的工作 - 在放置新的键值对的过程中就会发生扩容
if (++size > threshold) resize(); 复制代码
- 放在哈希表的位置汇中使用的是位运算去定位
tab[i = (n - 1) & hash] 复制代码
hash()
方法- 是将高位数据移位到低位进行疑惑运算
- 因为这些数据计算出的哈希值差异主要在高位
- 而其中的哈希寻址是忽略容量以上的高位的,有效的避免了哈希碰撞
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } 复制代码
- 小谈
resize()
- 那么就会
resize()
最大容量是1<<30
也就是2^30
newThr = oldThr << 1
其中限定值是以倍数增长的- 当元素个数超过了限定值,那么久调整
Map
的大小