双向链表与LinkedHashMap

Posted bitcarmanlee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了双向链表与LinkedHashMap相关的知识,希望对你有一定的参考价值。

1.双向链表比单向链表优势

对于插入操作,单链表与双链表都是O(1)时间复杂度。
对于删除操作,我们经常看到的说法是单链表的删除时间复杂度是O(1),其实这种说法不准确。因为单从删除这个动作来说,确实是O(1),只要把next指针的指向做改变即可。但是,在删除操作之前,我们需要找到前驱节点,这个操作就不是O(1),而是O(n)。

具体来说,链表删除数据一般是两种情况:
1.删除节点中值等于某个特定值的节点。
2.删除给定指针指向的节点。

对于第一种情况,不管单链表双链表,都需要从头节点开始挨个遍历,找到值等于给定值的节点,然后删除,这个时间复杂度都为O(n)。

第二种情况下,因为已经找到了要删除的节点,但是我们删除该节点之前需要找到其前驱节点。对于单链表,前面我们已经分析了这个时间复杂度为O(n)。而双向链表因为有前后两个指针,能够在O(1)时间复杂度的情况下直接定位到前驱节点。

同理,如果需要在指定的节点前面插入一个节点,双向链表的时间复杂度同样是O(n),而单链表是O(1)。

2.LinkedHashMap基本功能

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

JDK1.8中,LinkedHashMap声明代码如上。可以看出来,LinkedHashMap继承自HashMap,同时实现了Map接口。

LinkedHashMap与HashMap的最大区别在于,HashMap是无序的,我们对HashMap遍历的时候是无序的。但是我们有时候也需要一个有序的Map,遍历的时候能按照插入的顺序,对应的数据结构就是LinkedHashMap。

    /**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    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);
        }
    }

JDK1.8里,LinkedHashMap中的内部类Entry定义。由上面代码不难看出,相比HashMap中的Node类,LinkedHashMap中的Entry多了before, after两个性质,before, after实际上维护的就是LinkedHashMap中的双向链表。

HashMap中的Node类部分代码如下

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
.......


上述图片来自网络

结合上面的代码与图示,重点分析一下里面的指针

Node里面有next指针,该指针是用于维护单链表的next属性的,目的是解决Hash桶中冲突key。
而before,after指针,则是维护双向链表,访问时候有序靠的就是这个双向链表。

3.LinkedHashMap与HashMap遍历对比

写了一小段代码,简单对比一下LinkedHashMap与HashMap遍历的效果。

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapVisit {

    public static void visit() {
        LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
        map.put("b", 2);
        map.put("a", 1);
        map.put("d", 4);
        map.put("c", 3);
        for(Map.Entry<String, Integer> entry: map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public static void visit2() {
        Map<String, Integer> map = new HashMap<>();
        map.put("b", 2);
        map.put("a", 1);
        map.put("d", 4);
        map.put("c", 3);
        for(Map.Entry<String, Integer> entry: map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public static void main(String[] args) {
        visit();
        System.out.println();
        visit2();
    }
}

以上代码运行的结果:

b: 2
a: 1
d: 4
c: 3

a: 1
b: 2
c: 3
d: 4

通过上面例子可以发现,LinkedHashMap遍历的时候,是按照插入顺序的。

4.accessOrder参数

LinkedHashMap的构造函数中有一个accessOrder属性,默认为false。LinkedHashMap默认的顺序是插入序,如果accessOrder属性为true,实现的是访问序。在访问序下,最近访问的节点会是尾节点,遍历的时候会是在最后的位置。

    public static void visit3() {
        LinkedHashMap<String, Integer> map = new LinkedHashMap<>(8, 0.75f,true);
        map.put("b", 2);
        map.put("a", 1);
        map.put("d", 4);
        map.put("c", 3);
        for(Map.Entry<String, Integer> entry: map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        int num = map.get("a");
        System.out.println();
        for(Map.Entry<String, Integer> entry: map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

上面代码的输出为

b: 2
a: 1
d: 4
c: 3

b: 2
d: 4
c: 3
a: 1

以上是关于双向链表与LinkedHashMap的主要内容,如果未能解决你的问题,请参考以下文章

双向链表的实现(双向链表与单向链表的简单区别联系和实现)

模板(双向链表与队列)

Java基础汇总(十六)——LinkedHashMap

java源码 -- LinkedHashMap

[单向链表与双向链表的实现]

数据结构开发:循环链表与双向链表