Hashtable实现类
Hashtable:线程安全的,不允许null的键或值;是线程安全的但是Hashtable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差
基础编程
Map<Integer,String> map=new Hashtable<>();
map.put(11,"zhangsan");
map.put(22,"lisi");
map.put(11,"wangwu");
for(Integer tmp:map.keySet()){
String val=map.get(tmp);
System.out.println(tmp+"-->"+val);
}
String removed = map.remove(11);
System.out.println(removed);
System.out.println(map.size());
map.clear();
//存储空键和空值的问题
Map<Integer,String> map=new Hashtable<>();
// Map<Integer,String> map=new HashMap<>();
map.put(11,null);//NullPointerException
// map.put(null,"lisis");//Hashtable中空键会有NullPointerException
System.out.println(map.size());
类定义
/**
Since: 1.0 古老的类,目前不再推荐使用
*/
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
数据存储结构
private transient Entry<?,?>[] table; 具体的存储使用仍旧和HashMap一致,数组+链表
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash; 缓存节点的hash值
final K key; key值
V value; value值
Entry<K,V> next; 单向链表
构造器
public Hashtable() {
this(11, 0.75f); 默认的初始化容积为11,默认加载因子0.75
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); 初始化容积小于0则运行时异常
if (loadFactor <= 0 || Float.isNaN(loadFactor)) 如果负载因子小于等于0则运行异常,一般建议负载因子取值范围为(0,1)之间,事实上也可以大于等于1
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0) 如果初始化容积为0则默认为1
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity]; 立即初始化数组
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); 扩容阈值,取值为[初始化容积*负载因子]和Integer.MAX_VALUE - 8之间较小的值
}
成员方法
public synchronized V put(K key, V value)
public synchronized V remove(Object key)
... ...
绝大部分的方法上都有synchronized,线程安全,会影响并发性,但是提高数据的安全性
public synchronized V put(K key, V value) {
if (value == null) throw new NullPointerException(); 不允许值为null,否则异常
Entry<?,?> tab[] = table;
int hash = key.hashCode(); 调用key对象的成员方法hashCode获取key的hash码值
int index = (hash & 0x7FFFFFFF) % tab.length; 去除所获取hash码值的符号位,并对数组的长度进行求余,实际上就是获取key所对应的桶位置
Entry<K,V> entry = (Entry<K,V>)tab[index]; 获取数组元素,也就是单向链的首节点
for(; entry != null ; entry = entry.next) { 遍历桶对应的单向链
if ((entry.hash == hash) && entry.key.equals(key)) { 当单向链上某个节点上的hash值相等并且equals为true时进行节点数据的覆盖。【要求定义键对应类型时当equals为true时hashCode值必须相等】
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index); 如果单向链上不存在相等的key则执行添加操作
return null ;
}
private void addEntry(int hash, K key, V value, int index) {
Entry<?,?> tab[] = table; 临时存储数组
if (count >= threshold) { 如果当前存储的元素个数大于等于扩容阈值则进行扩容处理
rehash(); 扩容数组,新容积=老容积*2+1
tab = table;
hash = key.hashCode(); 获取key对应的hash码值
index = (hash & 0x7FFFFFFF) % tab.length; 根据hash码值通过对数组长度求余的方式获取存放位置的下标
}
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e); 将数据存储到数组中
count++;
modCount++;
}
protected void rehash() {
int oldCapacity = table.length; 获取原始数组的长度
Entry<?,?>[] oldMap = table; 缓存数组
int newCapacity = (oldCapacity << 1) + 1; 新容积=原始容积*2+1
if (newCapacity - MAX_ARRAY_SIZE > 0) { 新容积是否越界,如果超出Integer.MAX_VALUE - 8值,则新容积为Integer.MAX_VALUE - 8
if (oldCapacity == MAX_ARRAY_SIZE)
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; 按照新容积创建新数组
modCount++; 修改次数+1,用于实现遍历时的快速异常
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); 按照新容积值获取扩容阈值
table = newMap; 将新数组赋值给存储数据的数组属性
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;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
HashMap与HashTable区别
1、线程安全:HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰。如果要保证线程安全的话就建议使用ConcurrentHashMap,也可以通过Collections.synchronizedMap(hashmap)方式将hashmap转换为线程安全的map
2、效率:因为线程安全的问题,HashMap要比HashTable效率高一点。另外HashTable基本被淘汰,不要在代码中使用它
3、对Null key和Null value的支持:HashMap中null可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为null。但是在HashTable中put进的键值只要有一个null,直接抛NullPointerException。
4、初始容量大小和每次扩充容量大小的不同 :
①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
②创建时如果给定了容量初始值,那么Hashtable会直接使用给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说HashMap总是使用2的幂作为哈希表的大小。
5、底层数据结构:JDK1.8以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认8)时,将链表转化为红黑树,以减少搜索时间。Hashtable没有这样的机制。
推荐使用:在Hashtable的类注释可以看到,Hashtable是保留类不建议使用,推荐在单线程环境下使用HashMap替代,如果需要多线程使用则用ConcurrentHashMap替代。
HashMap与HashTable区别
最新推荐文章于 2024-10-01 18:51:18 发布