2019独角兽企业重金招聘Python工程师标准>>>

Java基础——Map

HashMap

  1. 数据结构:

    数组 + 单链表

     transient Entry[] table; // 数组static class Entry<K,V> implements Map.Entry<K,V> {final K key;V value;Entry<K,V> next;// 单链表,存储hash冲突的对象final int hash;
    
  2. hash桶的计算:

    首先把hash桶的个数适配到2的n次方

     int capacity = 1;while (capacity < initialCapacity)capacity <<= 1;//把容量适配到2^n,方便后面的this.loadFactor = loadFactor;threshold = (int)(capacity * loadFactor);table = new Entry[capacity];
    

    hash桶的查找通过hashcode与(桶个数-1)进行按位与操作,相当于取模,但是效率要高。

     static int indexFor(int h, int length) {return h & (length-1);}
    

    为什么要把hashmap的容量适配到2的n次方呢?因为2^n-1正好各个位都是1,这样在按位与操作时其结果完全取决于hashcode,只要hashcode算法得当,就可以使得hash桶的数据分布比较均匀。如果容量不是2的n次方的话,就会出现0的位,会导致进行与操作后有些桶就一直放不进数据的情况。

    旋转hash:在原有hashcode的基础上再hash一次,充分利用高位进行计算,减少因低位相同的情况导致的hash碰撞。

     static int hash(int h) {h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);}
    
  3. 扩容:

    当数据量达到阀值(capacity * loadFactor)时,为了减少数据量增加带来的hash碰撞,需要对HashMap进行扩容。需要把所有的entry移动一次,代价较大,所以在可以预估容量的时候尽量在初始化时指定容量。

     void transfer(Entry[] newTable) {Entry[] src = table;int newCapacity = newTable.length;for (int j = 0; j < src.length; j++) {Entry<K,V> e = src[j];if (e != null) {src[j] = null;do {Entry<K,V> next = e.next;int i = indexFor(e.hash, newCapacity);e.next = newTable[i];newTable[i] = e;e = next;} while (e != null);}}}
    
  4. fail-fast:

    HashMap是非线程安全的集合,在进行HashMap遍历(HashIterator)操作时,如果map有修改操作,都会增加modCount的值,通过modCount与expectModCount进行比较,如果两者不相等,立即抛出ConcurrentModificationException。

LinkedHashMap

  1. 数据结构:

    数组 + 单链表 + 双向链表

    LinkedHashMap是在HashMap的基础上添加了一个双向链表结构,来按一定的顺序维护里面的所有entry。

     Entry<K,V> before, after;//双向链表结构header = new Entry<K,V>(-1, null, null, null);header.before = header.after = header;
    
  2. 顺序性:

    提供两种顺序方式来维护entry:

    按插入有序:Insertion-Ordered,所有entry按插入的顺序排序,读取的时候总是从最先插入的那个entry开始读取。

    按访问有序:Access-Ordered(所有entry从least-recently到most-recently再到header排列)。entry每次被访问都要调整它的顺序,重新放到header结点前面,这样一直不被访问的entry就离header越来越远。

    这是怎么做到的呢?LinkedHashMap的添加结点操作都是addBefore,而且每次都是在header结点之前进行插入(其实这里面的head结点是个尾结点)离尾结点最远的就是最老的结点(header.after指向),这是个逆向链,所以遍历的时候从header.after开始,就能取到按插入有序的数据。

     private void addBefore(Entry<K,V> existingEntry) {        after  = existingEntry;before = existingEntry.before;before.after = this;after.before = this;}
    

    遍历操作,从header.after开始遍历。

     Entry<K,V> nextEntry    = header.after;Entry<K,V> nextEntry() {if (modCount != expectedModCount)throw new ConcurrentModificationException();if (nextEntry == header)throw new NoSuchElementException();Entry<K,V> e = lastReturned = nextEntry;nextEntry = e.after;return e;}
    
  3. LRU

    LinkedHashMap天然支持LRU操作,即Access-Ordered,默认不开启。

     protected boolean removeEldestEntry(Map.Entry eldest) {return false;// 默认是返回false,我们可以实现这个方法来支持LRU}
    
  4. 其他

    非线程安全,fast-fail(同hashmap)

ConcurrentHashMap

  1. ConcurrentHashMap采用了锁分离的技术来实现确保线程安全的情况下达到较好的性能。它把整个hash table分成好多个小的hash table(即Segment),每个Segment都有自己的锁来保证线程安全,这样就使得各个Segment都可以独立地进行管理,而不需要争用锁。

  2. 两个重要的结构:HashEntry和Segment

    HashEntry中,value被申明为volatile,这样保证了value的可见性,并发访问时不会出现脏数据。next被申请为final,保证了链表的中间和结尾部分都不会改变,进行读操作时就不需要加锁,这样可以提高并发性。

     final K key;final int hash;volatile V value;final HashEntry<K,V> next;// 下面这句是put操作的行为,也就是每次put都是往头节点前面插入新节点,不影响原来的链表结构。tab[index] = new HashEntry<K,V>(key, hash, first, value);
    

    Segment继承了ReentrantLock,天生具有锁的功能,所以在put或remove操作时可以直接加锁使用。

  3. 扩容(rehash)操作

    如果原hash桶的链表里的所有结点rehash值都一样,直接把链表连接到新桶上即可;

    否则就找到链表尾部相同rehash值的子链表,直接连接到新桶上(代码片段一),这样保证rehash到同一个桶的多个节点不会出现链接顺序反转的情况,也避免了像HashMap那样在高并发下rehash出现死循环的现象(http://coolshell.cn/articles/9606.html)。

    最后把当前子链表前面的那部分节点正常计算rehash并添加到新桶的位置(代码片段二)。

     // 代码片段一// Reuse trailing consecutive sequence at same slotHashEntry<K,V> lastRun = e;int lastIdx = idx;for (HashEntry<K,V> last = next;last != null;last = last.next) {int k = last.hash & sizeMask;if (k != lastIdx) {lastIdx = k;lastRun = last;}}newTable[lastIdx] = lastRun;
    

这里解释一下上面这行代码newTable[lastIdx] = lastRun, 刚开始在怀疑直接给新桶赋值的话会不会被后面的entry给覆盖掉?仔细想想,完全没必要担心这个。举个例子,segment(小hash表)容量从32扩充到64的情况,也就是容量从2^5=100000(掩码是:011111)扩充到2^6=1000000(掩码是:[1]11111)这个[]里的1就是扩充后新增的位,可以想象,在原容量下的entry,大部分都不会rehash到新桶里,只有[]指示的位是1的情况才会rehash到新桶里面,所以rehash操作移动链表上一半的元素到新桶里。另外,原容量下不同桶里面的元素,rehash后也不会出现在相同的桶里面,其位置还是取决于非[]指示的位置,跟原容量下的一样。所以上面这个操作可以直接链接过去,不必担心重复被覆盖的情况。

     // 代码片段二// Clone all remaining nodesfor (HashEntry<K,V> p = e; p != lastRun; p = p.next)       {int k = p.hash & sizeMask;HashEntry<K,V> n = newTable[k];newTable[k] = new HashEntry<K,V>(p.key, p.hash, n, p.value);}
  1. size()操作

    size操作会先尝试两次不加锁的情况下计算所有segment的size总数,如果两次计算的结果相等,说明size是正确的,直接返回这个结果。如果不相等,则所有segment加锁做一次计算。

推荐阅读:

Java基础——同步与锁

MySQL使用与优化总结

转载于:https://my.oschina.net/u/142836/blog/169097

Java基础——常用Map的实现细节相关推荐

  1. Java基础 ----常用时间类

    Java基础 ----常用时间类 java.util.Date java.util.Calendar java.text.SimpleDateFormat new Date().getTime() & ...

  2. 【CXY】JAVA基础 之 Map

    概述: 1.键值对,key-value,具有映射关系的数据 2.Map的key不允许重复,value可以重复 3.Map里的key类似一个Set,甚至可以通过map.keySet()拿到key的Set ...

  3. Java基础之map总结

    map的基础理解 话不多说,先上图,可以这样简单的对容器中的map进行分类: 我们在Java开发中,除了最常用的基本数据类型和String对象之外,也会经常用到集合类.集合类中存放的都是对象的引用,而 ...

  4. Java基础---常用类之Math类 and Syetem类

    常用类-Math类 Math类:关于数学的类 这个类中封装了一些关于数学的方法 可以完成一些常用的数学操作 特点:1.final修饰的类 所以Math类没有子类的 不可以继承2.Math类中的构造方法 ...

  5. Java基础之Map集合

    Map集合: 1.Map集合与Collection集合的区别: 1.Collection中的集合,元素是孤立存在的,向集合中存储元素采用一个个元素的方式存储. 2.Map中的集合1,元素是成对存在的, ...

  6. [Java基础]让Map value自增

    需求:我要在map中判断是否存在key,存在则让key对应的value = value+1,否则设置<key,value=1> 代码实现方式如下: ContainsKey import j ...

  7. Java基础---常用类之Arrays工具类+基本类型的包装类

    常用类之Arrays工具类 1.binarySearch(int[] a,int key) 使用二分搜索法来搜索指定的 int 型数组 2.fill(int[] a,int val)将指定的 int ...

  8. java基础——常用类

    包装类 分类 类结构 说明: ​ 实现了Serallizable接口说明此类可以串行化,实现网络传输 ​ 实现了Compareable接口说明此类实例可以相互比较大小 包装类和基本数据类型的相互转换 ...

  9. 【java基础】Map数据的存进和取出的顺序相同

    Map数据的存进和取出的顺序相同,需要用到linkedHashMap结构,如下所示: import java.util.HashMap; import java.util.LinkedHashMap; ...

最新文章

  1. Jupyter官方神器:可视化 Debug 工具!
  2. CSS文字文本样式(font字体、css外观属性)
  3. hihoCoder#1196 : 高斯消元·二(开关灯问题)
  4. html文件girlfriend,index.html
  5. javaweb登录系统账号密码验证等
  6. Android Studio开发基础之自定义View组件
  7. samba 设置文件的读写权限
  8. Java小白入门200例54之打印水仙花数
  9. Grads:绘制风流畅
  10. VUE子路由跳转,各位大神,为啥我这个子路由跳转不到相应的子页面,求助求助
  11. wordpress页脚添加备案号等版权信息
  12. Pandorabox(Openwrt) 双宽带(WAN) 叠加网络实战
  13. 计算车号Java,汽车VIN码校验算法 java版
  14. Hadoop Ha集群配置
  15. python大神能干什么_Python这么火,能干什么?这四大主要用途是你必须得知道的!...
  16. [Win32]鼠标的基本概念以及击中测试
  17. 分布式系统与网络分区
  18. 宝塔linux面板命令大全
  19. 从左上角到右下角 棋盘问题_分治算法之棋盘问题
  20. PCIE于 总线架构高性能数据预处理板 / K7 325T FMC接口数据采集传输卡

热门文章

  1. loadlibrary 失败_职称评审失败的原因有哪些?
  2. c语言50行以内有趣的代码,分享一段有趣的小代码
  3. 无密码进去mysql_技术分享 | 安全地无密码登录 MySQL
  4. 天问电子少年团DIY作品
  5. 2021年人工神经网络第三次作业-第二题:遗传算法与人工神经网络-参考答案
  6. 开源的关于智能车竞赛光电起始点方案
  7. 工欲善其事,必先利其器 -- 这烙铁,升温有点狠
  8. 逐飞关于第15届智能车竞赛相关工作
  9. 卡尔曼滤波器中的Q,R
  10. 为什么python打开pygame秒关闭后在运行_当我关闭Pygame时屏幕冻结