转载自https://github.com/pzxwhc/MineKnowContainer/issues/21
HashSet
阅读本文之前建议先弄懂 HashMap:Java 集合:HashMap(put方法的实现 与 哈希冲突),因为无论是 HashSet 还是 HashTable 本质上来说还是基于 HashMap的。
HashSet 实现了 Set 接口,而 Set 接口又继承于 Collection 接口。 它可以用于存储数据。
Collection 是 Java 集合的一个根接口,JDK 没有它的实现类。 内部仅仅做 add(),remove(),contains(),size() 等方法的声明。Set 接口不可以有重复的值,但是可以有空值。并且没有做同步处理。
HashSet 的实现就是基于 HashMap。HashSet 的很多方法就是调用 HashMap 的对应的方法。
我们知道,HashMap 是键值对的形式,HashSet 没有什么键值对的概念,那它内部具体又是怎么实现的呢?
其实就是 HashSet 用了一个空对象,如 private static final Object PRESENT = new Object();
- 用这个空对象来填充 HashMap 的 value 域
- 用这个空对象来填充 HashMap 的 value 域
- 用这个空对象来填充 HashMap 的 value 域
如下面的 add 方法:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
所以从这里就可以看出:
- 利用了 HashMap 实现。HashSet 的方法就是调用 HashMap 的对应的方法。
- 用空对象来填充 HashMap 的 value 域
HashTable
HashTable 是 HashMap 的线程安全版本。 内部的实现几乎和 HashMap 一模一样。例如:
同样的有一个数组:
private transient Entry<?,?>[] table;
对于 put 方法:
public synchronized V put(K key, V value) {
......
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
这里可以看到, for 循环表示如果出现了哈希冲突,那么就放在最后一位。因为不断的进行 entry = entry.next
,直到 entry != null
。需要注意的是,JDK8 中的 HashMap 如果有很多哈希冲突的话,那么是可能会把链表变成红黑树以此来提高效率。但是这里 HashTable 并没有这样做。
另外,从这里也可以看出,HashTable 实现多线程同步的主要方式是通过加 synchronized 关键字。
另外,对于 get 方法:
@SuppressWarnings("unchecked")
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
这里最明显的就是 synchronized,其实还有很多其他的方法用的也是 synchronized。get 方法的处理也是先根据 key 定位到 table 的某一个位置,最后再 for 循环拿到该值(因为可能出现了哈希冲突,所以要 for 循环)。
ConcurrentHashMap
对于 ConcurrentHashMap 并没有深入研究过源码。但是了解一些如下的概念:
-
ConcurrentHashMap 也是 HashMap 的线程安全版本,和 HashTable 一样。它支持完全并发的读和一定程度的并发的写。所以说会比 HashTable 的性能要高出很多。
-
实现:ConcurrentHashMap允许多个修改操作并发的进行,关键在于使用了锁分离的技术。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。但是有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而不仅仅是某个段。
-
HashTable 和 HashMap 最主要的区别:HashTable 每次执行同步的时候都是需要锁住整个结构。ConcurrentHashMap 正是为了解决这个问题而诞生的。ConcurrentHashMap 默认会把 hash 表分成16个桶。比如说 get,put,remove等操作就是锁住当前自己的桶。