- 浏览: 188337 次
- 性别:
- 来自: 上海
文章分类
最新评论
区别:
1 继承类不同:
A.HashMap继承AbstractMap
B.Hashtable继承Dictionary
2
执行效率不同:
A.HashMap是非线程安全的,是Hashtable的轻量级实现,效率较高
B.Hashtable是线程安全的,效率较低
3
put方法对key和value的要求不同
A.HashMap允许Entry的key或value为null
B.Hashtable不允许Entry的key或value为null,否则出现NullPointerException
4
有无contains方法
A.HashMap没有contains方法
B.Hashtable有contains方法
END
注意事项
注意:Hashtale是Syncchronize的,而HashMap是Asyncchronize的,当多个线程访问Hashtable时,Hashtable不需要自己为它的方法实现同步;而当多个线程访问HashMap时,需要通过Collections.synchronizedMap来同步HashMap。
一、HashMap的内部存储结构
Java中数据存储方式最底层的两种结构,一种是数组,另一种就是链表,数组的特点:连续空间,寻址迅速,但是在删除或者添加元素的时候需要有较大幅度的移动,所以查询速度快,增删较慢。而链表正好相反,由于空间不连续,寻址困难,增删元素只需修改指针,所以查询慢、增删快。有没有一种数据结构来综合一下数组和链表,以便发挥他们各自的优势?答案是肯定的!就是:哈希表。哈希表具有较快(常量级)的查询速度,及相对较快的增删速度,所以很适合在海量数据的环境中使用。一般实现哈希表的方法采用“拉链法”,我们可以理解为“链表的数组
从上图中,我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。它的内部其实是用一个Entity数组来实现的,属性有key、value、next。接下来我会从初始化阶段详细的讲解HashMap的内部结构。
一句话回答
如果任何人让我描述一下HashMap的工作机制的话,我就简单的回答:“基于Hash的规则”。这句话非常简单,但是要理解这句话之前,首先我们得了解什么是哈希,不是么?
什么是哈希
哈希简单的说就是对变量/对象的属性应用某种算法后得到的一个唯一的串,用这个串来确定变量/对象的唯一性。一个正确的哈希函数必须遵守这个准则。
当哈希函数应用在相同的对象或者equal的对象的时候,每次执行都应该返回相同的值。换句话说,两个相等的对象应该有相同的hashcode。
注:所有Java对象都从Object类继承了一个默认的hashCode()方法。这个方法将对象在内存中的地址作为整数返回,这是一个很好的hash实现,他确保了不同的对象拥有不同的hashcode。
关于Entry类的一点介绍
一个map的定义是:一个映射键(key)到值(value)的对象。非常简单对吧。
所以,在HashMap中一定有一定的机制来存储这些键值对。使得,HashMap有一个内部类Entry,看起来像这样。
static class Entry<K,V> implements Map.Entry<K,V>
{
final K key;
V value;
Entry<K,V> next;
final int hash;
...//More code goes here
}
当然,Entry类有属性用来存储键值对映射。key被final标记,除了key和value,我们还能看到两个变量next和hash。接下来我们试着理解这些变量的含义。
put()方法实际上做了什么
再进一步看put方法的实现之前,我们有必要看一看Entry实例在数组中的存储,HashMap中是这样定义的:
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
现在再来看put方法的实现。
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
让我们一步一步的看
首先,检查key是否为null,如果key是null值被存在table[0]的位置,因为null的hashcode始终为0接下来,通过key的hashCode()方法计算了这个key的hash值,这个hash值被用来计算存储Entry对象的数组中的位置。JDK的设计者假设会有一些人可能写出非常差的hashCode()方法,会出现一些非常大或者非常小的hash值。为了解决这个问题,他们引入了另外一个hash函数,接受对象的hashCode(),并转换到适合数组的容量大小。
接着是indexFor(hash,table,length)方法,这个方法计算了entry对象存储的准确位置。
接下来就是主要的部分,我们都知道两个不相等的key对象可能拥有过相同的hashCode值,两个不同的对象是怎么存储在相同的位置[叫做bucket]呢?
答案是LinkedList。如果你记得,Entry类有一个next变量,这个变量总是指向链中的下一个变量,这完全符合链表的特点。
所以,在发生碰撞的时候,entry对象会被以链表的形式存储起来,当一个Entry对象需要被存储的时候,hashmap检查该位置是否已近有了一个entry对象,如果没有就存在那里,如果有了就检查她的next属性,如果是空,当前的entry对象就作为已经存储的entry对象的下一个节点,依次类推。
如果我们给已经存在的key存入另一个value会怎么样的?逻辑上,旧的value值将被替换掉。在检测了Entry对象的存储位置后,hashmap将会遍历那个位置的entry链表,判断链表中的entry的key是否与传入的key相同,相同则替换(e.hash == hash && ((k = e.key) == key || key.equals(k)))。
在这种方式下HashMap就能保证key的唯一性。
get方法的工作机制
现在我们已经了解了HashMap中存储键值对的机制。下一个问题是:怎样从一个HashMap中查询结果。
其实逻辑跟put是一样的,如果传入的key有匹配就将该位置的value返回,如果没有就返回null.
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*
* <p>A return value of {@code null} does not <i>necessarily</i>
* indicate that the map contains no mapping for the key; it's also
* possible that the map explicitly maps the key to {@code null}.
* The {@link #containsKey containsKey} operation may be used to
* distinguish these two cases.
*
* @see #put(Object, Object)
*/
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
上面的代码看起来跟put()方法很像,除了if (e.hash == hash && ((k = e.key) == key || key.equals(k)))。
注意点
存储Entry对象的数据结构是一个叫做Entry类型的table数组。
数组中一个特定的索引位置称为bucket,因为它可以容纳一个LinkedList的第一个元素的对象。
Key对象的hashCode()需要用来计算Entry对象的存储位置。
Key对象的equals()方法需要用来维持Map中对象的唯一性。
get()和put()方法跟Value对象的hashCode和equals方法无关。
null的hashCode总是0,这样的Entry对象总是被存储在数组的第一个位置
如果数据大小是固定的,那么最好给HashMap设定一个合理的容量值
根据上面的分析,HashMap的初始默认容量是16,默认加载因子是0.75,也就是说,如果采用HashMap的默认构造函数,当增加数据时,数据实际容量超过16*0.75=12时,HashMap就扩容,扩容带来一系列的运算,新建一个是原来容量2倍的数组,对原有元素全部重新哈希,如果你的数据有几千几万个,而用默认的HashMap构造函数,那结果是非常悲剧的,因为HashMap不断扩容,不断哈希,在使用HashMap的场景里,不会是多个线程共享一个HashMap,除非对HashMap包装并同步,由此产生的内存开销和cpu开销在某些情况下可能是致命的。
以上摘自http://itlab.idcquan.com/Java/advance/906116.html
1 继承类不同:
A.HashMap继承AbstractMap
B.Hashtable继承Dictionary
2
执行效率不同:
A.HashMap是非线程安全的,是Hashtable的轻量级实现,效率较高
B.Hashtable是线程安全的,效率较低
3
put方法对key和value的要求不同
A.HashMap允许Entry的key或value为null
B.Hashtable不允许Entry的key或value为null,否则出现NullPointerException
4
有无contains方法
A.HashMap没有contains方法
B.Hashtable有contains方法
END
注意事项
注意:Hashtale是Syncchronize的,而HashMap是Asyncchronize的,当多个线程访问Hashtable时,Hashtable不需要自己为它的方法实现同步;而当多个线程访问HashMap时,需要通过Collections.synchronizedMap来同步HashMap。
一、HashMap的内部存储结构
Java中数据存储方式最底层的两种结构,一种是数组,另一种就是链表,数组的特点:连续空间,寻址迅速,但是在删除或者添加元素的时候需要有较大幅度的移动,所以查询速度快,增删较慢。而链表正好相反,由于空间不连续,寻址困难,增删元素只需修改指针,所以查询慢、增删快。有没有一种数据结构来综合一下数组和链表,以便发挥他们各自的优势?答案是肯定的!就是:哈希表。哈希表具有较快(常量级)的查询速度,及相对较快的增删速度,所以很适合在海量数据的环境中使用。一般实现哈希表的方法采用“拉链法”,我们可以理解为“链表的数组
从上图中,我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。它的内部其实是用一个Entity数组来实现的,属性有key、value、next。接下来我会从初始化阶段详细的讲解HashMap的内部结构。
一句话回答
如果任何人让我描述一下HashMap的工作机制的话,我就简单的回答:“基于Hash的规则”。这句话非常简单,但是要理解这句话之前,首先我们得了解什么是哈希,不是么?
什么是哈希
哈希简单的说就是对变量/对象的属性应用某种算法后得到的一个唯一的串,用这个串来确定变量/对象的唯一性。一个正确的哈希函数必须遵守这个准则。
当哈希函数应用在相同的对象或者equal的对象的时候,每次执行都应该返回相同的值。换句话说,两个相等的对象应该有相同的hashcode。
注:所有Java对象都从Object类继承了一个默认的hashCode()方法。这个方法将对象在内存中的地址作为整数返回,这是一个很好的hash实现,他确保了不同的对象拥有不同的hashcode。
关于Entry类的一点介绍
一个map的定义是:一个映射键(key)到值(value)的对象。非常简单对吧。
所以,在HashMap中一定有一定的机制来存储这些键值对。使得,HashMap有一个内部类Entry,看起来像这样。
static class Entry<K,V> implements Map.Entry<K,V>
{
final K key;
V value;
Entry<K,V> next;
final int hash;
...//More code goes here
}
当然,Entry类有属性用来存储键值对映射。key被final标记,除了key和value,我们还能看到两个变量next和hash。接下来我们试着理解这些变量的含义。
put()方法实际上做了什么
再进一步看put方法的实现之前,我们有必要看一看Entry实例在数组中的存储,HashMap中是这样定义的:
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
现在再来看put方法的实现。
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
让我们一步一步的看
首先,检查key是否为null,如果key是null值被存在table[0]的位置,因为null的hashcode始终为0接下来,通过key的hashCode()方法计算了这个key的hash值,这个hash值被用来计算存储Entry对象的数组中的位置。JDK的设计者假设会有一些人可能写出非常差的hashCode()方法,会出现一些非常大或者非常小的hash值。为了解决这个问题,他们引入了另外一个hash函数,接受对象的hashCode(),并转换到适合数组的容量大小。
接着是indexFor(hash,table,length)方法,这个方法计算了entry对象存储的准确位置。
接下来就是主要的部分,我们都知道两个不相等的key对象可能拥有过相同的hashCode值,两个不同的对象是怎么存储在相同的位置[叫做bucket]呢?
答案是LinkedList。如果你记得,Entry类有一个next变量,这个变量总是指向链中的下一个变量,这完全符合链表的特点。
所以,在发生碰撞的时候,entry对象会被以链表的形式存储起来,当一个Entry对象需要被存储的时候,hashmap检查该位置是否已近有了一个entry对象,如果没有就存在那里,如果有了就检查她的next属性,如果是空,当前的entry对象就作为已经存储的entry对象的下一个节点,依次类推。
如果我们给已经存在的key存入另一个value会怎么样的?逻辑上,旧的value值将被替换掉。在检测了Entry对象的存储位置后,hashmap将会遍历那个位置的entry链表,判断链表中的entry的key是否与传入的key相同,相同则替换(e.hash == hash && ((k = e.key) == key || key.equals(k)))。
在这种方式下HashMap就能保证key的唯一性。
get方法的工作机制
现在我们已经了解了HashMap中存储键值对的机制。下一个问题是:怎样从一个HashMap中查询结果。
其实逻辑跟put是一样的,如果传入的key有匹配就将该位置的value返回,如果没有就返回null.
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*
* <p>A return value of {@code null} does not <i>necessarily</i>
* indicate that the map contains no mapping for the key; it's also
* possible that the map explicitly maps the key to {@code null}.
* The {@link #containsKey containsKey} operation may be used to
* distinguish these two cases.
*
* @see #put(Object, Object)
*/
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
上面的代码看起来跟put()方法很像,除了if (e.hash == hash && ((k = e.key) == key || key.equals(k)))。
注意点
存储Entry对象的数据结构是一个叫做Entry类型的table数组。
数组中一个特定的索引位置称为bucket,因为它可以容纳一个LinkedList的第一个元素的对象。
Key对象的hashCode()需要用来计算Entry对象的存储位置。
Key对象的equals()方法需要用来维持Map中对象的唯一性。
get()和put()方法跟Value对象的hashCode和equals方法无关。
null的hashCode总是0,这样的Entry对象总是被存储在数组的第一个位置
如果数据大小是固定的,那么最好给HashMap设定一个合理的容量值
根据上面的分析,HashMap的初始默认容量是16,默认加载因子是0.75,也就是说,如果采用HashMap的默认构造函数,当增加数据时,数据实际容量超过16*0.75=12时,HashMap就扩容,扩容带来一系列的运算,新建一个是原来容量2倍的数组,对原有元素全部重新哈希,如果你的数据有几千几万个,而用默认的HashMap构造函数,那结果是非常悲剧的,因为HashMap不断扩容,不断哈希,在使用HashMap的场景里,不会是多个线程共享一个HashMap,除非对HashMap包装并同步,由此产生的内存开销和cpu开销在某些情况下可能是致命的。
以上摘自http://itlab.idcquan.com/Java/advance/906116.html
发表评论
文章已被作者锁定,不允许评论。
-
ReentrantLock与Condition
2017-03-17 14:25 526多线程和并发性并不是什么新内容,但是 Java 语言设计中的创 ... -
java linux监控
2017-03-13 17:49 483http://agapple.iteye.com/blog/1 ... -
transient和volatile两个关键字
2017-02-16 09:47 572transient和volatile两个关 ... -
java 锁机制
2016-12-09 13:43 465一段synchronized的代码被 ... -
java 正则表达式
2016-12-02 10:28 516众所周知,在程序开发中,难免会遇到需要匹配、查找、替换、判断字 ... -
java ClassNotFoundException和NoClassDefFoundException的差别
2016-08-17 19:47 907首先从名字上可以看出一类是异常,一类属于错误。异常可以通过异常 ... -
ThreadLocal
2016-07-19 11:10 327ThreadLocal是什么 Thre ... -
java CAS
2016-07-10 14:55 333cas 乐观锁每次不锁定整个线程,在操作之前进行判断。悲观锁独 ... -
concurrenthashmap
2016-07-10 11:11 422hash table虽然性能上不如 ... -
java 线程池的使用
2016-07-10 09:52 3721. 引言 合理利用线程池能够带来三个好处。第一:降低资源消 ... -
java.util.concurrent
2016-07-03 16:24 409我们都知道,在JDK1.5之 ... -
JVM 配置 以及垃圾收集器的选择
2016-04-15 12:36 728JVM监控的关键指标说明: a) FGC的环比增加次数。Zab ... -
jvm实时监控工具
2016-04-09 09:35 461 -
哈希 、一致性哈希、余数式哈希
2016-04-07 16:10 861什么是Hash Hash,一 ... -
jvm dump 相关
2016-03-22 17:22 681http://www.cnblogs.com/edwardla ... -
深入剖析volatile关键字
2016-03-21 16:02 534深入剖析volatile关键字 ... -
java线程安全问题之静态变量、实例变量、局部变量
2016-03-08 12:52 571java多线程编程中,存在很多线程安全问题,至于什么是线程安全 ... -
有状态的bean和无状态的bean的区别
2016-03-08 11:23 1493有状态会话bean :每个用户有自己特有的一个实例,在用户的生 ... -
Java nio详解
2016-01-20 16:30 551http://www.ibm.com/developerwor ... -
java 不定长数组
2015-11-24 15:00 768在调用某个方法时,若是方法的参数个数事先无法确定该如何处理 ...
相关推荐
《马士兵老师HashMap学习笔记详解》 HashMap是Java编程语言中常用的一种数据结构,它提供了键值对(key-value pair)的存储功能,是基于哈希表...通过深入学习和实践,开发者可以更好地应对实际开发中遇到的各种挑战。
通过深入学习和理解这些知识点,你将能够在面试中自信地应对关于HashMap的问题,提升你的专业技能。这套学习资料应该包含了HashMap的实例分析、源码解读、常见面试题以及实战演练等内容,确保你全面掌握这一核心Java...
本文将深入探讨HashMap的内部机制,包括其构造、工作原理、哈希函数、冲突解决策略以及扩容机制。 首先,HashMap的基本结构是由数组(Entry[] table)和链表组成的。每个元素是一个内部类Entry,它包含了键值对...
易语言HashMap类是一种在易语言编程环境中实现的高效数据结构,它主要用于存储键值对(key-value pairs),提供快速的数据存取。...通过深入学习和实践,开发者可以更好地利用HashMap类解决实际编程问题。
通过分析源码,开发者可以深入理解哈希表的工作原理,学习如何在易语言中实现高效的数据结构,这对于提升程序性能和优化内存管理至关重要。同时,这也为自定义数据结构或实现其他哈希表相关的功能提供了基础。
"学习笔记:三数组实现的HashMap"这篇博客文章可能讨论了一种非标准但有趣的HashMap实现,使用了三个数组来代替标准的哈希表结构。这里我们将深入探讨这种三数组实现HashMap的原理和可能的优势。 1. **基本概念**:...
在这个压缩包文件"hashmap.zip"中,包含了关于HashMap的案例、资料、讲义等资源,是深入学习HashMap的好材料。 首先,我们需要了解HashMap的基本概念。HashMap是一个散列容器,内部通过数组加链表的方式实现。每个...
在Java编程语言中,HashMap是集合框架中一个重要的类,用于存储键值对的数据结构。面试中,HashMap的源码分析与实现是一个常见的考察点,...深入学习和实践HashMap源码,能够帮助我们更好地理解和优化Java应用程序。
HashMap是Java编程语言中最常用的集合类之一,它提供了一种基于键值对(key-value pair)的数据存储方式,具有高效查找、插入和删除操作。...对于学习者来说,阅读源码并结合实践是掌握HashMap的最好方式。
在Java编程中,HashMap是开发人员最常用的集合类之一,用于存储键值对。然而,对于多线程环境,HashMap并不是线程安全的,这在并发编程中可能会引发一系列...通过深入学习和实践,可以提升你在并发编程领域的专业水平。
在本教程中,我们将深入探讨如何使用HashMap来实现产品的创建(Create)、读取(Read)、更新(Update)和删除(Delete),这对于初学者来说是一个很好的实践案例。 **1. HashMap基础** HashMap在内部使用了哈希表...
### 使用HashMap模拟网上购物车 ...通过本次实验,我们深入了解了`HashMap`的使用方法,并能够将其应用到实际问题中。同时,我们也掌握了如何利用`Scanner`类进行用户交互,以及如何实现简单的数据统计功能。
通过深入学习和理解HashMap,不仅可以提高代码的编写效率,也能在面试中展现出扎实的基础和深入的思考。这份资料和代码将帮助你更好地掌握HashMap的相关知识点,为面试和实际开发打下坚实的基础。
通过分析和学习易语言HashMap类的源码,开发者可以深入理解哈希表的工作原理,以及易语言如何实现高效的数据结构。这对于提升编程技能,尤其是理解和优化数据结构的性能,具有很大的帮助。同时,源码也可以作为参考...
"Java深入学习就靠他了"这个资源显然旨在帮助有经验的Java开发者深化对这门语言的理解,尤其是那些正处于技术突破阶段的程序员。它涵盖了Java的核心技术和高级特性,旨在提升开发者在J2SE(Java标准版)和J2EE(Java...
在HashMap中,这个映射过程由哈希函数完成,而哈希冲突的解决则采用了链地址法。 哈希函数是关键,它将键的哈希码转换为数组的索引。理论上,好的哈希函数应该能够将键均匀地分布到数组的不同位置,以降低哈希冲突...
在IT行业中,哈希表(HashMap)是一种高效的数据结构,它使用哈希函数将键(Key)映射到数组的特定位置,以便快速存取数据。Delphi是一种强大的Object Pascal编程语言,它提供了多种实现哈希表的方式。在这个特定的...
在本文中,我们将对 HashMap 的 put 方法的源码进行详细解读,分析put 方法源码中的内在逻辑关系,欣赏源码独特之美,从中学习更为精致的编程思维。 首先,让我们看一下 put 方法的源码: ```java public V put(K ...
本文将深入探讨如何使用HashMap来构建一个电话本管理系统,并通过源码分析增强理解。 HashMap是Java集合框架中的一个核心类,它实现了Map接口。Map接口存储键值对(key-value pairs),而HashMap则使用哈希表数据...
在Java编程语言中,`HashMap`和`HashTable`都是实现键值对存储的数据结构,但它们之间存在一些显著的区别,这些区别主要体现在...对于学习和理解,源码阅读是非常有价值的,可以帮助深入理解Java集合框架的设计原理。