HashMap二三事
先看看hashmap在整个Collection中的位置
HashMap中存储数据的结构是
/** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry<K,V>[] table;
上面的英文就不用说了。
原来基础的存储结构式Entry的数组!
至于Entry是HashMap的一个内部类
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } ..... }
看到里面的这个参数Entry<K,V> next大家应该都明白了,HashMap中每个Entry键值对都是一个链表!!!
下面我们看看map的put,get,iterator方法及遍历
put方法
public V put(K key, V value) { if (key == null) return putForNullKey(value); //计算key的hash 里面的实现比较麻烦 可以不用理会 int hash = hash(key); //由hash码得到存储位置 计算方法是hash与table.length-1相与 这样的好处就是能保证要存放的位置肯定不会超过table的范围 //前面的hash方法与indexFor 我没有仔细研究 不过大家可以认为 两个不同的hash会对应不同的存储位置 int i = indexFor(hash, table.length); //e.next 链表 //如果i的位置上已经有元素了 继续看for循环 //否则就new一个新的Entry for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //如果要存储的key的hash值与已经存在在那个位置元素的key的hash值相等 并且两个key的内容也相等 //话说这里我看的不是太懂 e本身是一个新的对象 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; //以下两行代码到底想干什么? e.recordAccess(this); //把要加入的值 给了e 是什么意思? return oldValue; //返回的是之前已经存在的那个键值对里的value } } modCount++; addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { //如果相应的位置已经有东西了 并且总的容量也到了 就扩容 //size threshold 这里面有点概念性的知识 大家看看源码就知道了 if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } void createEntry(int hash, K key, V value, int bucketIndex) { //之前的那个位置的数据(不管有没有)转放到e里面 //现在有个问题 什么时候table[bucketIndex]里面才有数据呢? Entry<K,V> e = table[bucketIndex]; //看构造函数就知道 把e作为新entry的next 拉链法!!! table[bucketIndex] = new Entry<>(hash, key, value, e); size++; } static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } ..... }
什么时候table[bucketIndex]里面才有数据呢? 换句话说拉链法是怎么实现的呢?我们先看下面的遍历。
hashmap的遍历
package iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Iterator; import java.util.HashMap; import java.util.Collection; /* * @desc 遍历HashMap的测试程序。 * (01) 通过entrySet()去遍历key、value,参考实现函数: * iteratorHashMapByEntryset() * (02) 通过keySet()去遍历key、value,参考实现函数: * iteratorHashMapByKeyset() * (03) 通过values()去遍历value,参考实现函数: * iteratorHashMapJustValues() * * @author skywang */ public class HashMapIteratorTest { public static void main(String[] args) { int val = 0; String key = null; Integer value = null; Random r = new Random(); HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("ss",12); //第一次 会调用addEntry(hash, key, value, i); map.put("ss",13); //第二次 会进入put方法里的for循环 然后直接return map.put("ss",12); //第三次 会进入put方法里的for循环 然后直接return 那么什么时候才会使用拉链法呢 //当若干个key的内容不同但是hashCode相同时 for (int i=0; i<12; i++) { // 随机获取一个[0,100)之间的数字 val = r.nextInt(10); key = String.valueOf(val); value = r.nextInt(5); // 添加到HashMap中 map.put(key, value); } // 通过entrySet()遍历HashMap的key-value iteratorHashMapByEntryset(map) ; // 通过keySet()遍历HashMap的key-value iteratorHashMapByKeyset(map) ; // 单单遍历HashMap的value iteratorHashMapJustValues(map); } /* * 通过entry set遍历HashMap * 效率高! */ @SuppressWarnings("unchecked") private static void iteratorHashMapByEntryset(HashMap<String, Integer> map) { if (map == null) return ; System.out.println("\niterator HashMap By entryset"); String key = null; Integer integ = null; Iterator<?> iter = map.entrySet().iterator(); while(iter.hasNext()) { Map.Entry<String, Integer> entry = (Entry<String, Integer>)iter.next(); key = (String)entry.getKey(); integ = (Integer)entry.getValue(); System.out.println(key+" -- "+integ.intValue()); } } /* * 通过keyset来遍历HashMap * 效率低! */ private static void iteratorHashMapByKeyset(HashMap<String, Integer> map) { if (map == null) return ; System.out.println("\niterator HashMap By keyset"); String key = null; Integer integ = null; Iterator<String> iter = map.keySet().iterator(); while (iter.hasNext()) { key = iter.next(); integ = map.get(key); System.out.println(key+" -- "+integ.intValue()); } } /* * 遍历HashMap的values */ private static void iteratorHashMapJustValues(HashMap<String, Integer> map) { if (map == null) return ; Collection<Integer> c = map.values(); Iterator<Integer> iter= c.iterator(); while (iter.hasNext()) { System.out.println(iter.next()); } } }
拉链法
上面已经说了使用entrySet的方式效率高,大家以后就采用这个吧,另外还提到了什么时候用拉链法看下面这个例子
HashMap<Person, Integer> map = new HashMap<Person, Integer>(); map.put(new Person("dlf", 14),12); map.put(new Person("dlf", 15),12); map.put(new Person("sdfe", 16),12);
对于person这个类,我重写了hashCode方法,但是没有重写equals方法;
person的hashCode方法:
public int hashCode() { int h = 0; if (name.equals("dlf")) { return 123456789; } if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
大家再看看hashMap里面的put方法
for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //如果要存储的key的hash值与已经存在在那个位置元素的key的hash值相等 并且两个key的内容也相等 //话说这里我看的不是太懂 e本身是一个新的对象 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; //以下两行代码到底想干什么? e.recordAccess(this); //把要加入的值 给了e 是什么意思? return oldValue; //返回的是之前已经存在的那个键值对里的value } }
当我
map.put(new Person("dlf", 14),12); map.put(new Person("dlf", 15),12);
第二个person的hashcode与第一个person的hashcode是一样的,但是看看上面if条件句的第二部分
(k = e.key) == key || key.equals(k)
此时调用Object的equals方法也就是==,那么==比较的是什么呢?栈里面存储的地址值,两个新new处理的对象地址那自然不同的,所以if条件不满足,跳出for循环;
最后的结果是
iterator HashMap By entryset
dlf 15 -- 12
dlf 14 -- 12
sdfe 16 -- 12
iterator HashMap By keyset
dlf 15 -- 12
dlf 14 -- 12
sdfe 16 -- 12
12
12
12
get方法
public V get(Object key) { if (key == null) return getForNullKey(); // 获取key的hash值 int hash = hash(key.hashCode()); // 在“该hash值对应的链表”上查找“键值等于key”的元素 大家看到了 是在hash对应的位置查找,而不是查找整个table 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; }
iterator方法
我们就看效率最高的entrySet方法
Set<Entry<Person, Integer>> set=map.entrySet(); //为了更清楚些 我分开写 Iterator<?> iter = set.iterator();
最开始调用entrySet方法的时候,entrySet对象还为null,会调用new EntrySet;
待得到set集合后,会再次调用iterator方法
public Set<Map.Entry<K,V>> entrySet() { return entrySet0(); } private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } ....... } Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }
大家看到了最后返回的Iterator是一个EntryIterator。
看EntryIterator的代码,它是继承了HashIterator;
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); //这个方法在HashIterator中定义 } }
随后我们在程序里调用
Map.Entry<Person, Integer> entry = (Entry<Person, Integer>)iter.next();
//以下为HashIterator类 //构造函数 //在我们new EntryIterator的时候 就已经调用这个其父类HashIterator的构造函数了 //HashIterator的成员变量在构造函数里面 就已经指到table里第一个元素了 HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } } final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; //这个next是构造函数里面就指到table里第一个元素了(第一个不为null的元素) if (e == null) throw new NoSuchElementException(); // 先让next=e.next 然后才判断next是否为空 if ((next = e.next) == null) { //从第一行调用来看 //如果table的第一个Entry(其实就是一个单链表) 就只有一个元素(其next为空) //让next找到table的下一个元素 Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; }
有了上面的if ((next = e.next) == null) 这一行,我们就不仅能遍历整个table,还能将table中某个entry中的所有元素也遍历了!不重复,不遗漏。
参考资料
http://www.cnblogs.com/skywang12345/p/3310835.html
我多说两句,上面的博客把java的整个Collection的源代码剖析了一遍,博客主人真乃牛人呀! 大家一定要去看看
HashMap二三事的更多相关文章
- Java并发编程二三事
Java并发编程二三事 转自我的Github 近日重新翻了一下<Java Concurrency in Practice>故以此文记之. 我觉得Java的并发可以从下面三个点去理解: * ...
- linux杂记(十二?) 关于账号和密码的二三事
关于密码的二三事 关于账号和密码的二三事 久了不更linux的相关知识,实在是懒得想内容点(纯粹是懒).那么今天就来谈谈关于linux密码和账号的重要概念. 假如你的主机遭到入侵,那么对方的第一个侵入 ...
- MySQL5.7关于密码二三事
MySQL5.7关于密码二三事 第一个:update user set password=password('root') where user='root' and host='localhost' ...
- Java中的匿名内部类及内部类的二三事
匿名内部类适合创建那些只需要使用一次的类,它的语法有些奇怪,创建匿名内部类会立即创建一个该类的实例,这个类定义立即消失,且不能重复使用. 定义匿名类的格式如下: new 实现接口() |父类构造器(实 ...
- Emacs 启动优化二三事
Emacs 启动优化二三事 */--> div.org-src-container { font-size: 85%; font-family: monospace; } p {font-siz ...
- WinForm二三事(三)Control.Invoke&Control.BeginInvoke
http://www.cnblogs.com/yuyijq/archive/2010/01/11/1643802.html 这个系列从2009年写到2010年,差点又成太监文.随着WPF/Silver ...
- iOS7下滑动返回与ScrollView共存二三事
[转载请注明出处] = =不是整篇复制就算注明出处了亲... iOS7下滑动返回与ScrollView共存二三事 [前情回顾] 去年的时候,写了这篇帖子iOS7滑动返回.文中提到,对于多页面结构的应用 ...
- 一只代码小白git托管路上的二三事
[经验]一只代码小白git托管路上的二三事 写在前面的话 寒假的时候,娄老师给我们布置了代码托管的作业,并要求把托管地址发给学委.因假期的时候没有带电脑回家,所以只是在手机上草草注册了,也稀里糊涂就将 ...
- java:Map借口及其子类HashMap二
java:Map借口及其子类HashMap二 重点:所有的集合必须依赖Iterator输出 Map<String, Integer> map = new HashMap<String ...
随机推荐
- 悲观的并发策略——Synchronized互斥锁
volatile既然不足以保证数据同步,那么就必须要引入锁来确保.互斥锁是最常见的同步手段,在并发过程中,当多条线程对同一个共享数据竞争时,它保证共享数据同一时刻只能被一条线程使用,其他线程只有等到锁 ...
- 财务模块多组织,GL, SLA, SOB, COA, BSV, CCID, LE 概念的简单介绍
GL= General Ledger 指的是Oracle 的总帐系统. application_id = 101. 在uk似乎居然还有不同的解释(In the UK, it was refer ...
- java中hashCode()与equals()详解
首先之所以会将hashCode()与equals()放到一起是因为它们具备一个相同的作用:用来比较某个东西.其中hashCode()主要是用在hash表中提高 查找效率,而equals()则相对而言使 ...
- RMI方式Ehcache集群的源码分析
Ehcache不仅支持基本的内存缓存,还支持多种方式将本地内存中的缓存同步到其他使用Ehcache的服务器中,形成集群.如下图所示: Ehcache支持多种集群方式,下面以RMI通信方式为例,来具体分 ...
- java虚拟机工具入门
jps 能显示现在都有那些java程序运行 C:\Users\Administrator>jps 16964 DeadLockJstack 9172 PULSEI~1.JAR 19392 Jps ...
- 【一天一道LeetCode】#141. Linked List Cycle
一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...
- Dynamics CRM2013 附件禁用方案
CRM2013的附件功能和以往有了不同,把公告.活动.注释合在了一块并称注释,在使用的过程中会发现一个无语的地方,就算表单状态为停用,注释还是处于可编辑状态,而且也查询不到公开的方法来处理注释的,为了 ...
- iOS中 WGAFN_网络监控 技术分享
需要用到第三方AFNetworking/SVProgressHUD 没有的可以关注我微博私信我.http://weibo.com/hanjunqiang AppDelegate.m #import & ...
- Ubuntu14.04安装配置星际译王词典
参考自:http://m.blog.csdn.net/blog/u014731529/25917149 平常总会遇到一些不认识的单词,汉字等等.一直使用Chrome 浏览器的翻译插件,不过插件的翻译总 ...
- EBS条形码打印
Oracle 提供两种方式实现 128 码的编码 第一种方式是使用 Reports Builder 实现对 128 码编码, 在 Metalink 305090.1[1] 有 比较详尽的描述,其中 ...