HashTable是Map接口线程安全实现版本,数据结构和方法实现与HashMap类似,本文记录HashTable源码解析,基于JDK1.8。
类结构
HashTable类层级关系图:
主要成员变量:
1 | // 内部采用Entry数组存储键值对数据,Entry实际为单向链表的表头 |
table属性通过transient修饰,原因在介绍HashMap源码的时候分析过。
Entry代码如下:
1 | private static class Entry<K,V> implements Map.Entry<K,V> { |
Entry为单向链表节点,HashTable采用数组加链表的方式存储数据,不过没有类似于HashMap中当链表过长时转换为红黑树的操作。
方法解析
构造函数
1 | // 设置指定容量和加载因子,初始化HashTable |
put(K key, V value)
put(K key, V value)
添加指定键值对,键和值都不能为null:
1 | // 方法synchronized修饰,线程安全 |
rehash()
rehash
扩容操作:
1 | protected void rehash() { |
get(Object key)
get(Object key)
获取指定key对应的value:
1 | public synchronized V get(Object key) { |
synchronized修饰,线程安全。
remove(Object key)
remove(Object key)
删除指定key,返回对应的value:
1 | public synchronized V remove(Object key) { |
synchronized修饰,线程安全。
剩下方法有兴趣自己阅读源码,public方法都用synchronized修饰,确保线程安全,并发环境下,多线程竞争对象锁,效率低,不推荐使用。线程安全的Map推荐使用ConcurrentHashMap。
和HashMap对比
线程是否安全:HashMap是线程不安全的,HashTable是线程安全的;HashTable内部的方法基本都经过 synchronized修饰;
对Null key 和Null value的支持:HashMap中,null可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为null;HashTable中key和value都不能为null,否则抛出空指针异常;
初始容量大小和每次扩充容量大小的不同:
3.1. 创建时如果不指定容量初始值,Hashtable默认的初始大小为11,之后每次扩容,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍;
3.2. 创建时如果给定了容量初始值,那么Hashtable会直接使用你给定的大小,而HashMap会将其扩充 为2的幂次方大小。
底层数据结构:JDK1.8及以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间,Hashtable没有这样的机制。