一 功能简介
Hashtable
实现方式: 底层数组+链表
初始大小:11
扩容:newSize = oldSize*2+1; 超过3/4 即0.75时扩容
存放键值对要求: key 和 value 都不能为null
线程安全性:线程安全,实现方式是在修改数据时锁住整个HashTable,效率低
父类:Dictionnary (已废弃,所以子类也都不推荐使用了) ,Hashtable 属于遗留类
HashMap
实现方式:底层 数组+链表
初始大小:16
扩容:newSize = oldSize*2; map中元素总数超过Entry数组的75%,触发扩容操作
存放键值对要求:key 和 value 都允许为null,这种key只能有1个
线程安全性:不安全
父类:AbstractMap
ConcurrentHashMap
实现方式:分段的数组 + 链表实现
通过把整个Map分为N(默认16)个部分(槽),相当于是长度为16的槽数组,
每个槽都继承了ReentrantLock,所以每个槽有一把锁,相比HashTable, 效率提升了N倍(读操作不加锁,因类型为volatile 保证能读到最新的值)
扩容:段内扩容
二 实现逻辑
2.1 hashMap的内部实现逻辑
java7
hashmap 里面是一个数组,数组中每个元素是一个Entry类型的实例
每个Entry的实例包含4个属性,hash,key,value,next
当put进入某个(key,value)时,会对key进行hash再获取到散列地址,相同散列地址的键值对,存放到同一个链表上(默认放链表头)
扩容顺序:先扩容再插入新值
java8
进行了优化,如果链表中元素超过8个,就转换为红黑树,以减少查询的复杂度 O(logN)
扩容顺序:先插入新值,再扩容
2.2 ConcurrentHashMap 的实现逻辑
java 7
ConcurrentHashMap的思路和HashMap思路是差不多,但是因为它支持并发操作,所以要复杂一些,
整个ConcurrentHashMap由16个Segment组成,Segment代表“部分” 或 “一段”的意思,所以很多地方将其描述为分段锁,
ConcurrentHashMap是一个Segment数组, 其中 Segment 通过继承 ReentrantLock 来进行加锁,即每个锁锁住一个Segment,这样保证线程安全
ConcurrentHashMap的扩容,长度初始化后,无法对 Segment数组进行扩容的,默认长度16,扩容是对Segment里面 的数组进行扩容,每个Segment 想当于一个hashMap
java 8
也用到了红黑树,逻辑和上面类似
2.3 HashMap中 Entry节点怎么存储?
散列表table是1个Entry数组,保存Entry实例,
对于冲突:在开散列中,如果若干个entry计算得到相同的散列地址(不同的key也可能计算出相同的散列地址),这些entry 被组织成一个链表,以table[i]为头指针
2.4 集合的长度扩容机制:(rehash)
当键值对的数量>=设定的阈值(capacity*0.75)时,为保证性能,会进行重散列(重新通过key, length等计算散列地址)
分2步
1 扩容table的长度,2倍
2 转移table中的entry,从旧table转移到新的table (转移过程中,对key进行重新计算散列地址)
2.5 如何计算具体数组位置?
使用 key 的 hash 值对数组长度进行取模就可以了
三 结论
虽然hashtable 是线程安全的,但由于其性能低下(锁整个table) 而且父类已废弃其属于遗留类,所以不推荐使用,
如果是单例模式下,可用hashMap
如果多线程模式下,推荐使用 ConcurrentHashMap
原文地址:https://www.cnblogs.com/hup666/p/11823795.html