底层数据结构:哈希表(数组+链表)
特点:
1.键值对不能为null;
2.底层数据结构:数组+链表
3.key重复性:key不能重复;
4.有序性:不能保证插入有序
继承关系:
public class Hashtable<K,V>
extends Dictionary<K,V> JDK较早提供的实现类
implements Map<K,V>, Cloneable, java.io.Serializable
默认值:
数组初始化大小:11
加载因子:0.75
增长方式:2*table.length+1
基本属性
private transient Entry<K,V>[] table; //哈希表 数组大小默认值为11
private transient int count; //哈希表中的实体个数
private int threshold; //扩容的阈值
private float loadFactor; //加载因子
private transient int modCount = 0;
private static final long serialVersionUID = 1421746759512286392L;
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
构造函数
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)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry[initialCapacity]; //数组初始化 与hashmap不同
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
initHashSeedAsNeeded(initialCapacity);
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
//数组默认大小11,加载因子默认0.75
}
public Hashtable() {
this(11, 0.75f);
}
CRUD :增删改查
put()
1、hashtable 多线程下安全;HashMap 线程不安全
2、synchronized:调用synchronized修饰的方法,对对象进行加锁(互斥:同一时刻同一资源只有一个线程访问)
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();//特点:value 不能为null
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
//key不能为null,key为null也会抛出空指针问题
//通过key的hash找到存储位置
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;//拿key找到指定索引位置
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {//未使用双等key判断
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
//hash方式与数组长度相关
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;//增长方式2倍加一
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 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;
}
}
}
private int hash(Object k) {
// hashSeed will be zero if alternative hashing is disabled.
return hashSeed ^ k.hashCode();
}
public synchronized int hashCode() {
int h = 0;
if (count == 0 || loadFactor < 0)
return h; // Returns zero
loadFactor = -loadFactor; // Mark hashCode computation in progress
Entry[] tab = table;
for (Entry<K,V> entry : tab)
while (entry != null) {
h += entry.hashCode();
entry = entry.next;
}
loadFactor = -loadFactor; // Mark hashCode computation complete
return h;
}
put()过程:
1、判断value是否为null,若为null抛出异常—》hashTable
2、通过key进行hash的获取到key该存储的索引位置
3、该索引位置的链表进行遍历,获取key是否存在(key存在的条件:hash是否相等,key使用equals() )
4、在存在该key的情况下,将value值进行更新且直接返回
5、key 不存在则进行新节点插入逻辑
5.1扩容考虑:entry节点个数大于阈值 (count > threshold)
5.2新扩容大小为:2*table.length+1
5.3将原哈希表中的数据全部进行重新hash到新的hash表中
5.4更新插入的key的新的位置
5.5找到新节点位置,创建entry实体通过头插法将元素进行插入
删除 remove()
删除操作本身也是线程安全的删除
过程:
1、通过key获取到当前存储的索引位置
2、对该索引位置链表进行遍历,找到key锁对应的entry实体、并进行删除
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)) {
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;
}
查找元素 get()
get() 方法本身也具有线性安全性
过程:
1、通过key来hash获取到存储索引位置-》通过key为null进行get操作也会抛出异常
2、遍历当前索引位置节点,判断是否相等(hash、equals),找到则直接返回value值
3、未找到则返回null
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;
}
拓展:
【1】HashMap不具有线程安全性,如何让它编程线性安全的?
答:使用集合工具类Collections使HashMap具有线性安全性,Collections.sychronizedMap(hashmap)
【2】HashTable能保证线性安全原理?
答:HashTable对相应方法添加Synchronized关键字,该关键字是一种互斥锁互斥锁的目的是保证同一时刻只能有一个线程对资源的访问在HashMap对put、get等一般方法添加Synchronized关键字,修饰的是类对象,该对象调用put操作即为该HashTable对象添加了一个互斥锁,那么在同一时刻只能一个线程访问该HashTable,从而保证添加元素不会出现异常