文章目录
1.概述
和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。
与HashMap不同点
- 在于table数组的初始化时机不同,HashTable对于table数组的初始化时机在构造函数中。而HashMap是在resize()中,即在第一次发生put键值对的时候才对table数组进行初始化。
- table数组的大小不同,HashTable可以是任意数值。HashMap只能是2的n次方的数值。
- 阈值初始化的计算方式不同,HashMap根据initialCapacity的值来得到一个最小的比他大的一个2的n次方的数当作阈值。而HashTable根据Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1)
- 默认容量为11。每次扩容是旧容量的2倍+1。而HashMap默认容量为16。每次扩容是旧容量的2倍
2.类图
继承了Dictionary,Dictionary是个被废弃的抽象类。
实现了Map接口,序列化和可扩容接口
3.属性
private transient Entry<?,?>[] table;//用于存储键值对的Entry数组 默认大小11
private transient int count;//统计键值对的数量
private int threshold;//扩容阈值
private float loadFactor;//装载因子
private transient int modCount = 0;//修改次数
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//数组大小最大值
4.构造方法
-
无参构造函数 内部调用有参的构造函数 默认大小11 装载因子0.75
public Hashtable() { this(11, 0.75f); }
-
Hashtable(int initialCapacity)
初始化HashTable的大小的构造函数,同样默认的装载因子0.75
public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); }
-
Hashtable(Map<? extends K, ? extends V> t)
public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f);//根据t的大小来决定初始化HashTable中Entry数组的大小 putAll(t); }
-
Hashtable(int initialCapacity, float loadFactor)
public Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0)//若参数为0,则创建一个大小为1的HashTable initialCapacity = 1; this.loadFactor = loadFactor;//这只装载因子 table = new Entry<?,?>[initialCapacity];//初始化table数组 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//初始化阈值 }
5.简单的基本方法
- size() 获得键值对的数量
public synchronized int size() {
return count;
}
- isEmpty() 判断是否为空
public synchronized boolean isEmpty() {
return count == 0;
}
- keys() 获得键枚举器对象.这个对象实现了迭代器接口。可以使用迭代的方式得到所有的键。
public synchronized Enumeration<K> keys() {
return this.<K>getEnumeration(KEYS);
}
private static final int KEYS = 0;
private static final int VALUES = 1;
private static final int ENTRIES = 2;
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {
return Collections.emptyEnumeration();
} else {
return new Enumerator<>(type, false);
}
}
- elements() 获得值枚举器对象。和上面类似可以通过迭代的方式得到所有的值
public synchronized Enumeration<V> elements() {
return this.<V>getEnumeration(VALUES);
}
- keySet() 得到所有key的集合
public Set<K> keySet() {
if (keySet == null)
keySet = Collections.synchronizedSet(new KeySet(), this);
return keySet;
}
6.查找
6.1 contains(Object value)
判断值存不存在
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
for (int i = tab.length ; i-- > 0 ;) {//遍历数组
for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {//遍历结点
if (e.value.equals(value)) {//找到返回true
return true;
}
}
}
return false;//否则返回false
}
6.2 containsValue(Object value)
唯一的区别就是没有加synchronized
public boolean containsValue(Object value) {
return contains(value);
}
6.3 containsKey(Object key)
判断key存不存在
public synchronized boolean containsKey(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();//计算hash
int index = (hash & 0x7FFFFFFF) % tab.length;//找到数组中存储的坐标
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {//遍历这一位置的链表。若key相同返回true
return true;
}
}
return false;//否则返回false
}
6.4 get(Object key)
根据key得到对应的value 和上面查找有没有key存在类似 只是返回的结果不同
找到了对应的key返回value 没有找到返回null
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
7.添加单个结点
7.1 addEntry(int hash,K key,V value,int index)
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {// 若容量超过阈值 扩容
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();//计算hash
index = (hash & 0x7FFFFFFF) % tab.length;//计算存储位置的索引
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];//拿到原来在这一位置的节点。
tab[index] = new Entry<>(hash, key, value, e);//链表法的方式加入新结点
count++;
}
7.2 put(K key,V value)
添加键值对到map中,若已存在key 覆盖它并返回旧值,否则添加一个新结点到map中,返回null。
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();//计算hash
int index = (hash & 0x7FFFFFFF) % tab.length;//计算索引
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];//拿到那一位置的链表
for(; entry != null ; entry = entry.next) {//遍历链表
if ((entry.hash == hash) && entry.key.equals(key)) {//key存在,修改对应的value
V old = entry.value;
entry.value = value;
return old;//返回旧值
}
}
addEntry(hash, key, value, index);//否则调用addEntry添加新结点。
return null;
}
8.扩容rehash()
扩容后新的容量是旧容量的2倍加1。在不超过最大容量的范围内。否则就是扩到最大容量。使用的是头插法。
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;//老的数组
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;//新数组容量
if (newCapacity - MAX_ARRAY_SIZE > 0) { // 若新数组容量大于最大允许的值则将其设为最大允许的大小
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];//根据新容量创建新的数组
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//计算阈值
table = newMap;//新数组赋值给table
for (int i = oldCapacity ; i-- > 0 ;) {//遍历旧数组
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {//遍历旧数组中的结点
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;//计算再次hash计算新存储的位置。
e.next = (Entry<K,V>)newMap[index]; //使用的是头插法
newMap[index] = e;
}
}
}
9.移除
9.1remove(Object key)
根据给定的key 移除键值对。
public synchronized V remove(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();//计算hash
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) {//遍历链表
if ((e.hash == hash) && e.key.equals(key)) {//找到对应的key 删除这个键值对。
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;//返回旧值
}
}
return null;//没有找到对应的key返回null
}
9.2clear() 清空
public synchronized void clear() {
Entry<?,?> tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )//数组全部置为null
tab[index] = null;
count = 0;
}
10.其他方法
public synchronized boolean equals(Object o)//比较是否相同
public synchronized int hashCode()//计算hash
public synchronized V getOrDefault(Object key, V defaultValue)//和HashMap一样,有对应的value返回value 没有就返回defaultValue
public synchronized V putIfAbsent(K key, V value)//不存在则put 若存在 判断对应的value是不是为null 为null的话改变这个值。这是和HashMap不一样的地方
public synchronized boolean replace(K key, V oldValue, V newValue)//替换
public synchronized V replace(K key, V value)//替换 和上面不同的地方在于,上面必须key对应的value和参数oldValue相同是 才可以替换。这里是找到key对应的value就替换