LinkedHashMap剖析

来源:互联网 发布:华林证券软件 编辑:程序博客网 时间:2024/06/05 15:40

LinkedHashMap剖析

简介

LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

HashMap与LinkedHashMap

LinkedHashMap 实现与 HashMap 的不同之处在于,LinkedHashMap 维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。除此之外,基本操作和HashMap基本一样,所以重点分析双链表相关的内容。

LinkedHashMap 采用的 hash 算法和 HashMap 相同,但是它重新定义了数组中保存的元素 Entry,该 Entry 除了保存当前对象的引用外,还保存了其上一个元素 before 和下一个元素 after 的引用,从而在哈希表的基础上又构成了双向链接列表。代码如下

static class Entry<K,V> extends HashMap.Node<K,V> {    Entry<K,V> before, after;    Entry(int hash, K key, V value, Node<K,V> next) {        super(hash, key, value, next);    }}transient LinkedHashMap.Entry<K,V> head;transient LinkedHashMap.Entry<K,V> tail;

初始化:

在 LinkedHashMap 的构造方法中,实际调用了父类 HashMap 的相关构造方法来构造一个底层存放的 table 数组,但额外可以增加 accessOrder 这个参数,如果不设置,默认为 false,代表按照插入顺序进行迭代;当然可以显式设置为 true,代表以访问顺序进行迭代。

插入:

LinkedHashMap 并未重写父类 HashMap 的 put 方法,而是重写了父类 HashMap 的 put 方法调用的子方法void afterNodeAccess(HashMap m) 和void afterNodeInsertion(boolean evict),removeNode等来提供了自己特有的双向链接列表的实现。

void afterNodeInsertion(boolean evict) { // possibly remove eldest    LinkedHashMap.Entry<K,V> first;    if (evict && (first = head) != null && removeEldestEntry(first)) {        K key = first.key;        removeNode(hash(key), key, null, false, true);    }}

读取:

LinkedHashMap 重写了父类 HashMap 的 get 方法,实际在调用父类 getEntry() 方法取得查找的元素后,再判断当排序模式 accessOrder 为 true 时,记录访问顺序,将最新访问的元素添加到双向链表的表尾。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失。

public V get(Object key) {    Node<K,V> e;    if ((e = getNode(hash(key), key)) == null)  //调用父类的方法获得值        return null;    if (accessOrder)  //记录访问顺序        afterNodeAccess(e);    return e.value;}   void afterNodeAccess(Node<K,V> e) { // move node to last    LinkedHashMap.Entry<K,V> last;    if (accessOrder && (last = tail) != e) {        LinkedHashMap.Entry<K,V> p =            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;        p.after = null;        if (b == null)            head = a;        else            b.after = a;        if (a != null)            a.before = b;        else            last = b;        if (last == null)            head = p;        else {            p.before = last;            last.after = p;        }        tail = p;        ++modCount;    }}

排序模式

LinkedHashMap 定义了排序模式 accessOrder,该属性为 boolean 型变量,对于访问顺序,为 true;对于插入顺序,则为 false。一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。

这些构造方法都会默认指定排序模式为插入顺序。如果想构造一个排序模式的LinkedHashMap,那么必须在构造方法中将accessOrder申明为true。

该哈希映射的迭代顺序就是最后访问其条目的顺序,这种映射很适合构建 LRU 缓存。LinkedHashMap 提供了 removeEldestEntry(Map.Entry

下面来看看HashMap:

HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

从结构实现来讲,HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。

HashMap就是使用哈希表来存储的。哈希表为解决冲突,可以采用开放地址法和链地址法等来解决问题,Java中HashMap采用了链地址法。链地址法,简单来说,就是数组加链表的结合。在每个数组元素上都一个链表结构,当数据被Hash后,得到数组下标,把数据放在对应下标元素的链表上。

在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。

在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数,默认为16),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数。

扩容

Node[] table的初始化长度length(默认值是16),Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳的最大数据量的Node(键值对)个数。threshold = length * Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。
结合负载因子的定义公式可知,threshold就是在此Load factor和length(数组长度)对应下允许的最大元素数目,超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。默认的负载因子0.75是对空间和时间效率的一个平衡选择,建议大家不要修改,除非在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。

java8相比java7中扩容还是很多的优化,java7中的扩容要重新进行hash运算,然后采用头插法,所以会导致数据颠倒。而java8中,由于每次扩容都是变为原容量的2倍,所以链表上为1(奇数位)的那位加上原容量及是现有的位置,所以不用再进行hash运算。

Hash

static final int hash(Object key) {    int h;    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}

这里的hash主要分为三个部分:取key的hashCode值、高位运算、取模运算

hash后需要对数组长度进行取模,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。

线程安全性

HashMap是非线程安全的,在并发的多线程使用场景中使用HashMap可能造成死循环。

关于hashMap具体参考 java8之重新认识HashMap

0 0
原创粉丝点击