HashMap的底层的一些变量:

      transient Node<K,V>[] table;        //存储数据的Node数组

      transient Set<java.util.Map.Entry<K,V>> entrySet;

      transient int size;          //map中存放数据的个数,不等于table.length

      transient int modCount;         //修改的次数,防止

      int threshold;            //临界值

      final float loadFactor;        //扩展因子,一般情况下threshold=table.length*loadFactor;

构造一个空的HashMap时,只有loadFactor被赋值为默认的0.75。代码如下:

       public HashMapMmc(){
this.loadFactor=DEFAULT_LOAD_FACTOR;
}

这里我将介绍三个方法,put  get  remove,最后介绍entrySet()遍历。

  1. put()方法:

在调用put(key,value)方法时,底层调用的是这个方法:

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n,i;
if((tab=table)==null||(n=tab.length)==0)
n=(tab=resize()).length;
if((p=tab[i=(n-1)&hash])==null)
tab[i]=newNode(hash,key,value,null);
else{
Node<K,V> e;K k;
if(p.hash==hash&&((k=p.key)==key||(k!=null&&k.equals(key))))
e=p;
else if(p instanceof TreeNode)
e=((TreeNode<K,V>)p).putTreeVal(this,tab,hash,key,value);
else{
for(int binCount=0;;++binCount){
if((e=p.next)==null){
p.next=newNode(hash,key,value,null);
if(binCount>=TREEIFY_THRESHOLD-1)
treeifyBin(tab,hash);
break;
}
if(e.hash==hash&&((k=e.key)==key||(key!=null&&key.equals(k))))
break;
p=e;
}
}
if(e!=null){ // existing mapping for key
V oldValue=e.value;
if(!onlyIfAbsent||oldValue==null)
e.value=value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if(++size>threshold)
resize();
afterNodeInsertion(evict);
return null;
}

这个方法有5个参数,第一个为hash,可以理解为对key经过运算之后的一个值(具体算法:(key==null)?0:(h = key.hashCode())^(h>>>16)),第二个为key,第三个为value,这些都不用说了吧,第四个为onlyIfAbsent,这里代表的是是否覆盖,如果为false,同样的key放在map中,后面放入的值会覆盖原来的值,put方法在调用这个putVal()方法时,onlyIfAbsent写死为false的,所以HashMap中,是没有重复的key值的,后来的value会覆盖原来的value。看下面方法第四个参数:

 public V put(K key,V value ){
return putVal(hash(key),key,value,false,true);
}

然后说放入过程:

  1. 先检查table够不够存放数据。刚刚new出来的HashMap,table是为空的。在放入时会先进行扩容,按照默认的大小16.

     Node<K,V>[] newTab=(Node<K,V>[])new Node[newCap];
  2. 计算要放入的位置,HashMap是没有顺序的,默认的16个索引位置中,会随机的找一个放入。(注意:key是可以等于null的,key等于null时,计算出来的索引是0)计算索引的方法是:
    (n-1)&hash                //n代表的是table的length,hash就是上面的第一个参数hash(key);
  3. 所谓的碰撞问题解析:正常情况下直接放入就行了,但是如果加入的元素和之前的元素计算出来的索引位置是一样的。例如:新建一个HashMap,放入(1,"a")和(17,"b")时,他们计算出来的索引相同,这时第一个Node放入好之后,第二个Node不会在重新在table中占一个索引了,会在同一个索引的Node上形成链表。即Node1.next=Node2.    Node1和Node2都在table数组里同一个索引里面。如果在放入一个(33,"c"),这个其实也是和上面两个计算出来是同一个索引位置,会放在Node2.next=Node3.
  4.   p.next=newNode(hash,key,value,null);                  //newNode方法会新声明一个Node

2.  get(Object key)方法:

知道了put方法,get(Object key)方法就比较简单了,直接通过key算出他在table数组中的索引位置直接获取就行了,因为有可能同一个索引位置放了几个元素,所以他会先找到第一个元素,然后对比hash和key是否都相等。比如,在一个初始的table中,放入(33,"a"),(17,"b")。他们的hash分别为33和17,key也分别为33和17。当我调用get(17)时,先会根据17算出在table中的索引为1,然后取出在这个索引中的第一个元素(33,"aa"),让对比他们的hash和key是否都相等。显而易见,第一个元素的key和hash都是33,而我们想要get的hash和key都是17.所以不相等。那么他就会去获取第一个元素的next是否存在,如果存在会获取出来在判断hash和key是否都相等。

3. remove(Object key)方法:

和get(Object key)方法类似,先计算索引位置,找出这个索引位置的第一个Node命名为p,在对比 p的key,hash和参数中的key,根据参数key计算出来的hash是否一样,如果一样那么就在这个索引位置的值设为null。如果在有碰撞的情况下,就会与p.next做对比,如果一样那么p.next将指向这个p.next.next。然后这个元素没有了指针也会就被jvm回收了。

4.entrySet()方法:

我遍历了一个HashMap看了看,因为想看看他是怎么把碰撞的同一个索引位置的那么多数取出了的,发现这个代码不是很好理解,经过百度和自己猜测,有了一点了解。当时情况是这样的:

这个在代码中是这样的:调用entrySet方法来遍历出一个个Map.Entry

  for(Map.Entry<? extends K,? extends V> e:m.entrySet()){
K key=e.getKey();
V value=e.getValue();
}
entrySet()方法的代码如下:
 public Set<Map.Entry<K, V>> entrySet(){
Set<Map.Entry<K, V>> es;
return (es=entrySet)==null?(es=new EntrySet()):es;
}

这个entrySet是等于null的,也就是说每次都是new EntrySet();EntrySet类的代码如下:

   final class EntrySet extends AbstractSet<Map.Entry<K, V>>{
public final int size(){return size;}
public final void clear(){HashMapMmc.this.clear();}
public final Iterator<Map.Entry<K, V>> iterator(){
return new EntryIterator();
} public final boolean contains(Object o){
if(!(o instanceof Map.Entry))
return false;
Map.Entry<?, ?> e=(Map.Entry<?, ?>) o;
Object key=e.getKey();
Node<K,V> candidate=getNode(hash(key),key);
return candidate!=null&&candidate.equals(o);
} public final boolean remove(Object o){
if(o instanceof Map.Entry){
Map.Entry<?, ?> e=(java.util.Map.Entry<?, ?>) o;
Object key= e.getKey();
Object value=e.getValue();
return removeNode(hash(key), key, value, true,true)!=null;
}
return false;
} public final Spliterator<Map.Entry<K, V>> spliterator(){
return new EntrySpliterator<>(HashMapMmc.this,0,-1,0,0);
} public final void forEach(Consumer<? super Map.Entry<K, V>> action){
Node<K,V> [] tab;
if(action==null)
throw new NullPointerException();
if(size>0&&(tab=table)!=null){
int mc=modCount;
for(int i=0;i<tab.length;++i){
for(Node<K,V> e=tab[i];e!=null;e=e.next)
action.accept(e);
}
if(modCount!=mc)
throw new ConcurrentModificationException();
}
}
}

看了EntrySet之后,感觉new EntrySet()里面不应该是空的吗?怎么能够遍历出值来呢?

但是debug了下下面的这个e确实是有值的。最后查找了一下资料得出,增强性for循环内部是使用的iterator方法,又看了看果然EntrySet类中覆写了iterator方法。返回的是一个new EntryIterator(),我又去找EntryIterator类,类里就只有一个方法。然后又发现它继承了HashIterator类,
这个类东西就多了。看下面的代码:
for(Map.Entry<? extends K,? extends V> e:m.entrySet()){}
 abstract class HashIterator{
Node<K,V> next;
Node<K,V> current;
int expectedModeCount;
int index; HashIterator(){
expectedModeCount=modCount;
Node<K,V>[] t=table;
current=next=null;
index=0;
if(t!=null&&size>0){ //先入先进
do{}while(index<t.length&&(next=t[index++])==null);
}
} public final boolean hasNext(){
return next!=null;
} final Node<K,V> nextNode(){
Node<K,V>[] t;
Node<K,V> e= next;
if(modCount!=expectedModeCount)
throw new ConcurrentModificationException();
if(e==null)
throw new NoSuchElementException();
if((next=(current=e).next)==null&&(t=table)!=null){
do{}while(index<t.length&&(next=t[index++])==null);
}
return e;
} public final void remove(){
Node<K,V> p=current;
if(p==null)
throw new IllegalStateException();
if(modCount!=expectedModeCount)
throw new ConcurrentModificationException();
current=null;
K key=p.key;
removeNode(hash(key),key,null,false,false);
expectedModeCount=modCount;
}
}

可以看出这个HashIterator迭代器的默认构造器中,会初始化一个next的变量,这个变量是在table数组中取得,索引是从0递增的,即先入先出原则。构造初期会从0开始找有值的索引位置,找到后将这个Node赋值给next;然后要遍历的时候是调用nextNode()方法,这个方法是先判断next.next是否为空,如果为空继续往上找有值的索引位置,如果不为空就找next.next。这样就能都遍历出来了,是从索引0到table.length去一个个寻找遍历的。

第一次写自己的理解,希望多多指正!



        

浅谈HashMap原理,记录entrySet中的一些疑问的更多相关文章

  1. 转:浅谈CSS在前端优化中一些值得注意的关键点

    前端优化工作中要考虑的元素多种多样,而合理地使用CSS脚本可以在很大程度上优化页面的加载性能,以下我们就来浅谈CSS在前端优化中一些值得注意的关键点: 当谈到Web的“高性能”时,很多人想到的是页面加 ...

  2. 【WebApi系列】浅谈HTTP在WebApi开发中的运用

    WebApi系列文章 [01]浅谈HTTP在WebApi开发中的运用 [02]聊聊WebApi体系结构 [03]详解WebApi参数的传递 [04]详解WebApi测试和PostMan [05]浅谈W ...

  3. 浅谈Python在信息学竞赛中的运用及Python的基本用法

    浅谈Python在信息学竞赛中的运用及Python的基本用法 前言 众所周知,Python是一种非常实用的语言.但是由于其运算时的低效和解释型编译,在信息学竞赛中并不用于完成算法程序.但正如LRJ在& ...

  4. 浅谈xss原理

    近日,论坛上面XSS满天飞,各处都能够见到XSS的痕迹,前段时间论坛上面也出现了XSS的迹象.然后我等小菜不是太懂啊,怎么办?没办法仅仅有求助度娘跟谷歌这对情侣了. 能够说小菜也算懂了一些.不敢藏私, ...

  5. 浅谈箭头函数和setTimeout中的this

    箭头函数会改变this的指向,这个大家看文档都看到过,可是有没有具体理解呢?我发现自己应该可能大概是......emmmm,然后我整理了一遍,加强一下概念吧顺带再讲一下setTimeout这个函数改写 ...

  6. Java重点之小白解析--浅谈HashMap与HashTable

    这是一个面试经常遇到的知识点,无论什么公司这个知识点几乎是考小白必备,为什么呢?因为这玩意儿太特么常见了,常见到你写一百行代码,都能用到好几次,不问这个问哪个.so!本小白网罗天下HashMap与Ha ...

  7. 【JDK源码分析】浅谈HashMap的原理

    这篇文章给出了这样的一道面试题: 在 HashMap 中存放的一系列键值对,其中键为某个我们自定义的类型.放入 HashMap 后,我们在外部把某一个 key 的属性进行更改,然后我们再用这个 key ...

  8. 浅谈HashMap的实现原理

    1.    HashMap概述: HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变 ...

  9. 浅谈HashMap 的底层原理

    本文整理自漫画:什么是HashMap? -小灰的文章 .已获得作者授权. HashMap 是一个用于存储Key-Value 键值对的集合,每一个键值对也叫做Entry.这些个Entry 分散存储在一个 ...

随机推荐

  1. 虚拟机Mac系统中VMware_tools安装和vm共享文件夹的设置(转)

    原文来源: http://wenku.baidu.com/link?url=KRgfG40q2SEwZfde9xA7HVKjCsFBkMcf83tyellnzsHYZ_ErU1hWpVmTHYZem0 ...

  2. 在cmd中显示mysql -uroot-proot 不是命令

    这个代码的意思是打开mysql,用户名为root,密码也是root 解决办法:方法一:首先要进入mysql的bin目录下,再执行. 密码错了,重新输入密码,没有密码嘛

  3. 44. Wildcard Matching (String; DP, Back-Track)

    Implement wildcard pattern matching with support for '?' and '*'. '?' Matches any single character. ...

  4. ibatis 常用标签

    prepend:自动在前面加上:自动新手:自动预:自动前置 property:属性 compareValue:指定的常数,值 //判断不相等: <isNotEqual prepend=" ...

  5. PKUWC2019 真·游记

    由于我这个大傻逼考试结果实在是没法看,所以这篇游记将尽可能略去和考试有关的内容,变成一篇真正的游记…… 接下来的内容中将会出现各种旅游攻略,寻求干货的同学可以提前左转了. Day -7 学考终于结束了 ...

  6. W-D-S-链接地址

    1.程序一开始是烧写到nandflash上,设置为nandflash启动,6410片内有8K的内存,设为nandflash启动时,是从片内内存0地址开始,一上电,nandflash前面8K的内容会原原 ...

  7. Java程序设计9——泛型

    泛型是对集合的补充,JDK1.5增加泛型支持很大程度上都是为了让集合能记住其元素的数据类型.在没有泛型之前,一旦把一个对象丢进Java集合中,集合就会忘记对象的类型,把所有的对象都当成Object类型 ...

  8. TableLayout 里的TextView等组的LayoutParams参数问题

    TableLayout 里的TextView等组的LayoutParams参数不能是LinearLayout.LayoutParams这样来定义, 只能是用TableRow.LayoutParams ...

  9. intrinsicConditionQueue笔记

    一, 使用conditionQueue需要注意的一些点: 一个conditionQueue被多种Predicate condition 使用是很正常的,所以当一个wait的线程被唤醒的时候,很有可能它 ...

  10. B-spline Curves 学习之B样条曲线定义(4)

    B-spline Curves: Definition 本博客转自前人的博客的翻译版本,前几章节是原来博主的翻译内容,但是后续章节博主不在提供翻译,后续章节我在完成相关的翻译学习. (原来博客网址:h ...