Map实现的双列集合

本文详细介绍了Java中Map接口的几种实现类,包括HashMap、HashTable和ConcurrentHashMap的异同。HashMap是非同步的,线程不安全,而HashTable线程安全但效率较低。ConcurrentHashMap在多线程环境下提供了更高的效率,通过锁分离或CAS+Synchronized实现并发安全。此外,文章还探讨了它们的底层实现、遍历方式和扩容策略等细节。
摘要由CSDN通过智能技术生成

 Map接口实现类体系图

 

 

HashMap和HashTable的异同

  • 继承的父类不同

       HashMap继承自AbstractMap类,Hashtable继承自Dictionary类,但二者都实现了Map接口。Dictionary类是一个已经被废弃的类(见其源码中的注释)。

  • 底层实现

       HashMap:Node[ ]数组+链表+红黑树

       HashTable: Entry[ ]数组+链表

        这里还要说一些东西,

        HashMap中新添加节点在某一索引位产生哈希冲突,会优先放置在链表的末尾,因为当链表达到一定长度就要变成红黑树,此后在该索引位新添加的节点就会根据树化方式放置在末尾

       HashTable中新添加的节点在某一索引位产生哈希冲突,会优先放置在链表的头节点,也就是说每次冲突都会让索引位先指向新添加的节点,而后让新添加节点内的next指针指向之前链表的头节点,完成后新添加的节点就会变成链表头节点。

  • 线程是否安全

        HashMap线程不安全

        HashTable线程安全

javadoc中关于hashmap的一段描述如下:

    此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
    Hashtable 中的方法大多是Synchronize的,而HashMap中的方法在一般情况下是非Synchronize的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。HashTable实现线程安全的代价就是效率变低,因为会锁住整个HashTable,而ConcurrentHashMap做了相关优化,因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定,效率比HashTable高很多。
   HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。

       hashmap的put方法调用putVal()方法,假如A线程和B线程同时对同一个数组索引位调用putVal()方法,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作,造成A的写入操作丢失。故解决方法就是使用ConcurrentHashMap。

      HashMap的迭代器(HashIterator)是fail-fast的,故当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException异。

 

       Hashtable的迭代器(enumerator)迭代器不是fail-fast的。但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

  • 遍历方式

       HashMap只支持Iterator遍历,而HashTable支持Iterator和Enumeration两种方式遍历

  • contains方法,toString()方法

        HashMap是没有contains方法的,但有containsValue和containsKey方法,没有重写toString

        hashtable则保留了contains方法,同containsValue,并包括containsValue和containsKey方法,重写了toString()方法

  • 是否允许null值

       Hashmap是允许key和value为null值的,用containsValue和containsKey方法判断是否包含对应键值对;HashTable键值对都不能为空,否则报空指针异常。

  • 计算索引值方式

        为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。

        ①:HashMap有个hash()方法重新计算了key的hashCode,目的是为了有效避免hash冲突:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

        注意这里计算hash值,先调用hashCode方法计算出一个hash码,再将hash码与右移16位的hash码相异或,从而得到新的hash值。

       在求hash值对应的位置索引时,index = (n - 1) & hash。将哈希表的大小固定为了2的幂,因为是取模得到索引值,故这样取模时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
        ②:Hashtable通过key的hashCode()方法得到的hash值就为最终hash值。

        在求hash值位置索引时计算index的方法:

int index = (hash & 0x7FFFFFFF) % tab.length;

     &0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数

     因此hashtable的哈希冲突概率会比hashmap高很多

  • 默认初始容量,个性化初始容量

       HashMap 的初始容量为:16,Hashtable 初始容量为:11。两者的默认负载因子都是0.75。 

       这里有一点也要注意, HashMap有tableSizeFor()方法,而HashTable没有该方法,因此: 

       对于  HashMap,如果自定义初始容量>=0,最后哈希表的容量为比自定义容量大并且最接近自定义容量的2的n次幂,如果自定义初始容量<0,则会报错

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值