HashMap与Hashtable的八点区别(源码解析)

区别
  1. 继承的父类不同
  2. 初始化数组长度,和扩容时的增量不同
  3. 是否允许存储空值不同
  4. 获取Hash值和数组下标的方法不同
  5. 底层数据结构不同
  6. 扩容方法不同
  7. 线程安全问题
  8. 性能存在差距


1. 继承的父类不同
  • HashMap 继承了 AbstractMap,AbstractMap又实现了 Map 接口
  • Hashtable 继承了 Dictionary
/*
 * HashMap源码
 */
//HashMap继承了AbstractMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {}
//AbstractMap又实现了Map接口
public abstract class AbstractMap<K,V> implements Map<K,V> {}
/*
 * Hashtable源码
 */
//Hashtable继承了Dictionary
Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {}


2. 初始化数组长度,和扩容时的增量不同
  • HashMap 默认的初始数组长度是 16,默认的加载因子是 0.75,每次扩容变成之前数组的 2 倍长度。
  • Hashtable 默认的初始数组长度是 11,默认的加载因子是 0.75,每次扩容是之前数组的 2 倍长度加 1。
/*
 * HashMap源码
 */
//HashMap 中的扩容方法resize(),旧数组长度*2得到新数组长度
final Node<K,V>[] resize() {
   ... ...
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
   ... ...
}
/*
 * Hashtable源码
 */
//Hashtable中的扩容方法rehash(),旧数组长度左移1位加1得到新数组长度
protected void rehash() {	
	... ... 
    int newCapacity = (oldCapacity << 1) + 1;
    ... ... 
}


3. 是否允许存储空值不同

  • HashMap 允许value为空,只能有一个key为空。
  • 而Hashtable 的 key,value 都不允许为空,在源码中的 put 方法里限制了如果 value 为空则抛出空指针异常,但是并没有限制 key,它的 hash 值就是 key.hashCode( ),对象为空调用 hashCode( ) 方法会抛出空指针异常。
/*
 * HashMap源码
 */
//HashMap如果key为空,则会放在数组下标为0的Node桶里,根据put方法的流程新传入的key为null的键值
//会覆盖掉之前的,所以最多只会有一个key的为null
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/*
 * Hashtable源码
 */
//Hashtable中hash值就是key的hashCode,对象为空的话hashCoe()方法会报空指针异常,对于value有了约束,为空也会抛出空指针异常
public synchronized V put(K key, V value) {
    ... ...
    if (value == null) {
        throw new NullPointerException();
    }
    ... ...
    int hash = key.hashCode();
    ... ...
}


4. 获取Hash值和数组下标的方法不同
  • HashMap 的 hash( ) 方法会把key的 hashCode 与它的高16位异或,目的是使它的低16位更加散列,减少哈希冲突,提高性能。HashMap 获取数组下标通过 (数组长度 - 1) & hash 计算得到,实际上就是 hash %(数组长度)。
  • Hashtable 的 hash 值由 key.hashCode( ) 得到,数组下标是先把 (hash & 0x7FFFFFFF => 取前31位二进制数,是为了避免出现整型溢出吗?),再对数组长度取模。
/*
 * HashMap源码
 */
//HashMap的hash()方法会把key的hashCode与它的高16位异或
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//HashMap获取数组下标通过 (数组长度 - 1) & hash 计算得到
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    ... ...
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    ... ...
}
/*
 * Hashtable源码
 */
//Hashtable的hash值由key.hashCode()得到,数组下标是先把 (hash & 0x7FFFFFFF) 再对数组长度取模
public synchronized V put(K key, V value) {
    ... ...
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    ... ...
}


5. 底层数据结构不同
  • HashMap 在 JDK1.8 中底层数据结构变成了数组加链表加红黑树,数组长度在大于 8 并 满足一定条件下升级成红黑树 ,比链表在查找性能上更加优秀。
  • Hashtable 底层采用数组加链表的结构。


6. 扩容方法不同
  • HashMap 在扩容时会生成两个链表或者红黑树,依次插入新数组的 当前坐标当前坐标 + 旧数组长度 的位置。 - HashMap 扩容流程
  • Hashtable 在扩容时每个结点依次做取余运算得到新数组下标。
/*
 * Hashtable源码
 */
//Hashtable在扩容rehash()时每个结点的新数组下标为 (e.hash & 0x7FFFFFFF) % newCapacity
protected void rehash() {
    ... ...
    for (int i = oldCapacity ; i-- > 0 ;) {
        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
        	... ...
            int index = (e.hash & 0x7FFFFFFF) % newCapacity;
            ... ...
        }
    }
}


7. 线程安全问题
  • HashMap 不是线程安全的集合,源码中没有进行任何相关处理。
  • Hashtable 是线程安全的,他在 put,remove 等可能存在线程安全的方法上加了 synchronized 关键字,锁住了当前对象。
/*
 * Hashtable源码
 */
public synchronized V put(K key, V value) {}

public synchronized V remove(Object key) {}


8. 性能存在差距
  • HashMap 是线程不安全的,先天性能优于 Hashtable,而且在确定数组下标,hash 方法扩容 等方法中利用位运算来提升计算时的性能,当哈希冲突严重的时候红黑树的结构也会使查找性能更加优秀。
  • Hashtable 采用了 synchronized 修饰 put,remove方法,锁住了当前 Hashtable 的实例化对象,多线程争抢锁激烈时性能会很差,数据结构也更加老旧,有种被舍弃的感觉。
  • Hashtable 基本已经被弃用,需要考虑线程安全的时候会选择 ConcurrentHashMap。ConcurrentHashMap 数据结构和 HashMap 相同,在 JDK1.8 中改为数组加链表加红黑树的数据结构。与 Hashtable 把 synchronized 关键字加到方法上不同,后者采用 CAS(compare and swap)加上 synchronized 锁住某一个 Node结点。当插入删除在 Node 的第一个结点处就能完成操作时,一般直接采用 CAS 而避免使用锁产生不必要开销,只有在必要的时候才会加上 synchronized 关键字锁上当前 Node 结点。



HashMap知识点总结(附源码分析链接)

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值