Java基础——常用Map的实现细节
2019独角兽企业重金招聘Python工程师标准>>>
Java基础——Map
HashMap
数据结构:
数组 + 单链表
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;
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);}
扩容:
当数据量达到阀值(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);}}}
fail-fast:
HashMap是非线程安全的集合,在进行HashMap遍历(HashIterator)操作时,如果map有修改操作,都会增加modCount的值,通过modCount与expectModCount进行比较,如果两者不相等,立即抛出ConcurrentModificationException。
LinkedHashMap
数据结构:
数组 + 单链表 + 双向链表
LinkedHashMap是在HashMap的基础上添加了一个双向链表结构,来按一定的顺序维护里面的所有entry。
Entry<K,V> before, after;//双向链表结构header = new Entry<K,V>(-1, null, null, null);header.before = header.after = header;
顺序性:
提供两种顺序方式来维护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;}
LRU
LinkedHashMap天然支持LRU操作,即Access-Ordered,默认不开启。
protected boolean removeEldestEntry(Map.Entry eldest) {return false;// 默认是返回false,我们可以实现这个方法来支持LRU}
其他
非线程安全,fast-fail(同hashmap)
ConcurrentHashMap
ConcurrentHashMap采用了锁分离的技术来实现确保线程安全的情况下达到较好的性能。它把整个hash table分成好多个小的hash table(即Segment),每个Segment都有自己的锁来保证线程安全,这样就使得各个Segment都可以独立地进行管理,而不需要争用锁。
两个重要的结构: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操作时可以直接加锁使用。
扩容(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);}
size()操作
size操作会先尝试两次不加锁的情况下计算所有segment的size总数,如果两次计算的结果相等,说明size是正确的,直接返回这个结果。如果不相等,则所有segment加锁做一次计算。
推荐阅读:
Java基础——同步与锁
MySQL使用与优化总结
转载于:https://my.oschina.net/u/142836/blog/169097
Java基础——常用Map的实现细节相关推荐
- Java基础 ----常用时间类
Java基础 ----常用时间类 java.util.Date java.util.Calendar java.text.SimpleDateFormat new Date().getTime() & ...
- 【CXY】JAVA基础 之 Map
概述: 1.键值对,key-value,具有映射关系的数据 2.Map的key不允许重复,value可以重复 3.Map里的key类似一个Set,甚至可以通过map.keySet()拿到key的Set ...
- Java基础之map总结
map的基础理解 话不多说,先上图,可以这样简单的对容器中的map进行分类: 我们在Java开发中,除了最常用的基本数据类型和String对象之外,也会经常用到集合类.集合类中存放的都是对象的引用,而 ...
- Java基础---常用类之Math类 and Syetem类
常用类-Math类 Math类:关于数学的类 这个类中封装了一些关于数学的方法 可以完成一些常用的数学操作 特点:1.final修饰的类 所以Math类没有子类的 不可以继承2.Math类中的构造方法 ...
- Java基础之Map集合
Map集合: 1.Map集合与Collection集合的区别: 1.Collection中的集合,元素是孤立存在的,向集合中存储元素采用一个个元素的方式存储. 2.Map中的集合1,元素是成对存在的, ...
- [Java基础]让Map value自增
需求:我要在map中判断是否存在key,存在则让key对应的value = value+1,否则设置<key,value=1> 代码实现方式如下: ContainsKey import j ...
- Java基础---常用类之Arrays工具类+基本类型的包装类
常用类之Arrays工具类 1.binarySearch(int[] a,int key) 使用二分搜索法来搜索指定的 int 型数组 2.fill(int[] a,int val)将指定的 int ...
- java基础——常用类
包装类 分类 类结构 说明: 实现了Serallizable接口说明此类可以串行化,实现网络传输 实现了Compareable接口说明此类实例可以相互比较大小 包装类和基本数据类型的相互转换 ...
- 【java基础】Map数据的存进和取出的顺序相同
Map数据的存进和取出的顺序相同,需要用到linkedHashMap结构,如下所示: import java.util.HashMap; import java.util.LinkedHashMap; ...
最新文章
- Jupyter官方神器:可视化 Debug 工具!
- CSS文字文本样式(font字体、css外观属性)
- hihoCoder#1196 : 高斯消元·二(开关灯问题)
- html文件girlfriend,index.html
- javaweb登录系统账号密码验证等
- Android Studio开发基础之自定义View组件
- samba 设置文件的读写权限
- Java小白入门200例54之打印水仙花数
- Grads:绘制风流畅
- VUE子路由跳转,各位大神,为啥我这个子路由跳转不到相应的子页面,求助求助
- wordpress页脚添加备案号等版权信息
- Pandorabox(Openwrt) 双宽带(WAN) 叠加网络实战
- 计算车号Java,汽车VIN码校验算法 java版
- Hadoop Ha集群配置
- python大神能干什么_Python这么火,能干什么?这四大主要用途是你必须得知道的!...
- [Win32]鼠标的基本概念以及击中测试
- 分布式系统与网络分区
- 宝塔linux面板命令大全
- 从左上角到右下角 棋盘问题_分治算法之棋盘问题
- PCIE于 总线架构高性能数据预处理板 / K7 325T FMC接口数据采集传输卡