HashTable源码学习
一.介绍
1.HashMap和HashTable的区别
1.相同点
- 二者都实现了Map接口。
- 底层都是哈西表
2.不同点
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。
Hashtable 第一次创建对象的时候就会给底层数组开辟空间 Entry[] 11
HashMap 创建对象时 没有给底层数组进行空间开辟
HashMap把Hashtable的contains方法去掉了。改成containsValue和containsKey
Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
Hashtable中,key和value都不允许出现null值
HashMap中,null可以作为键,这样的键只有一个
哈希值的使用不同,HashTable直接使用对象的hashCode
HashMap重新计算hash值。
HashTable在不指定容量的情况下的默认容量为11
HashMap为16
Hashtable不要求底层数组的容量一定要为2的整数次幂
HashMap则要求一定为2的整数次幂。
Hashtable扩容时,将容量变为原来的2倍加1
HashMap扩容时,将容量变为原来的2倍。
Hashtable 中的方法是Synchronize的。
HashMap线程不安全
计算index的方法不同:
- HashTable: index = (hash & 0x7FFFFFFF) % tab.length
- HashMap:index = hash & (tab.length – 1)
2.HashTable
像HashMap一样,Hashtable在哈希表中存储键/值对。当使用一个哈希表,要指定用作键的对象,以及要链接到该键的值。
然后,该键经过哈希处理,所得到的散列码被用作存储在该表中值的索引
二.源码部分
1.基本属性
继承Dictionary 类
是一个抽象类,用来存储键/值对,作用和Map类相似。
给出键和值,你就可以将值存储在Dictionary对象中。一旦该值被存储,就可以通过它的键来获取它。所以和Map一样, Dictionary 也可以作为一个键/值对列表。
Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆
java.io.Serializable接口:
可以启用其序列化功能。未实现次接口的类无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
/**
* The hash table data.哈希表数据
*/
private transient Entry<?,?>[] table;
/**
* The total number of entries in the hash table.哈希表中enyty的总数
*/
private transient int count;
/**
* The table is rehashed when its size exceeds this threshold. (The
* value of this field is (int)(capacity * loadFactor).)
* 阈值,边界值
* @serial
*/
private int threshold;
/**
* The load factor for the hashtable.
* 加载因子、负载因子
* @serial
*/
private float loadFactor;
/**
* The number of times this Hashtable has been structurally modified
* Structural modifications are those that change the number of entries in
* the Hashtable or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the Hashtable fail-fast. (See ConcurrentModificationException).
*/
private transient int modCount = 0;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = 1421746759512286392L;
/**
* The maximum size of array to allocate.
* 要分配的数组的最大大小。
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2.构造函数
/**
* 无参构造,默认容量为11,加载因子为0.75
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
*/
public Hashtable() {
this(11, 0.75f);
}
/**
* Constructs a new, empty hashtable with the specified initial capacity
* and default load factor (0.75).
* 创建指定大小的哈希表
* 初始容量用户自定义,加载因子为默认的0.75
* @param initialCapacity the initial capacity of the hashtable.
* @exception IllegalArgumentException if the initial capacity is less
* than zero.
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty hashtable with the specified initial
* capacity and the specified load factor.
* 创建了一个指定大小的哈希表,并且通过fillRatio指定填充比例
* @param initialCapacity the initial capacity of the hashtable.
* @param loadFactor the load factor of the hashtable.
* @exception IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive.
*/
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);
//当初始化容量为0的时候,将初始容量设置为1
if (initialCapacity==0)
initialCapacity = 1;
//加载因子赋值
this.loadFactor = loadFactor;
//创建一个新的大小为初始容量的enytry数组
table = new Entry<?,?>[initialCapacity];
//阈值为initialCapacity * loadFactor或最大值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
/**
* Constructs a new hashtable with the same mappings as the given
* Map. The hashtable is created with an initial capacity sufficient to
* hold the mappings in the given Map and a default load factor (0.75).
* 第四个构造方法创建了一个以M中元素为初始化元素的哈希表。
* 哈希表的容量被设置为M的两倍,或是11
* @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);
}
3.contains()
/**
* Tests if some key maps into the specified value in this hashtable.
* This operation is more expensive than the {@link #containsKey
* containsKey} method.
* 测试此映射表中是否存在与指定值关联的键。
* <p>Note that this method is identical in functionality to
* {@link #containsValue containsValue}, (which is part of the
* {@link Map} interface in the collections framework).
*
* @param value a value to search for
* @return <code>true</code> if and only if some key maps to the
* <code>value</code> argument in this hashtable as
* determined by the <tt>equals</tt> method;
* <code>false</code> otherwise.
* @exception NullPointerException if the value is <code>null</code>
*/
public synchronized boolean contains(Object value) {
//如果传入的value值为空,则抛出异常
if (value == null) {
throw new NullPointerException();
}
//2层循环遍历数组及其链表
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)) {
return true;
}
}
}
return false;
}
4.put()
/**
* Maps the specified <code>key</code> to the specified
* <code>value</code> in this hashtable. Neither the key nor the
* value can be <code>null</code>. <p>
*
* The value can be retrieved by calling the <code>get</code> method
* with a key that is equal to the original key.
* 将指定 key 映射到此哈希表中的指定 value。
* @param key the hashtable key
* @param value the value
* @return the previous value of the specified key in this hashtable,
* or <code>null</code> if it did not have one
* @exception NullPointerException if the key or value is
* <code>null</code>
* @see Object#equals(Object)
* @see #get(Object)
*/
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();
//得到数组下标
int index = (hash & 0x7FFFFFFF) % tab.length;
//忽略警告
@SuppressWarnings("unchecked")
//将对应的数组元素保留起来
Entry<K,V> entry = (Entry<K,V>)tab[index];
//当该位置已有元素,则发生哈希冲突,遍历链表找通过equal方法找到key
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
//覆盖原来的值
V old = entry.value;
entry.value = value;
//返回原来的值
return old;
}
}
5.addEntry()
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
//如果数组有效值等于阈值,将进行扩容操作,然后在计算新的index
if (count >= threshold) {
// Rehash the table if the threshold is exceeded 如果超过阈值,请重新散列表
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.创建新条目。count++
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
6.扩容机制
/**
* Increases the capacity of and internally reorganizes this
* hashtable,增加hashtable的容量并对其重组
* in order to accommodate and access its entries more
* efficiently.
* 以便高效地访问更多的条目
* This method is called automatically when the number of keys in the hashtable exceeds this hashtable's capacity and load factor.
* 当哈希表中的键数超过该哈希表的容量和负载因子时,将自动调用此方法。
*/
@SuppressWarnings("unchecked")
protected void rehash() {
//先将数组原来的容量保存起来
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
//新的容量为原来的一半 + 1
int newCapacity = (oldCapacity << 1) + 1;
//如果新的容量超出了最大值,则将新容量变为最大值
if (newCapacity - MAX_ARRAY_SIZE > 0) {
//如果原来的容量就是最大值则将不扩容,return
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
//新建一个新容量大小的数组
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
//阈值也重新赋值,如若已经超出最大值,则为MAX_ARRAY_SIZE + 1
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//将table指向新数组
table = newMap;
//遍历原来的hashtable,将其拷贝过来
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;
//数组下标值不同于hashmap,都重新计算
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}
三.总结
- Hash:是一种信息摘要算法,它还叫做哈希,或者散列。我们平时使用的MD5,SHA1都属于Hash算法,通过输入key进行Hash计算,就可以获取key的HashCode(),比如我们通过校验MD5来验证文件的完整性。
- Hashtable 实现并发安全的原理是通过
synchronized 关键字
- 当线程数量增加的时候,Hashtable 的性能会急剧下降,因为每一次修改都需要锁住整个对象,而其他线程在此期间是不能操作的。不仅如此,还会带来额外的上下文切换等开销,所以此时它的吞吐量甚至还不如单线程的情况。
HashTable源码学习的更多相关文章
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- HashMap与HashTable源码学习及效率比较分析
一.个人学习后的见解: 首先表明学习源码后的个人见解,后续一次依次进行分析: 1.线程安全:HashMap是非线程安全的,HashTable是线程安全的(HashTable中使用了synchroniz ...
- Dapper源码学习和源码修改
之前ORM比较火热,自己也搞了个WangSql,但是感觉比较low,大家都说Dapper性能好,所以现在学习学习Dapper,下面简单从宏观层面讲讲我学习的Dapper. 再了解一个东西前,先得学会使 ...
- Hadoop源码学习笔记(1) ——第二季开始——找到Main函数及读一读Configure类
Hadoop源码学习笔记(1) ——找到Main函数及读一读Configure类 前面在第一季中,我们简单地研究了下Hadoop是什么,怎么用.在这开源的大牛作品的诱惑下,接下来我们要研究一下它是如何 ...
- 并发-HashMap和HashTable源码分析
HashMap和HashTable源码分析 参考: https://blog.csdn.net/luanlouis/article/details/41576373 http://www.cnblog ...
- HashMap的源码学习以及性能分析
HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...
- Java入门系列之集合Hashtable源码分析(十一)
前言 上一节我们实现了散列算法并对冲突解决我们使用了开放地址法和链地址法两种方式,本节我们来详细分析源码,看看源码中对于冲突是使用的哪一种方式以及对比我们所实现的,有哪些可以进行改造的地方. Hash ...
- Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析
目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...
- JUC源码学习笔记4——原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法
JUC源码学习笔记4--原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法 volatile的原理和内存屏障参考<Java并发编程的艺术> 原子类源码基于JDK8 ...
随机推荐
- “伏魔”赏金 | WebShell检测之「模拟污点引擎」首次公测,邀你来战!
安全是一个动态的过程,攻防对抗如同在赛博世界里降妖伏魔,其要义是:取彼之长,补己之短.--伏魔引擎的诞生 伏魔引擎挑战赛 注册时间: 2022.01.10 00:00:00 - 2022.01.24 ...
- Linux下Julia安装
1.找到官网,执行 wget https://julialang-s3.julialang.org/bin/linux/x64/1.4/julia-1.4.0-linux-x86_64.tar.gz ...
- i-Urban Renovation使用3D Tiles可视化鸟取县Munakata建筑状态
Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 日本的鸟取县,使用i-Urban Renovation appl ...
- Ajax_Json用法
Ajax_Json用法 关于json的服务端代码 //首先在方法里面设置一个响应json数据对象 const data = { name:'chenxigua' } //因为 s ...
- 关于C++11共享数据带来的死锁问题的提出与解决
举个例子,如果有一份资源,假如为list<int>资源,假设有两个线程要对该资源进行压入弹出操作,如果不进行锁的话,那么如果两个线程同时操作,那么必然乱套,得到的结果肯定不是我们想要的结果 ...
- && || 区别
command1 && command2 如果command1 成功,那么就执行command2 command1 || command2 如果command1 不成功,那么就执行co ...
- URL Rewrite(四种重定向策略)
目录 一:Rewrite基本概述 1.Rewrite简介 2.Rewrite基本概述 3.Rewrite作用 4.什么是URL? 二:rewrite语法 三:Rewrite标记Flag 1.last和 ...
- undo和redo的区别
undo和redo的区别: undo一般用于事务的取消与回滚,记录的是数据修改前的值: redo一般用于恢复已确认但未写入数据库的数据,记录的是数据修改后的值.
- Device or resource busy
格式化磁盘显示忙碌,如何解决呢? [root@jp33e503-11-8 ~]# mkfs.xfs /dev/sdc mkfs.xfs: cannot open /dev/sdc: Device or ...
- python 小兵(7)迭代器
阅读目录 函数名的使用以及第一类对象 闭包 迭代器 回到顶部 函数名的使用以及第一类对象 函数名的运用 函数名是一个变量, 但它是一个特殊的变量, 与括号配合可以执行函数的变量 1.函数名的内存地址 ...