HashMap早知道
第一眼hashmap始终Collection那个地点
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZGxmMTIzMzIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
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写一个hashMap
入坑java很多年了,现在总结一下自己学到的东西. 1.首先我们先来聊聊什么是HashMap? 什么是hash?hash用中文的说法就叫做“散列”,通俗的讲就是把任意长度的字符串输入,经过hash计算 ...
- Java集合之HashMap
1. HashMap概述: HashMap是基于哈希表的Map接口的非同步实现(Hashtable跟HashMap很像,唯一的区别是Hashtalbe中的方法是线程安全的,也就是同步的).此实现提供所 ...
- [转] HashMap的存取之美
本文转自 http://www.nowamagic.net/librarys/veda/detail/1202 HashMap是一种十分常用的数据结构,作为一个应用开发人员,对其原理.实现的加深理解有 ...
- java HashMap那点事
集合类的整体架构 比较重要的集合类图如下: 有序否 允许元素重复否 Collection 否 是 List 是 是 Set AbstractSet 否 否 HashSet TreeSet 是(用二 ...
- [源码解析]HashMap和HashTable的区别(源码分析解读)
前言: 又是一个大好的周末, 可惜今天起来有点晚, 扒开HashMap和HashTable, 看看他们到底有什么区别吧. 先来一段比较拗口的定义: Hashtable 的实例有两个参数影响其性能:初始 ...
- HashMap的key可以是可变的对象吗???
大家都知道,HashMap的是key-value(键值对)组成的,这个key既可以是基本数据类型对象,如Integer,Float,同时也可以是自己编写的对象,那么问题来了,这个作为key的对象是否能 ...
- java中HashMap详解
HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实 ...
- 《转》Java中HashMap详解
HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实 ...
- [Java] HashMap详解
转自:http://alex09.iteye.com/blog/539545 HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 Hash ...
随机推荐
- asp.net网站后台退出后,点后退按钮仍能进,如何安全退出
用户登录成功后,将用户名保存Session Session["usrename"]=username; 退出后Sessssion["username"]=str ...
- OpenCV学习笔记:如何扫描图像、利用查找表和计时
目的 我们将探索以下问题的答案: 如何遍历图像中的每一个像素? OpenCV的矩阵值是如何存储的? 如何测试我们所实现算法的性能? 查找表是什么?为什么要用它? 测试用例 这里我们测试的,是一种简单的 ...
- BZOJ_1016_[JSOI2008]_最小生成树计数_(dfs+乘法原理)
描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1016 给出一张图,其中具有相同权值的边的数目不超过10,求最小生成树的个数. 分析 生成树的计 ...
- NOI 2014 感想
NOI2014结束了,我卡线登上了领奖台... 这是我第一次NOI,我觉得我收获了很多东西: 1.考前心态不重要,重要的是实力 真正考试的时候是顾不得想其他事情的 2.测试数据是人出的!不是随机的!不 ...
- 让ASP.NET MVC页面返回不同类型的内容
在ASP.NET MVC的controller中大部分方法返回的都是ActionResult,更确切的是ViewResult.它返回了一个View,一般情况下是一个HTML页面.但是在某些情况下我们可 ...
- WinForm触摸屏程序功能界面长时间不操作自动关闭回到主界面 z
操作者经常会在执行了某操作后,没有返还主界面就结束了操作然后离开了,程序应该关闭功能窗体自动回到主界面方便下一位操作者操作.那么对于WinForm程序怎么实现呢? 实现原理:拦截Application ...
- Visual Studio 2015 下载地址
Visual Studio 2015 发行说明: https://visualstudio.com/zh-cn/news/vs2015-vs.aspx Visual Studio 2015 特性简 ...
- SR4K的API使用(libMesaSR.dll)
看看libMesaSR.dll的导出函数: 遇到的问题: error C4430: 缺少类型说明符 - 假定为 int.注意: C++ 不支持默认 int 解决: HWND等未定义 解决: #incl ...
- x&-x
x&-x的值是啥米呢?列入10 二进制位 1010为2 1011则为 1,1000为8,就是一个整数对应的二进制数中1所在最低位的权值. 在树状数组中很有用
- 韦东山yy公开课笔记(2)--汇编,段,栈,重定位/链接地址,位置无关吗
1. 要不要学习汇编 可以只懂一点,工作中基本不用,一旦用就是出了大问题 ldr : load 读内存 ldr r0, [r1] : r1里存放的是地址值, 去这个地址读取4字节的内容,存入r0 s ...