Java中LinkedHashSet的源码剖析
更新时间:2023年09月05日 09:45:48 作者:昱晟168
这篇文章主要介绍了Java中LinkedHashSet的源码剖析,LinkedHashSet是HashSet的子类,LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表,需要的朋友可以参考下
LinkedHashSet 的全面说明
- LinkedHashSet是HashSet的子类
- LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCOde值来决定元素的存储位置,同时使用链表维护元素的次序,这使元素看起来以插入顺序保存的
- LinkedHashSet不允许添加重复元素
注意是由于底层维护着是一个双向链表和数组,所以插入和取出的顺序是一致的,原因是用了链表维护元素添加的顺序
继承关系图:
构造器
Constructor and Description
- LinkedHashSet() 构造一个具有默认初始容量(16)和负载因子(0.75)的新的,空的链接散列集。
- LinkedHashSet(Collection<? extends E> c) 构造与指定集合相同的元素的新的链接散列集。
- LinkedHashSet(int initialCapacity) 构造一个具有指定初始容量和默认负载因子(0.75)的新的,空的链接散列集。
- LinkedHashSet(int initialCapacity, float loadFactor) 构造具有指定的初始容量和负载因子的新的,空的链接散列集。
LinkedHashSet 底层机制示意图
说明:
- 在 LinkedHashSet 中维护一个 hash 表和双向链表【 LinkedHashSet 有 head 和 tail 】
- 每一个节点有 before 和 after 属性,这样可以形成双向链表
- 在添加一个元素时,先求 hash 值,在求索引,确定该元素在 hashtable 的位置,然后将添加的元素加入到双向链表【如果已经存在,则不添加【原则和 HashSet 一样】】
tail.next = new Element;//简单指定 newElement.prev = tail; tail = newElement;
这样的话,我们遍历 LinkedHashSet 也能确保插入顺序和遍历顺序一致
注意: LinkedHashSet 的特点是插入的顺序和遍历的顺序是一致【本质底层是 LinkedHashMap 】,并且不能存储相同的元素【也就是对象】
package com.hspedu.set_; import java.util.LinkedHashSet; import java.util.Set; @SuppressWarnings({"all"}) public class LinkedHashSetSource { public static void main(String[] args) { //分析一下LinkedHashSet的底层机制 Set set = new LinkedHashSet(); set.add(new String("AA")); set.add(456); set.add(456); set.add(new Customer("刘", 1001)); set.add(123); set.add("HSP"); System.out.println("set=" + set); //老韩解读 //1. LinkedHashSet 加入顺序和取出元素/数据的顺序一致 //2. LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类) //3. LinkedHashSet 底层结构 (数组table+双向链表) //4. 添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry //5. 数组是 HashMap$Node[] 存放的元素/数据是 LinkedHashMap$Entry类型 /* //继承关系是在内部类完成. 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); } } */ } } class Customer { private String name; private int no; public Customer(String name, int no) { this.name = name; this.no = no; } }
LinkedHashSet 源码剖析
LinkedHashSet set = new LinkedHashSet<>(); set.add("湛江"); set.add(null); set.add(168); set.add(new Date()); 步骤1:使用无参构造器创建对象 public LinkedHashSet() { super(16, .75f, true); 调用父类的有参构造 HashSet 在底层创建一个 LinkedHashMap } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); } 在 LinkedHashMap 的构造器调用父类的 构造器 HashMap 注意是:LinkedHashMap是HashMap的子类 public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor);loadFactor加载因子 initialCapacity初始容量 accessOrder = false; } public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } 步骤2:添加方法 下面这个方法是调用了 LinkedHashMap的父类HashMap中的put方法 public boolean add(E e) { return map.put(e, PRESENT)==null; } 调用 HashMap 中 put 方法 public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } 继承调用 HashMap方法中 putVal 方法 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; 如果 table 表为null并且长度为0时,则调用 resize 方法完成扩容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } 在创建 newNode 节点会动态绑定到 LinkedHashMap 中的 newNode 方法 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); 完成双向链表之间的连接 return p; } 再调用 LinkedHashMap 中的静态内部类 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); } } 继续调用本类 静态内部类 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; } 最后调用本类 LinkedHashMap类中 linkNodeLat 方法完成前后的指向形成链表 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } }
课后练习题:
到此这篇关于Java中LinkedHashSetd的源码剖析的文章就介绍到这了,更多相关LinkedHashSetd源码剖析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SpringBoot整合Redis正确的实现分布式锁的示例代码
这篇文章主要介绍了SpringBoot整合Redis正确的实现分布式锁的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2020-07-07
最新评论