上次对hashMap进行了源码分析我们知道hashMap是非线程安全的,其次hashMap允许key值为null,且只有一个。这次我们对HashTable进行分析,看看hashMap和HashTable的区别在哪。
1.HashTable是线程安全的
synchronized void clear()
synchronized Object clone()
boolean contains(Object value)
synchronized boolean containsKey(Object key)
synchronized boolean containsValue(Object value)
synchronized Enumeration<V> elements()
synchronized Set<Entry<K, V>> entrySet()
synchronized boolean equals(Object object)
synchronized V get(Object key)
synchronized int hashCode()
synchronized boolean isEmpty()
synchronized Set<K> keySet()
synchronized Enumeration<K> keys()
synchronized V put(K key, V value)
synchronized void putAll(Map<? extends K, ? extends V> map)
synchronized V remove(Object key)
synchronized int size()
synchronized String toString()
synchronized Collection<V> values()
从源码可以看到hashTable相关的方法都是加上了synchronized 同步锁,多线程访问的时候需要拿到同步锁才能访问。
java开发:集合(四):hashMap源码解析
Java开发:java多线程(一):synchronized 对象锁和类锁的区别
2.hashTable的key值和value不能为空
hashTable:
public synchronized V put(K key, V value) {
//确保value不为空
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
int hash = hash(key); //计算哈希值
int index = (hash & 0x7FFFFFFF) % tab.length; //根据哈希值计算在数组中的索引
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) { //如果对应的key已经存在
V old = e.value;
e.value = value; //替换掉原来的value
return old;
}
}
//否则新添加一个Entry
modCount++;
if (count >= threshold) { //判断数组中的Entry数量是否已经达到阈值
rehash(); //如果达到了,扩容
tab = table;
hash = hash(key); //重新计算哈希值
index = (hash & 0x7FFFFFFF) % tab.length; //重新计算在新的数组中的索引
}
//创建一个新的Entry
Entry<K,V> e = tab[index];
//存到对应的位置,并将其next置为原来该位置的Entry,这样就与原来的连上了
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
hashMap
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
//计算key的哈希值
int hash = hash(key);
//根据key哈希值和数组长度计算出存储下标
int i = indexFor(hash, table.length);
//历遍table[i]整个链表,如果出现key重复的则覆盖value值,然后return结束程序
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++;
//上面的for循环结束后没有发现key没有重复则会执行这个方法
addEntry(hash, key, value, i);
return null;
}
private V putForNullKey(V value) {
//获取数组的第一个位置元素,历遍链表找到key为null的键值对然后覆盖value值
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
//结束方法
return oldValue;
}
}
modCount++;
//上面的for循环结束后没有发现key为null的元素则会执行这个方法
addEntry(0, null, value, 0);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//创建节点
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
//根据bucketIndex获取数组指定位置的元素,e 是链表的头结点
Entry<K,V> e = table[bucketIndex];
//创建节点放到数组中,这时候该节点成为头结点,同时它的next指向上一个头结点e
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
对比hashMap和hashTable的put方法可以发现有如下区别:
1.hashMap会对key判断null值,为null的话直接把元素插到table[0]对应的链表,然后历遍链表查看有没有key为null的元素,有则覆盖,无则添加。因此hashMap只能有一个key值可以为null
2.hashTable则是先对value值判断null值,为null直接抛出空异常。接着根据key计算哈希值找到在数组的映射索引,那如果key=null的话计算哈希值的时候也直接会报空异常。因此hashTable的key和value都不能为空,hashMap的key和value都可以为空,且key只能有一个为空。