1.java集合框架图
2.所属包
package java.util;
3.继承与实现关系
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
4.属性
/**
* 存放Entry的数组
*/
private transient Entry<K,V>[] table;
/**
* 哈希表中Entry的数量
*/
private transient int count;
/**
* 哈希表的临界值 threshold =capacity*loadFactor
*/
private int threshold;
/**
* 哈希表的加载因子
*/
private float loadFactor;
/**
* 被修改的次数
*/
private transient int modCount = 0;
/**
* 默认的hash临界值
*/
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
5.Hashtable的数据结构
哈希表的核心就是根据元素求位置。而多个元素可能出现相同的位置,那么就叫冲突。解决冲突的常用方式:链地址法:将多个值的不同的哈希结果(哈希值)用数组进行存储,然后将产生相同哈希值的元素,以单向链表的形式进行存储。所以拉链法的套路就是数组+单链表。第二种解决方式是线性探测法:将多个值余上表长度,如果多个值产生相同的哈希值,那么就依次往下寻找位置,直到不冲突为止。线性探测法致命的缺点就是当数据量上来时,就会频繁的进行碰撞冲突,然后找到位置比较费劲。
/**
* 采用单向链表的方式进行连接
*/
private static class Entry<K,V> implements Map.Entry<K,V> {
int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
。。。。。。
}
6.构造方法
/** 构造方法1
* 指定初始化容量和加载因子参数的构造方法
*/
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);
//如果初始化容量为0,那么就初始化为1
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
useAltHashing = sun.misc.VM.isBooted() &&
(initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
}
/** 构造方法2
* 指定初始化容量,采用默认加载因子0.75的构造方法
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/** 构造方法3
* 构造一个初始化容量为11,加载因子为0.75的构造方法
*/
public Hashtable() {
this(11, 0.75f);
}
/** 构造方法4
* 通过给定的Map来构造HashTable.
*
* @param t the map whose mappings are to be placed in this map.
* @throws NullPointerException if the specified map is null.
* @since 1.2
*/
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
7.方法
hash方法:
//通过键来获取hash值
private int hash(Object k) {
if (useAltHashing) {
if (k.getClass() == String.class) {
return sun.misc.Hashing.stringHash32((String) k);
} else {
int h = hashSeed ^ k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
} else {
return k.hashCode();
}
}
putAll方法:
/**
* 采用同步的方式插入值
*/
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());
}
put方法:
/**
* 将指定 key 映射到此哈希表中的指定 value。键和值都不可以为 null。
*/
public synchronized V put(K key, V value) {
// 如果插入的值为null,抛出空指针异常
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
int hash = hash(key);
//获取下标索引
int index = (hash & 0x7FFFFFFF) % tab.length;
//遍历单链表,如果存在hash值和键都相同,那么就新值替换旧值,返回旧值
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;
}
}
modCount++;
//如果表长超出了临界值,重新进行hash值计算,并重新设置Entry数组、hash值、下标索引
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);
count++;
return null;
}
rehash方法:
/**
* 增加此哈希表的容量并在内部对其进行重组,以便更有效地容纳和访问其元素。当哈希表中的键的数量超出哈希表的容量和加载因子时,自动调用此方法。
*/
protected void rehash() {
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)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<K,V>[] newMap = new Entry[newCapacity];
modCount++;
//由新容量计算临界值
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean currentAltHashing = useAltHashing;
useAltHashing = sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = currentAltHashing ^ useAltHashing;
table = newMap;
//遍历旧表中的元素,将旧表中的元素都转移到新表中
for (int i = oldCapacity ; i-- > 0 ;) {
//遍历拥有相同hash值的单向链表
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
//重新通过键生成hash值
if (rehash) {
e.hash = hash(e.key);
}
//通过取模的方式来求下标索引
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
contains方法:
/**
* 以同步的方式判断hash表中是否包含指定的值
*/
public synchronized boolean contains(Object value) {
//如果值为空,则抛出空指针异常
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
//遍历每一条单链表中的值,如果存在与输入的值相同,那么返回true
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;
}
containsValue方法:
/**
* 以同步的方式判断hash表中是否包含指定的值
*/
public boolean containsValue(Object value) {
return contains(value);
}
containsKey方法:
/**
* 以同步的方式来判断hash表中是否存在输入的键
*/
public synchronized boolean containsKey(Object key) {
Entry tab[] = table;
//通过键获取对应的hash值
int hash = hash(key);
//通过hash值获取对应的下标索引
int index = (hash & 0x7FFFFFFF) % tab.length;
//根据索引获取的Entry遍历单链表,如果存在hash值和键都相同的,那么就返回true。
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}
get方法:
/**
* 使用同步的方式通过键获取值
*/
public synchronized V get(Object key) {
Entry tab[] = table;
//通过键获取hash值
int hash = hash(key);
//通过取模操作来获取下标索引
int index = (hash & 0x7FFFFFFF) % tab.length;
//遍历该索引对应的那条单链表,如果存在hash值和键都相同的Entry,就返回该Entry对应的值。
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;
}
remove方法:
/**
* 通过键获取hash值
* 通过hash进行取模获取下标索引
* 通过索引获取Entry遍历单链表,找到hash和key都相同的元素将其删除,并调整其前后关系。
* 返回删除的旧值
*/
public synchronized V remove(Object key) {
Entry tab[] = table;
//使用键获取对应的hash值
int hash = hash(key);
//使用取模操作来获取下标索引
int index = (hash & 0x7FFFFFFF) % tab.length;
//遍历该索引对应的单链表,遍历找到hash值和键都相同的点,删除之后需要调整单链表中节点的前后关系。
for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
/* prev变量赋值为e
* 如果prev不为空,那么e的下一个节点为prev的下一个节点。
* 如果prev为空,那么e的下一个节点为作为单链表的第一个节点(也对应着Entry数组中的值)
*/
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
//删除一个值,长度减一
count--;
V oldValue = e.value;
//将删除的节点e的值设置为空,没有了引用,这样便于编译器回收
e.value = null;
return oldValue;
}
}
return null;
}
clear方法:
/**
* 遍历表table,将元素置为空,长度设置为0
*/
public synchronized void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
-----------------------------该源码为jdk1.7版本的