Java集合系列-LinkedHashMap内部探究

182 阅读3分钟

说明

虽然很多人做过这种讲解的文章、但是自己点进源码看里面的结构的感受是不一样的。

本章做一个HashTable的简单的总结、是jdk1.8版本的。

继承图

image.png

从图上可以看出一个简单的继承关系、主要是对LikedHashMap简单源码的探究

抛出问题

  1. 内部数据结构是怎么样的?
  2. 是否有扩容、扩容机制是什么时候扩容的、扩容多少?
  3. key或value是否能为空。
  4. 是否线程安全?

创建一个LinkedHashMap集合对象

Map<String,Integer> map = new LinkedHashMap<>();

属性

 // 双链表头
transient LinkedHashMap.Entry<K,V> head;

// 双链表尾
transient LinkedHashMap.Entry<K,V> tail;

// 用于访问顺序
final boolean accessOrder;

transient关键字在序列化的时候不会被序列化、 accessOrder:属性在这里的作用是、如果为true、则获取的数据会在链表后面、为false则不会

例子

public static void main(String[] args) {
    Map<String,Object> map = new LinkedHashMap<>(16, 0.75f, true);
    map.put("1","2");
    map.put("2","2");
    map.put("3","2");
    map.put("4","2");
    map.get("1");
    map.get("2");
    System.out.println(map.toString());
    Map<String,Object> map1 = new LinkedHashMap<>(16, 0.75f, false);
    map1.put("1","2");
    map1.put("2","2");
    map1.put("3","2");
    map1.put("4","2");
    map1.get("1");
    map1.get("2");
    System.out.println(map1.toString());
}

输出结果

{3=2, 4=2, 1=2, 2=2}
{1=2, 2=2, 3=2, 4=2}

构造器

public LinkedHashMap() {
    // 调用的是HashMap的构造
    super();
     // 给存取顺序 赋值
    accessOrder = false;
}

public LinkedHashMap(Map<? extends K, ? extends V> m) {
    // 调用的是HashMap的构造
    super();
    // 给存取顺序 赋值
    accessOrder = false;
    // 调用了HashMap中的方法
    putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
     // 调用的是HashMap的构造
    super(initialCapacity, loadFactor);
    // 给存取顺序 赋值
    this.accessOrder = accessOrder;
}

可以看一个第二个构造器中的putMapEntries方法

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) { // pre-size
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)
            resize();
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

点入到这个源码中会发现是调用的是HashMap中的方法、会发现他会初始化一些HashMap的需要的值、存储不够会调用扩容的方法、添加也是调用HashMap的方法一个一个遍历添加。

对集合CRUD简单说明

Map<String,Object> map = new LinkedHashMap<>();
map.put("key","1");

他其实调用的是HashMap的中添加方法、可以去看看我写的HashMap的添加方法解析

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

虽然他是调用的HashMap中的添加方法、但是他的Node节点创建的是LinkedHashMap节点的对象

image.png

可以得出一个结论存储的数据在HashMap的table中也有、只不过里面存储的对象是LinkedHashMap.Entry节点对象、可以自行debug调试看结果。

Map<String,Object> map = new LinkedHashMap<>();
map.put("1","2");
System.out.println(map.remove("1"));

他调的也是HashMap中的remove() 实现方法

image.png

他会去调用一个LinkedHashMap中的afterNodeRemoval()方法来删除替换节点的顺序。

改和增加是一样的他也是、通过key的hash值来找到对应位置、将其对应值覆盖

public V get(Object key) {
    Node<K,V> e;
            // getNode()是调用HashMap中的方法获取节点中的位置
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e); // 更换链表的位置
    return e.value; // 返回获取的value
}

解决问题

  1. 内部数据结构是怎么样的?
    1. LinkedHashMap是HashMap的一个子类、基本的一些方法都是走hashMap来实现的、只不过是Node(节点)、对象不一样、插入顺序和取出顺序一样 双向链表
  2. 是否有扩容、扩容机制是什么时候扩容的、扩容多少?
    1. 走HashMap的机制
  3. key或value是否能为空。
    1. 可以、只允许一条记录的键为null,允许多条记录的值为null
  4. 是否线程安全?
    1. 线程不安全集合。

总结

LinkedHashMap是HashMap的一个子类、他基本走的都是HashMap的方法及扩容的方式、但是存储的Node节点对象不一样、就是双向链表、是指每一个节点都有一对前驱后续、头节点的前驱一般为NULL、尾节点的后续一般为NULL 双向链表百度百科

  • 为什么Node节点对象不一样、可以往上看增加的方法那块写了。

结构图 image.png