Java集合之Map——Hashtable详解
简介
- 与
HashMap
一样,Hashtable
也是一个散列表,是以key-value存储形式存在,即主要用来存放键值对; - 与
HashMap
不同,Hashtable
的函数都是同步的,这意味着它是线程安全的; Hashtable
的key、value都不可以为null
,并且,Hashtable
中的映射不是有序的;- 实现结构是数组+单向链表。
源码解读
继承关系
1 | public class Hashtable<K,V> |
Cloneable
空接口,表示可以克隆。 创建并返回HashMap对象的一个副本;Serializable
序列化接口。属于标记性接口。HashMap对象可以被序列化和反序列化;- 实现了
Map
接口,Map
接口是声明了key-value键值对
的接口; 继承于
Dictionary
抽象类,Dictionary
类是声明了操作键值对函数接口
的抽象类。成员变量
与
HashMap
相比简单了一些。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//哈希表,多个哈希桶组成
private transient HashtableEntry<?,?>[] table;
//HashTable条目数总量
private transient int count;
//哈希表的容量,可容纳元素个数,也称为阈值,为哈希表扩容的临界条件
private int threshold;
//加载因子
private float loadFactor;
//修改次数
private transient int modCount = 0;大部分与
HashMap
中变量相似:threshold
是哈希表能存储的元素个数,也成为阈值,当表中存储元素个数大于阈值时,将会对哈希表进行扩容,即增加桶的个数;- 源码中出现的
capacity
代表的是哈希表的大小,即表中哈希桶的个数,table.length
,它与阈值不同; loadFactor
,加载因子,也称负载因子,它表示HashMap
的疏密程度,是阈值与哈希桶个数的比值,$loadFactor=\frac{threshold}{table.length}$,实时加载因子为$loadFactor=\frac{size}{table.length}$;
构造方法
public Hashtable()
,默认构造方法,指定的容量大小是11,加载因子为0.75;public Hashtable(int initialCapacity)
,默认初始容量,构造一个具有指定的初始容量和默认加载因子(0.75);public Hashtable(int initialCapacity, float loadFactor)
,构造一个具有指定的初始容量和加载因子的HashMap
;public Hashtable(Map<? extends K, ? extends V> t)
,构造一个新的 HashMap与指定的相同的映射 Map 。
主要分析一下第三个构造方法(前两个方法也是调用第三个方法):
1 | public Hashtable(int initialCapacity, float loadFactor) { |
成员方法
向表中添加键值对public synchronized V put(K key, V value)
1 | //这里使用Synchronized锁方法的方式来保证put方法的在多线程下的数据安全 |
总体流程大致为:
- 判断插入键值对是否合理;
- 通过
(key.hashCode() & 0x7FFFFFFF) % tab.length
来确定目标哈希桶; - 遍历桶中元素,若键相等,则进行覆盖;
- 否则调用
private void addEntry(int hash, K key, V value, int index)
,进行链表头插法; - 在
addEntry
方法中,先判断插入键值对后哈希表是否需要扩容,若需要则先扩容,然后重新计算哈希值; - 最终进行头插法。
移除键值对public synchronized V remove(Object key)
直接通过key
的key.hashCode()
定位目标桶,然后就是正常的链表删除节点操作:
1 | //这里使用Synchronized 锁方法的方式来保证get函数在多线程下的数据安全 |
获取元素public synchronized V get(Object key)
直接通过key
的key.hashCode()
定位目标桶,然后遍历链表找相应元素。
1 | //这里使用Synchronized 锁方法的方式来保证get函数在多线程下的数据安全 |
哈希表的扩容,protected void rehash()
扩容时机:(每次扩容都将哈希表大小扩容成原来的两倍加一)
- 当容器中的元素数量大于阈值(即加载因子乘以哈希表大小)进行扩容。
没有HashMap
的那么多细节,直接上代码:
1 | protected void rehash() { |
总结
- 通过上面的分析我们可以清楚地知道,
Hashtable
的put
、get
和remove
方法在多线程下可以保证数据安全,实现方式都是使用Synchronized
同步锁方法的方式实现的。
扩展——与HashMap
的比较
散列表 | 实现方式 | 是否线程安全 | 数据安全实现方式 | key\value是否可为Null | 效率高低 |
---|---|---|---|---|---|
Hashtable |
数组+单向链表 | 是 | Synchronized |
不可为null |
低 |
HashMap |
数组+单向链表/红黑树 | 否 | 无 | 可为null |
高 |