vlambda博客
学习文章列表

周末晚上回来写的HashTable源码分析

一,HashTable源码分析

源码分析三部曲,构造函数,方法分析,总结一下

二,方法分析

2.1,构造函数


//构造一个初始值为11,负载因子为0.75
public Hashtable() {
this(11, 0.75f);
}
//我们看下具体的操作实现
public Hashtable(int initialCapacity, float loadFactor) {
//如果初始容量小于0,则应该抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//负载因子也是不可能要小于等于0的,思考一下为什么?
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);

if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
//构建初始容量为initialCapacity大小的entry
table = new Entry<?,?>[initialCapacity];
//阈值的计算,这个后面我自己单独看下为啥要这么计算
//此时,我真的不知道为啥这么设置
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

为什么初始容量小于0抛出异常?因为数组的特点,数组下标是从0开始的。

2.2,put()方法

public synchronized V put(K key, V value) {
//由于hashTable现在很少用,初入职场时,也是常被面试问到
// 不允许存入value为null的情况
if (value == null) {
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
//拿到key的hashCode值
int hash = key.hashCode();
//计算key应该插入的位置
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
//下面的逻辑就是判断key是否已经存在了
//如果key已经存在,则直接替换,如果不存在,则执行下面的addEntry方法
//首先根据index获取对应的entry数据
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
//如果hash值相等且key值相等,则说明已经存在了
if ((entry.hash == hash) && entry.key.equals(key)) {
//获取旧值
V old = entry.value;
//将新值赋值给entry.value
entry.value = value;
//返回旧值即可
return old;
}
}
//将新元素插入到对应的位置
addEntry(hash, key, value, index);
return null;
}

//第二步
private void addEntry(int hash, K key, V value, int index) {
modCount++;
//获取table引用赋值给临时变量tab[]
Entry<?,?> tab[] = table;
//rehash()操作也相当于集合中的数组扩容操作了
if (count >= threshold) {
//rehash操作这里暂时不分析了,因为文章太长了
rehash();

tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}


@SuppressWarnings("unchecked")
//获取index位置的元素entry,然后构造一个entry放入table[index]位置
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
// entry的数量加一
count++;
}

2.3,get()方法

public synchronized V get(Object key) {
//将table引用赋值给临时局部变量tab[]
Entry<?,?> tab[] = table;
//获取key的hash值,其实就是为了确定index做准备的
int hash = key.hashCode();
//hash取模操作
int index = (hash & 0x7FFFFFFF) % tab.length;
//遍历table[]中的每一个元素entry,,比对hash值和key是否相等,
//若相等则返回entry对应的元素,否则返回null,此时循环查找元素的时间复杂度O(n)
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}

2.4,contains()方法

public synchronized boolean contains(Object value) {
//判断元素value是否在hash表里面
//由于put的时候,不会存在value为null的元素
//这里相当于做个前置校验操作了
if (value == null) {
throw new NullPointerException();
}
//成员变量table赋值给临时局部变量,也是编程中常用的一种写法了
Entry<?,?> tab[] = table;
//为什么要从后往前找?
for (int i = tab.length ; i-- > 0 ;) {
//找到则返回true,否则返回false
for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}

2.5,containsValue()方法

public boolean containsValue(Object value) {
//方法的复用,便于代码的扩展,可读性
return contains(value);
}

2.6,containsKey()方法

public synchronized boolean containsKey(Object key) {
//将table成员变量的引用赋值给临时局部变量tab
Entry<?,?> tab[] = table;
//计算hash值
int hash = key.hashCode();
//确定检索的位置
int index = (hash & 0x7FFFFFFF) % tab.length;
//若key的hash值相等且key相等,则返回true,否则返回false
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}

2.7,size()方法

public synchronized int size() {
//返回key的数量,有多少key,就有多少entry
return count;
}

2.8,isEmpty()方法

public synchronized boolean isEmpty() {
//若hashTable的元素个数为0,则表明为空
return count == 0;
}

2.9,remove()方法

public synchronized V remove(Object key) {
//将table引用赋值给临时变量tab[]
Entry<?,?> tab[] = table;
//计算key的hash值就是快速找到key的位置
int hash = key.hashCode();
//确定key的位置
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
//找到了key
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
//断开链接
prev.next = e.next;
} else {
//此时tab[index]置为了null
tab[index] = e.next;
}
//元素个数减一
count--;
//返回待删除的元素
V oldValue = e.value;
//等待某个时刻,触发gc
e.value = null;
return oldValue;
}
}
return null;
}

2.10,clear()方法

public synchronized void clear() {
Entry<?,?> tab[] = table;
modCount++;
//循环,将table[]的每一个元素置为null
//成员变量count置为0,等待gc在某个时刻被触发,将堆上的
//不可达对象进行回收,进行内存碎片的清理
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}

2.11,getOrDefault()方法

public synchronized V getOrDefault(Object key, V defaultValue) {
//复用原来的get()方法,若元素key不存在,则返回null
V result = get(key);
//若为null,则使用默认值
return (null == result) ? defaultValue : result;
}

2.12,replace()方法

public synchronized V replace(K key, V value) {
Objects.requireNonNull(value);
Entry<?,?> tab[] = table;
int hash = key.hashCode();
//得到key所在的位置index
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
//循环判断每一个entry数据,是否hash值以及key和传入的key相等
//若相等,则使用新值value替换查找到的旧值
for (; e != null; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
return null;
}

三,总结一下

其实,这些方法分析下来,难度是有的,但是整体分析下来还是比较值得的,如果我自己只分析,不记录文字输出出来应该很快就会分析完,内容输出来确实可以帮助自己一些,如果对别人有一点点启发何尝不是一件正确的事情呢,这里自己分析了大部分的常用的方法,极个别的方法没有分析,用的比较少,为什么hashtable是线程安全的?想必看过整篇文章就明白了,目前自己不会很详细的把每个知识点都说到,毕竟一篇文章是说不完的。