LinkedList与链表
一、ArrayList的缺陷
由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。
二、链表
1、链表的概念及结构
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向或者双向

2. 带头或者不带头
3. 循环或者非循环
虽然有这么多的链表的结构,但是我们重点掌握两种:
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
2、链表的实现
那么现在,我们可以利用现有的知识,来模拟完成一个链表的实现过程:
public class TwoWayLinkList<T> {//首结点private Node head;//尾结点private Node last;//链表长度private int N;//结点类private class Node{public T item;public Node pre;public Node next;public Node(T item, Node pre, Node next) {this.item = item;this.pre = pre;this.next = next;}}public TwoWayLinkList() {//初始化头结点和尾结点this.head = new Node(null,null,null);this.last = null;//初始化元素个数this.N = 0;}//清空链表public void clear() {this.head.next = null;this.last = null;this.N = 0;}//获取链表长度public int length() {return this.N;}//判断链表是否为空public boolean isEmpty() {return N == 0;}//获取第一个元素public T getFirst() {if (isEmpty()) {return null;}return head.next.item;}//获取最后一个元素public T getLast() {if (isEmpty()) {return null;}return last.item;}//插入元素tpublic void insert(T t) {//如果链表为空if (isEmpty()) {//创建新结点Node newNode = new Node(t,head,null);//让新结点称为尾结点last = newNode;//让头结点指向尾结点head.next = last;}else {//如果不为空//创建新结点Node newNode = new Node(t,last,null);//让当前尾结点指向新结点last.next = newNode;//让新结点成为尾结点last = newNode;}//元素个数加一this.N++;}//指定位置插入元素tpublic void insert(int i, T t) {//找到i位置的前一个结点Node pre = head;for (int j = 0; j < i; j++) {pre = pre.next;}//找到i位置的结点Node cur = pre.next;//创建新结点Node newNode = new Node(t,pre,cur);//让前一结点的next变为新结点pre.next = newNode;//让i位置的前一结点变为新结点cur.pre = newNode;//元素个数加一this.N++;}//获取指定位置的元素public T get(int i) {Node now = head.next;for (int j = 0; j < i; j++) {now = now.next;}return now.item;}//找到第一次出现t的位置public int indexOf(T t) {Node now = head;for (int i = 0; now.next != null; i++) {now = now.next;if (now.item.equals(t)) {return i;}}return -1;}//删除i处的元素,并返回其元素public T remove(int i) {//找到前一个结点Node pre = head;for (int j = 0; j < i; j++) {pre = pre.next;}//找到i位置的下一个结点Node cur = pre.next;//找到下一个结点Node nextNode = cur.next;//让前一个结点的next指向下一个结点pre.next = nextNode;//让下一个结点的pre指向前一个结点nextNode.pre = pre;//元素个数减一this.N--;return cur.item;}
}
三、LinkedList的使用
1、什么是LinkedList
关于LinkedList的具体介绍,我们可以看一下它的官方文档;LinkedList 的官方文档
LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

在集合框架中,LinkedList也实现了List接口,具体如下:
那么,这里关于LinkedList,我们要注意几点说明:
1. LinkedList实现了List接口2. LinkedList的底层使用了双向链表3. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问4. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)5. LinkedList比较适合任意位置插入的场景
2、LinkedList的使用
(1)LinkedList的构造
(2) LinkedList的其他常用方法介绍
那么,接下来我们来看一下如何使用这些方法:
public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1); // add(elem): 表示尾插list.add(2);list.add(3);list.add(4);list.add(5);list.add(6);list.add(7);System.out.println(list.size());System.out.println(list);
// 在起始位置插入0list.add(0, 0); // add(index, elem): 在index位置插入元素elemSystem.out.println(list);list.remove(); // remove(): 删除第一个元素,内部调用的是removeFirst()list.removeFirst(); // removeFirst(): 删除第一个元素list.removeLast(); // removeLast(): 删除最后元素list.remove(1); // remove(index): 删除index位置的元素System.out.println(list);
// contains(elem): 检测elem元素是否存在,如果存在返回true,否则返回falseif(!list.contains(1)){list.add(0, 1);}list.add(1);System.out.println(list);System.out.println(list.indexOf(1)); // indexOf(elem): 从前往后找到第一个elem的位置System.out.println(list.lastIndexOf(1)); // lastIndexOf(elem): 从后往前找第一个1的位置int elem = list.get(0); // get(index): 获取指定位置元素list.set(0, 100); // set(index, elem): 将index位置的元素设置为elemSystem.out.println(list);
// subList(from, to): 用list中[from, to)之间的元素构造一个新的LinkedList返回List<Integer> copy = list.subList(0, 3);System.out.println(list);System.out.println(copy);list.clear(); // 将list中元素清空System.out.println(list.size());}
(3)LinkedList的遍历
和其它的遍历差不多,LinkedList的遍历也有三种方法:
1、foreach遍历
for (int e:list) {System.out.print(e + " ");
}
System.out.println();
2、使用迭代器遍历---正向遍历
ListIterator<Integer> it = list.listIterator();while(it.hasNext()){System.out.print(it.next()+ " ");
}System.out.println();
3、使用反向迭代器---反向遍历
ListIterator<Integer> rit = list.listIterator(list.size());while (rit.hasPrevious()){System.out.print(rit.previous() +" ");
}System.out.println();
当然,还有一种是最常见的循环遍历LinkedList,我们在这里就不赘述了