这个哈希表的实现是相关数据结构的学习记录,
简单实现了一个哈希表,跟Java api中的Hashtable是不相同的,这个哈希表底层用的是数组和TreeMap,对于这个哈希表来说,有几个比较重要的地方,首先是hash值的运算方法,采用的是模除哈希表的长度M
// 计算每个key对应的hash值
private int hash(K key) {
return (key.hashCode() & 0x7fffffff) % M;
}
哈希表的容量,即M,这是一个避免哈希冲突的关键量,本类使用的是一个capacity数组(内部的数据是从相关网站上搜集的被测试可以有效避免hash冲突的素数),这个数组当中的每一个值在每次数组扩容跟缩容的时候使用。
然后是哈希表发生哈希冲突的平均负载量(哈希表中实际存储元素处于哈希表的长度),本类定义两个静态常量,upperTol与lowTol,当平均负载大于upperTol时进行扩容,在小于lowTol时进行缩容
其他需要注意的地方在代码的注释中有更详细的说明
import java.util.ArrayList;
import java.util.TreeMap;
public class HashTable<K, V> {
// 如果哈希表的平均负载量达到upperTol,则要进行扩容
private static final int upperTol = 10;
// 如果哈希表的平均负载量小于lowTol,则要进行缩容
private static final int lowTol = 2;
// capacity数组中存的为每次扩容或者缩容的哈希表长度
private final int[] capacity = { 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613,
393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189,
805306457, 1610612741 };
// capacityIndex为指向数组元素的索引
private int capacityIndex = 0;
// 定义一个TreeMap类型的数组hashtable
private TreeMap<K, V>[] hashtable;
private int size;
// M为哈希表的长度
private int M;
// hash表的初始化,有参构造,用户可自定义哈希表的长度
public HashTable() {
this.M = capacity[capacityIndex];
size = 0;
hashtable = new TreeMap[M];
// 初始化哈希表中的每一个map
for (int i = 0; i < M; i++) {
hashtable[i] = new TreeMap<>();
}
}
// 计算每个key对应的hash值
private int hash(K key) {
return (key.hashCode() & 0x7fffffff) % M;
}
public void add(K key, V value) {
// 计算key的hash值,将此值对应的hash表中的map取出来
TreeMap<K, V> map = hashtable[hash(key)];
// 判断此map是否含有key对应值
if (map.containsKey(key))
// 如果包含直接添加
map.put(key, value);
else {
// 否则,添加该值
map.put(key, value);
size++;
}
// 如果size/M>upperTol时进行扩容,这里将除法转化为乘法,避免了浮点类型的转换,
// 扩容之前要对capacityIndex的合法性进行判断,以免发生溢出
if (size > M * upperTol && capacityIndex + 1 < capacity.length)
capacityIndex++;
resize(capacity[capacityIndex]);
}
// 改变数组容量
private void resize(int newM) {
// TODO Auto-generated method stub
TreeMap<K, V>[] newHashtable = new TreeMap[newM];
// 初始化新数组中的每个map
for (int i = 0; i < newM; i++) {
newHashtable[i] = new TreeMap<>();
}
// 由于在下面有hash操作,模除的是新的M,则需要对M进行更新操作,同时,还要保留旧的M.
int oldM = M;
this.M = newM;
// 循环遍历旧数组,取出原有的map
for (int i = 0; i < oldM; i++) {
TreeMap<K, V> map = hashtable[i];
// 对于每一个map,取出其key的集合,将其中key对应的元素一个一个放入新的map中
for (K key : map.keySet()) {
newHashtable[hash(key)].put(key, map.get(key));
}
}
// 将当前newHashtable指向hashtable
this.hashtable = newHashtable;
}
public int Size() {
return size;
}
public V remove(K key) {
V ret = null;
TreeMap<K, V> map = hashtable[hash(key)];
if (map.containsKey(key)) {
ret = map.remove(key);
size--;
}
if (size < M * lowTol && capacityIndex - 1 > 0)
capacityIndex--;
resize(capacity[capacityIndex]);
return ret;
}
public void set(K key, V value) {
TreeMap<K, V> map = hashtable[hash(key)];
if (!map.containsKey(key))
throw new IllegalArgumentException("no such key");
map.put(key, value);
}
public boolean contains(K key) {
return hashtable[hash(key)].containsKey(key);
}
public V get(K key) {
TreeMap<K, V> map = hashtable[hash(key)];
V value = map.get(key);
return value;
}
}