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 ...
随机推荐
- ECharts, PHP, MySQL, Ajax, JQuery 实现前后端数据可视化
ECharts 下载js代码 工作原理浅析 在项目中引入ECharts 后台处理 数据库端MySQL PHP端 JQuery Ajax处理 ECharts 端处理 前端全部代码 演示结果 总结 最近要 ...
- Cocoa层粒子发射器动画添加多个cell的一种重构
在iOS动画之旅第19章中最后的挑战中需要我们在雪花例子发生器中添加多个雪花贴图,也就是多个cell,因为我们不可能将每个cell的参数都重新写一遍,所以有必要写一个helper方法来做这件事: fu ...
- SpriteKit中类似Cocos2D的CCActionSpawn并发方法GroupAction
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 我们知道在Cocos2D中对于并发Action的处理可以使用C ...
- pandas小记:pandas数据输入输出
http://blog.csdn.net/pipisorry/article/details/52208727 数据输入输出 数据pickling pandas数据pickling比保存和读取csv文 ...
- Android Multimedia框架总结(十一)CodeC部分之AwesomePlayer到OMX服务
转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52623882 前言:上篇文< ...
- 带吸附效果的ViewPager(一)
什么叫吸附效果?先看一个示例更为直观,借用网上的一个效果图: 类似这种效果的app很多,网上的实现方法也是很多,但各种重写各种监听又令人不胜其烦,今日突发奇想,顺着自己的思路实现了类似的效果,不敢独享 ...
- Android实现登录小demo
安卓,在小编实习之前的那段岁月里面,小编都没有玩儿过,如果说玩儿过,那就是安卓手机了,咳咳,敲登录的时候有种特别久违的熟悉,这种熟悉的感觉就和当时敲机房收费系统一样,那叫一个艰难啊,不过小编相信,在小 ...
- Android初级教程Activity小案例(计算器乘法运算)
首先设置两个布局文件,一个布局文件进行输入数据,获取加法运算:另一个布局文件进行显示最终结果.Activity1启动Activity2,并传递计算结果值给Activity2. main.xml: &l ...
- Spring+Hibernate4 Junit 报错No Session found for current thread
论坛上有另外一篇更全面的帖子,jinnianshilongnian写的:http://www.iteye.com/topic/1120924 本文的环境是: spring-framework-3.1 ...
- 数据拟合:多项式拟合polynomial curve fitting
http://blog.csdn.net/pipisorry/article/details/49804441 常见的曲线拟合方法 1.使偏差绝对值之和最小 2.使偏差绝对值最大的最小 3 ...