刚入java不久的程序猿,对于简单的使用已毫不满足,最终为了一探究竟,翻开了JDK的源码,以下观点为自己的理解及看了多篇博客的总结,欢迎各位大神指出不对的地方,当然也欢迎和我一样刚学的同学,一起加油努力吧~~
Hashtable是什么 |
Hashtable和HashMap一样是一个集合,不过不同于HashMap的是,HashMap允许null键与null值,而Hashtable不允许,HashMap是线程不安全的,而Hashtable是线程安全的,由于线程安全性问题,HashMap相对于Hashtable效率会更高一些
Hashtable源码解析 |
由于之前楼主也没看过Hashtable的源码,所以在这里我们就一起来学习一下吧,按照以往惯例,先了解下整体的结构
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
...
}
在这里,我们看到Hashtable继承自Dictionary这个字典类,并实现了Map,Cloneable,Serializable接口,具体的这些接口作用以及Map接口里的方法已在之前的HashMap解析中介绍过,没看过的同学可以点击链接JDK之HashMap源码解析,下面着重看下Dictionary这个抽象类中的方法
public abstract
class Dictionary<K,V> {
/**
* 一个空的构造方法
*/
public Dictionary() {
}
/**
* 一个用于计算长度的抽象方法
*/
abstract public int size();
/**
* 一个用于判断是否为空的抽象方法
*/
abstract public boolean isEmpty();
/**
* 一个用于取出key的抽象方法,取出类型为枚举
*/
abstract public Enumeration<K> keys();
/**
* 一个用于取出value的抽象方法,取出类型为枚举
*/
abstract public Enumeration<V> elements();
/**
* 根据key值取出value
*/
abstract public V get(Object key);
/**
* 以key对应value的形式存放值
*/
abstract public V put(K key, V value);
/**
* 根据key值移除某个元素
*/
abstract public V remove(Object key);
}
这里我给每个方法都标了注释,由于这个类是抽象类,当继承这个类需实现其抽象方法,然而我们发现这个类中除了构造方法外都是抽象方法,这样在Hashtable是其子类需实现这些方法,接下来我们来看Hashtable这个类,先来看看变量
/**
* 存储数据的table数组
*/
private transient Entry<K,V>[] table;
/**
* Hashtable中元素的总数
*/
private transient int count;
/**
* 阈值
*/
private int threshold;
/**
* 加载因子
*/
private float loadFactor;
/**
* 修改次数
*/
private transient int modCount = 0;
/** 版本ID号 */
private static final long serialVersionUID = 1421746759512286392L;
/**
* 容量阈值,大小为Integer的最大容量
*/
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
这里对Hashtable的变量进行了相应的注释,下面我们继续往下看
/**
* 静态内部类Holder,调用该类时初始化值
*/
private static class Holder {
/**
* 容量阈值,初始化hashSeed时候使用
*/
static final int ALTERNATIVE_HASHING_THRESHOLD;
static {
/**
* 获取系统变量jdk.map.althashing.threshold
*/
String altThreshold = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
"jdk.map.althashing.threshold"));
int threshold;
try {
/**
* 判断altThreshold是否为空,并为阈值赋值
*/
threshold = (null != altThreshold)
? Integer.parseInt(altThreshold)
: ALTERNATIVE_HASHING_THRESHOLD_DEFAULT;
// 如果该阈值为-1,则将Integer最大值赋予阈值
if (threshold == -1) {
threshold = Integer.MAX_VALUE;
}
//当阈值小于0时,抛出IllegalArgumentException异常
if (threshold < 0) {
throw new IllegalArgumentException("value must be positive integer.");
}
} catch(IllegalArgumentException failed) {
throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed);
}
ALTERNATIVE_HASHING_THRESHOLD = threshold;
}
}
/**
* 后面计算hash值时用到
*/
transient int hashSeed;
/**
* 初始化hashseed
*/
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
/**
* 根据key值和hashseed计算哈希值
*/
private int hash(Object k) {
return hashSeed ^ k.hashCode();
}
/**
* 返回hashCode的值
*/
public synchronized int hashCode() {
int h = 0;
if (count == 0 || loadFactor < 0)
return h; // 返回0
loadFactor = -loadFactor; //标记计算过程中的hashCode
Entry[] tab = table;
for (Entry<K,V> entry : tab)
while (entry != null) {
h += entry.hashCode();
entry = entry.next;
}
loadFactor = -loadFactor; //标记完成后的hashCode
//返回哈希值
return h;
}
这里和HashMap中一样,就不多做解释了,继续看代码,Hashtable中几个构造方法
/**
* @param initialCapacity 初始化容量大小(但并不是真正初始化大小)
* @param loadFactor 加载因子
* @throws IllegalArgumentException 抛出IllegalArgumentException异常
*/
public Hashtable(int initialCapacity, float loadFactor) {
//当初始化容量大小小于0时,抛出IllegalArgumentException异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//当加载因子小于0或者为无效数字时,抛出IllegalArgumentException异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
//当初始化容量等于0时赋值
if (initialCapacity==0)
initialCapacity = 1;
//加载因子
this.loadFactor = loadFactor;
//初始化table
table = new Entry[initialCapacity];
//阈值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//初始化hashseed
initHashSeedAsNeeded(initialCapacity);
}
/**
* @param initialCapacity 初始化容量
* @throws IllegalArgumentException 抛出IllegalArgumentException异常
* 调用HashMap重载的构造方法,参数分别为初始化容量和加载因子0.75f
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* 调用HashMap重载的构造方法,参数分别为初始化容量11和加载因子0.75f
*/
public Hashtable() {
this(11, 0.75f);
}
/**
* @param Map对象
* this调用相应构造方法,传入初始化容量Math.max(2*t.size()和0.75f)
*/
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
看到这里我们发现基本上和HashMap差不多,毕竟都是以键值对的形式存在的,好了,下面看一下从父类继承过来的抽象方法和Map接口中的方法
/**
* 返回Hashtable中元素的数量
*/
public synchronized int size() {
return count;
}
/**
* 判断Hashtable是否为空
*/
public synchronized boolean isEmpty() {
return count == 0;
}
/**
* 获取HashTable中所有的key
*/
public synchronized Enumeration<K> keys() {
return this.<K>getEnumeration(KEYS);
}
/**
* 具体的获取key的方法,参数为传入类型0、1、2,分别代表key,value,entries
*/
private <T> Enumeration<T> getEnumeration(int type) {
//当HashTable里无元素时,返回空的枚举集合,否则返回一个新建的
if (count == 0) {
return Collections.emptyEnumeration();
} else {
return new Enumerator<>(type, false);
}
}
/**
* 获取HashTable中所有的value
*/
public synchronized Enumeration<V> elements() {
return this.<V>getEnumeration(VALUES);
}
/**
* 判断某个值是否在HashTable中存在的具体方法
*/
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
//遍历判断
for (int i = tab.length ; i-- > 0 ;) {
for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
/**
* 判断某个值是否在HashTable中存在
*/
public boolean containsValue(Object value) {
return contains(value);
}
/**
* 判断某个key是否在HashTable中存在
*/
public synchronized boolean containsKey(Object key) {
Entry tab[] = table;
//计算哈希值
int hash = hash(key);
//计算在数组中的位置
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
//判断是否存在该key
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}
这里先列出了部分方法,相应的地方也做了注释,刚刚我们说到HashTable相对于HashMap是线程安全的,原因就在方法上,我们可以看到每个用到的方法上都加了锁synchronized,HashTable就是因为有锁所以线程是安全的,当然牺牲了部分的效率问题,接下来我们来看几个常用的方法
/**
* 被允许的最大值为Integer的最大值减去8
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* 根据key值获取元素value
*/
public synchronized V get(Object key) {
Entry tab[] = table;
//计算哈希值
int hash = hash(key);
//计算在数组中的位置
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
/**
* 存放键值对key,value
* HashTable键值不能为空
*/
public synchronized V put(K key, V value) {
//值不能为空,否则抛空指针异常
if (value == null) {
throw new NullPointerException();
}
// 确认HashTable中,有无这个key
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
//如果有的话,替换值,并返回旧值
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
//修改次数+1
modCount++;
//如果元素数量大于阈值,重新计算哈希值
if (count >= threshold) {
//扩容并重新计算哈希值
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
//创建新的entry
Entry<K,V> e = tab[index];
//放到数组中
tab[index] = new Entry<>(hash, key, value, e);
//元素数量+1
count++;
return null;
}
/**
* 扩容并重新计算哈希值
*/
protected void rehash() {
//HashTable旧的容量
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table;
//新的容量,为原大小的一倍加1
int newCapacity = (oldCapacity << 1) + 1;
//判断新容量是否超出的最大值,如超出了更改为最大值
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
//如果旧容量已经是最大值了返回
return;
newCapacity = MAX_ARRAY_SIZE;
}
//用新的容量创建的数组
Entry<K,V>[] newMap = new Entry[newCapacity];
//修改次数+1
modCount++;
//重新计算阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//根据新的容量重新初始化hashseed
boolean rehash = initHashSeedAsNeeded(newCapacity);
table = newMap;
//循环遍历旧的元素
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
//重新计算哈希值
e.hash = hash(e.key);
}
//重新计算位置
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
//设置元素
e.next = newMap[index];
//放入元素
newMap[index] = e;
}
}
}
/**
* 根据key值移除某个元素
*/
public synchronized V remove(Object key) {
Entry tab[] = table;
//计算哈希值
int hash = hash(key);
//计算在数组中位置
int index = (hash & 0x7FFFFFFF) % tab.length;
//遍历找到该元素
for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
//修改次数+1
modCount++;
//判断不是第一位,则将元素移动
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
//元素数量-1
count--;
//将该元素置空,并返回旧的值
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
这里我们看完了几个常用的方法,看了源码,想必大家都知道为什么HashTable不能存放空值空键了,最后还剩一些方法,大致说明下
/**
* 将所有元素放入HashTable,循环遍历调用put方法
*/
public synchronized void putAll(Map<? extends K, ? extends V> t) {
for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
put(e.getKey(), e.getValue());
}
/**
* 清空HashTable,数组置空,元素个数置0
*/
public synchronized void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
/**
* 克隆方法
*/
public synchronized Object clone() {
try {
Hashtable<K,V> t = (Hashtable<K,V>) super.clone();
t.table = new Entry[table.length];
for (int i = table.length ; i-- > 0 ; ) {
t.table[i] = (table[i] != null)
? (Entry<K,V>) table[i].clone() : null;
}
t.keySet = null;
t.entrySet = null;
t.values = null;
t.modCount = 0;
return t;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
/**
* toString方法,控制台输出调用
*/
public synchronized String toString() {
int max = size() - 1;
if (max == -1)
return "{}";
StringBuilder sb = new StringBuilder();
Iterator<Map.Entry<K,V>> it = entrySet().iterator();
sb.append('{');
for (int i = 0; ; i++) {
Map.Entry<K,V> e = it.next();
K key = e.getKey();
V value = e.getValue();
sb.append(key == this ? "(this Map)" : key.toString());
sb.append('=');
sb.append(value == this ? "(this Map)" : value.toString());
if (i == max)
return sb.append('}').toString();
sb.append(", ");
}
}
/**
* 根据类型转换为迭代器
* /
private <T> Iterator<T> getIterator(int type) {
if (count == 0) {
return Collections.emptyIterator();
} else {
return new Enumerator<>(type, true);
}
}
/**
* 比较值是否相等
*/
public synchronized boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<K,V> t = (Map<K,V>) o;
if (t.size() != size())
return false;
try {
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Map.Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(t.get(key)==null && t.containsKey(key)))
return false;
} else {
if (!value.equals(t.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
到这里HashTable源码解析基本完成,还有一些转Set,writeObject之类的方法,感兴趣的同学可以自己打开源码库看一下,最后总结一下,HashTable和HashMap由于都实现了Map接口,所以其也有很多相似之处,如果看过HashMap的再来看HashTable应该会觉得很轻松,毕竟差别还是很小的,如果有什么错误的地方还望指出