[置顶] HashTable vs HashMap
转载请注明出处:http://blog.csdn.net/crazy1235/article/details/76686891
关于HashMap的分析,请详见下面这两篇文章:
以**JDK1.7**版本的HashTable和HashMap为例来分析!
HashTable vs HashMap
HashMap和HashTable有什么区别?
HashMap是非线程安全的,HashTable是线程安全的。
HashMap的键和值都允许有null值存在,而HashTable则不行。
因为线程安全的问题,HashMap效率比HashTable的要高。
HashTable的key和value都不允许为null值,而HashMap的key和value则都是允许null值的。
HashTable的迭代器使用Enumeration,HashMap的迭代器使用Iterator。
Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
-
(图片转自网路)
类比着HashMap来说:
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
HashTable 集成了 Dictionary 这个类。
构造函数
public Hashtable() {
this(11, 0.75f); // 默认容量为11,加载因子为0.75
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
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];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
useAltHashing = sun.misc.VM.isBooted() &&
(initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
}
hash函数
transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);
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();
// 这部分的hash运算与HashMap中的一样
// 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();
}
}
useAltHashing 为 boolean,其如果为真,则执行另一散列的字符串键,以减少由于弱哈希计算导致的哈希冲突的发生。
// 最大数组长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 当容量超过阈值的时候,会调用rehash()函数来重新构造table数组
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;
// 遍历旧数组,重新计算每个节点的hash值,然后添加到新数组(拉链表)中去
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;
}
}
}
synchronized
与HashMap相比,HashTable的函数都加上了synchronized来进行加锁。但是这样会导致HashTable执行效率低于HashMap。
public synchronized int size() {
return count;
}
public synchronized boolean isEmpty() {
return count == 0;
}
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
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;
}
// 。。。函数很多,就不一一列举了
从上面的contains()函数可以看出,HashTable内部实现也是采用“拉链法”保存数据的!
HashTable相比HashMap多了一个contains(Object value)函数,它与HashMap中的containsValue(Object value)的实现一致!
put
// 保存一个集合数据
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());
}
// 同样是加了锁
public synchronized V put(K key, V value) {
// value不能为null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
// 判断是否有这个元素,如果有更新value,返回旧的value值
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++;
if (count >= threshold) { // 如果元素个数超过阈值,则需要重新扩容
// 重建数组
rehash();
tab = table;
hash = hash(key);
// 计算新节点在数组中的位置
index = (hash & 0x7FFFFFFF) % tab.length;
}
// 插入新节点
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
get
// 同步方法
public synchronized V get(Object key) {
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)) {
return e.value;
}
}
return null;
}
contains
public boolean containsValue(Object value) {
return contains(value);
}
public synchronized boolean containsKey(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 true;
}
}
return false;
}
remove
// 同步函数
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--; // 节点个数-1
V oldValue = e.value;
e.value = null; // e置空
return oldValue;
}
}
return null;
}
clear
// 清空数据
public synchronized void clear() {
Entry tab[] = table;
modCount++;
// 很暴力,直接将数组各个元素置为null
for (int index = tab.length; --index >= 0; )
tab[index] = null;
//
count = 0;
}
遍历
private static final int KEYS = 0;
private static final int VALUES = 1;
private static final int ENTRIES = 2;
// 遍历key
public synchronized Enumeration<K> keys() {
return this.<K>getEnumeration(KEYS);
}
// 遍历value
public synchronized Enumeration<V> elements() {
return this.<V>getEnumeration(VALUES);
}
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {
return Collections.emptyEnumeration();
} else {
return new Enumerator<>(type, false);
}
}
// 迭代器
private <T> Iterator<T> getIterator(int type) {
if (count == 0) {
return Collections.emptyIterator();
} else {
return new Enumerator<>(type, true);
}
}
private transient volatile Set<K> keySet = null;
private transient volatile Set<Map.Entry<K,V>> entrySet = null;
private transient volatile Collection<V> values = null;
public Set<K> keySet() {
if (keySet == null)
keySet = Collections.synchronizedSet(new KeySet(), this);
return keySet;
}
// key set
private class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return getIterator(KEYS); // 1
}
public int size() {
return count;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return Hashtable.this.remove(o) != null;
}
public void clear() {
Hashtable.this.clear();
}
}
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet==null)
entrySet = Collections.synchronizedSet(new EntrySet(), this);
return entrySet;
}
// EentrySet
private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return getIterator(ENTRIES); // 2
}
public boolean add(Map.Entry<K,V> o) {
return super.add(o);
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry entry = (Map.Entry)o;
Object key = entry.getKey();
Entry[] tab = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next)
if (e.hash==hash && e.equals(entry))
return true;
return false;
}
public boolean remove(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K,V> entry = (Map.Entry<K,V>) o;
K key = entry.getKey();
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.equals(entry)) {
modCount++;
if (prev != null)
prev.next = e.next;
else
tab[index] = e.next;
count--;
e.value = null;
return true;
}
}
return false;
}
public int size() {
return count;
}
public void clear() {
Hashtable.this.clear();
}
}
public Collection<V> values() {
if (values==null)
values = Collections.synchronizedCollection(new ValueCollection(),
this);
return values;
}
// value collection
private class ValueCollection extends AbstractCollection<V> {
public Iterator<V> iterator() {
return getIterator(VALUES); // 1
}
public int size() {
return count;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
Hashtable.this.clear();
}
}
从上面的函数可以看出,都离不开【Enumerator】这个类。
private class Enumerator<T> implements Enumeration<T>, Iterator<T>
Enumeration
Enumeration是java.util中的一个接口类,在Enumeration中封装了有关枚举数据集合的方法,与Iterator差不多,用来遍历集合中的元素。
枚举Enumeration只提供了遍历Vector和Hashtable类型集合元素的功能,这种类型的集合对象通过调用elements()方法获取一个Enumeration对象 然后Enumeratino对象再调用以下方法来对集合中的元素进行遍历。
Iterator和Enumeration区别
接口函数不同
public interface Enumeration<E> {
//
boolean hasMoreElements();
//
E nextElement();
}
public interface Iterator<E> {
//
boolean hasNext();
//
E next();
//
void remove();
}
- 可以看出最大的区别是 Iterator支持删除元素!
Iterator支持fail-fast机制,而Enumeration不支持
Enumeration 是JDK 1.0添加的接口。使用到它的函数包括Vector、Hashtable等类,这些类都是JDK 1.0中加入的,Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步,而在Vector、Hashtable实现Enumeration时,添加了同步。
而Iterator 是JDK 1.2才添加的接口,它也是为了HashMap、ArrayList等集合提供遍历接口。Iterator是支持fail-fast机制的:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
fail-fast 机制
fail-fast 机制是java集合(Collection)中的一种错误机制。
当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;
那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
在分析HashMap源码的时候就说道了这个机制:
参考
http://www.cnblogs.com/skywang12345/p/3308762.html
http://blog.csdn.net/chenssy/article/details/38151189
http://www.cnblogs.com/skywang12345/p/3311275.html
https://www.zhihu.com/question/20581065
http://www.jb51.net/article/84900.htm
[置顶] HashTable vs HashMap的更多相关文章
- Java中List,ArrayList、Vector,map,HashTable,HashMap区别用法
Java中List,ArrayList.Vector,map,HashTable,HashMap区别用法 标签: vectorhashmaplistjavaiteratorinteger ArrayL ...
- Java容器类List、ArrayList、Vector及map、HashTable、HashMap的区别与用法
Java容器类List.ArrayList.Vector及map.HashTable.HashMap的区别与用法 ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数 ...
- [置顶] Android开发笔记(成长轨迹)
分类: 开发学习笔记2013-06-21 09:44 26043人阅读 评论(5) 收藏 Android开发笔记 1.控制台输出:called unimplemented OpenGL ES API ...
- 在UWP中页面滑动导航栏置顶
最近在研究掌上英雄联盟,主要是用来给自己看新闻,顺便copy个界面改一下段位装装逼,可是在我copy的时候发现这个东西 当你滑动到一定距离的时候导航栏会置顶不动,这个特性在微博和淘宝都有,我看了@ms ...
- WinFrom窗体始终置顶
调用WindowsAPI使窗体始终保持置顶效果,不被其他窗体遮盖: [DllImport("user32.dll", CharSet = CharSet.Auto)] privat ...
- winform窗体置顶
winform窗体置顶 金刚 winform 置顶 今天做了一个winform小工具.需要设置置顶功能. 网上找了下,发现百度真的很垃圾... 还是必应靠谱些. 找到一个可以链接. https://s ...
- 自定义置顶TOP按钮
简述一下,分为三个步骤: 1. 添加Html代码 2. 调整Css样式 3. 添加Jquery代码 具体代码如下: <style type="text/css"> #G ...
- ahk之路:利用ahk在window7下实现窗口置顶
操作系统:win7 64位 ahk版本:autohotkey_L1.1.24.03 今天安装了AutoHotkey_1.1.24.03.SciTE.PuloversMacroCreator,重新开始我 ...
- HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别
①HashMap的工作原理 HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象.当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算h ...
随机推荐
- zcash 的资料
我的比特币使用的是 electrum 2.9.3 版本.我的zcash用的是 jaxx 1.2 版. zcash又叫zec,可以在 bithumb (韩国的) 平台上进行交易zcash. zcash ...
- arthas使用介绍
背景: 一次线上问题的综合排查排查,两个相同的系统的某个模块,数据量更少的系统查询更慢. 先说下整体思路: 查看系统整理负载,网络有100左右毫秒的延迟,看起来影响不大 查看正序运行整体情况,一次查询 ...
- (转)JSON Web Token - 在Web应用间安全地传递信息
JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息. 让我们来假想一下一个场景.在A用户关注了B用户的时候,系统发邮件给B用户, ...
- google chrome插件开发,自己动手,丰衣足食
因为平时上网都用chrome,但总感觉除了速度快,简洁以外总还有地方满足不了我的需要,然后找插件…后来发现,插件虽然海量但找个称心如意的也不是件容易的事儿,用在找插件的时间都能自己写一个了,于是,今年 ...
- SQL语句中case函数
case函数,严格的意义上来讲case函数已经试流程控制语句了,不是简单意义上的函数,不过为了方便,很多人将case函数称为流程控制函数. case函数的一般有两种用法:1.case expressi ...
- curl基本使用
curl简介 linux curl是一个利用URL规则在命令行下工作的文件传输工具.它支持文件的上传和下载. curl可以使用URL的语法模拟浏览器来传输数据,因为它是模拟浏览器,因此它同样支持多种协 ...
- iview使用vue-i18n实现国际化
iview官网中和网上的例子中使用的都是webpack方式,需要import js文件,但是由于项目架构比较简单,没有使用webpack,纯html和js进行交互.所以这里就直接使用js文件引用方式. ...
- 怎样快速掌握一个用你没学过的框架写的PHP项目?
我的思路一般是先搞定框架的route.也就是说,明白一个请求的url地址是对应的哪个controller处理的,找到controller后,再理解一下它的类库加载方案,也就是说一些辅助类以及自己逻辑类 ...
- 20145204《Java程序设计》第5周学习总结
20145204<Java程序设计>第5周学习总结 教材学习内容总结 语法与继承构架 我们之前接触到的C通常都是将程序流程和错误处理混在一起,在编写程序的时候必须考虑可能出现的错误并提前做 ...
- 如何运行.ipynb文件
首先cmd下面输入: pip install jupyter notebook ,安装慢的改下pip的源为国内的源 然后cmd中输入: jupyter notebook就会弹出一个页面 先upload ...