链表的本质是逻辑操作对物理操作映射。 主要还是利用指针对内存的操作。 链表应用非常广,尤其是对于大量的插入更新场景,如比较有名NIO模型(异步事件IO模型),主要也是双向列表+红黑树来构建的,还有一些节点队列都采用链表操作。 以及跳表的搜索,也是结合二分法+链表的特征来处理。
1. LinkedList
效果图
功能需求
-
双向链表,支持从头和尾部插入和循环查找
-
双向链表的使用迭代器遍历
源码分析
-
新增:构建节点,节点包括元素,前节点,后节点
private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } -
头节点插入:找到头节点为空的情况,则是尾结点就是当前插入的节点
private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; } -
尾节点插入:找到尾结点,如果是空,则从尾结点插入
void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; } -
删除节点:便利所有的节点,然后后进行删除,如果是空节点就不删除
-
遍历节点找到要删除的节点
ublic boolean remove(Object o) { if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; } -
删除非空节点
E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; } -
-
在指定位置添加所有节点
Object[] a = c.toArray();for (Object o : a) { @SuppressWarnings("unchecked") E e = (E) o; Node<E> newNode = new Node<>(pred, e, null); if (pred == null) first = newNode; else pred.next = newNode; pred = newNode; } -
迭代器功能
-
基本说明: 对于常用的遍历方式一种是数组,知道它的大小然后进行遍历,如for循环, 还有一种,你并不知道总节点的长度,或者长度一致在变化,类似一个节点的查找,知道出现没有节点数据为止。
-
实现思路: 对于构建的节点能够知道其前节点,后面节点,然后根据节点的索引来遍历,知道没有找到节点来停止。而找下一个节点数据结构包括前置的内存地址引用和后节点地址引用。 根据内存地址找到对应的下一节点或者上一节点的内存信息。 然后进行读取,判断,处理操作
-
具体实现: 实现ListIterator接口,包含了hasNext,next,hasPrevious,previous,nextIndex,previousIndex,remove,add,set
//获取下一个节点 public E next() { checkForComodification(); if (!hasNext()) throw new NoSuchElementException(); lastReturned = next; next = next.next; nextIndex++; return lastReturned.item; } //删除下一个节点 public void remove() { checkForComodification(); if (lastReturned == null) throw new IllegalStateException(); Node<E> lastNext = lastReturned.next; unlink(lastReturned); if (next == lastReturned) next = lastNext; else nextIndex--; lastReturned = null; expectedModCount++; }
-
2. LinkedHashMap
效果图

功能需求
- 对于HashMap支持有序,保证插入和输出顺序保持一致,这样可以利用链表的进行迭代的功能来输出。
源码分析
-
LinkedHashMap 集成了HashMap, 对于HashMap数据结构后面再分享。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> -
增加Map.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); } } -
HashMap->put值
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; 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); -
每次put的数据都会在插入到链表的队尾。这里设计有点巧妙:重载HashMap的newNode的方法,在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; }
-
-
更新,remove,只是会更新链表节点还是使用hashMap方法
-
遍历:对链表迭代器实现LinkedHashIterator
final LinkedHashMap.Entry<K,V> nextNode() { LinkedHashMap.Entry<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); current = e; next = e.after; return e; } public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; }
3. LinkedHashSet
功能需求
- 主要是使用LinkHashMap的特性,使用Map key 不重复的特性,把集合全部使用Map设置key,然后得到一个集合。
源码分析
-
把读取的对象使用map集合存储,详见HashSet
// Set the capacity according to the size and load factor ensuring that // the HashMap is at least 25% full but clamping to maximum capacity. capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f), HashMap.MAXIMUM_CAPACITY); // Create backing HashMap map = (((HashSet<?>)this) instanceof LinkedHashSet ? new LinkedHashMap<E,Object>(capacity, loadFactor) : new HashMap<E,Object>(capacity, loadFactor)); // Read in all elements in the proper order. for (int i=0; i<size; i++) { @SuppressWarnings("unchecked") E e = (E) s.readObject(); map.put(e, PRESENT); }