目录
前言💪💪
上篇介绍了ArrayList常用的方法以及ArrayList的简单模拟实现,通过源码知道ArrayList底层是由数组来存储数据的,由于底层是一段连续的空间,当在中间插入元素的时候,需要把后边儿所有的元素向后移,复杂度为O(N),其效率是非常低的。因此ArrayList不适合做任意位置插入或删除,为了提高效率,Java中提供了LinkedList集合类,即链表结构。
链表
链表是一种在物理存储上非连续的存储结构链表中的逻辑顺序是通过链表中节点的引用链接实现的。
链表的结构非常多样,以下组合起来共有8种。
-
单向的和双向的
-
带头的和不带头的
-
循环的和非循环的
-
单向带头循环
-
单向带头非循环
-
单向不带头循环
-
单向不带头非循环
-
双向带头循环
-
双向带头非循环
-
双向不带头循环
-
双向不带头非循环
重点掌握的两种:
1.单向不带头非循环:结构简单,一般不会用来单独存数据,更多的作用是作为其他数据结构中的子结构。
2.双向不带头循环:在Java集合框架中的LinkedList底层就是双向不带头循环链表
每个节点里面有三个或者两个属性,分别用于存储和引用 值 和 下一个节点地址 和 上一个节点地址
LinkedList简介
LinkedList底层是双向不带头循环链表,因为链表没有将元素存储在一个连续的空间内,元素存储在节点中,然后通过节点中的引用属性连接起来,所有在进行插入或者删除元素的时候,只需改变指向即可不需要移动数据,因此效率比较高。
在集合框架中,LinkedList也实现了List接口,具体框架如下图:
说明:👇👇
- LinkedList实现了List接口
- LinkedList的底层是一个双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList对于任意位置插入和删除时效率较高,时间复杂度为O(1)
- LinkedList比较适合任意位置插入的场景下使用
LinkedList使用
构造方法
方法 | 解释 |
---|---|
LinkedList() | 无参构造方法 |
public LinkedList(Collection<? extends E> c) | 使用其他集合容器元素构造 |
LinkedList常用方法
boolean add(E e) //尾插 e
void add(int index, E element) //将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c) //尾插 c 中的元素
E remove(int index) //删除 index 位置元素
boolean remove(Object o) //删除遇到的第一个 o
E get(int index) //获取下标 index 位置元素
E set(int index, E element) //将下标 index 位置元素设置为 element
void clear() //清空
boolean contains(Object o) //判断 o 是否在线性表中
int indexOf(Object o) //返回第一个 o 所在下标
int lastIndexOf(Object o) //返回最后一个 o 的下标
List<E> subList(int fromIndex, int toIndex) //截取部分 list
LinkedList的遍历
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
//使用for-each遍历
for (int x:list) {
System.out.print(x + " ");
}
System.out.println();
//使用迭代器遍历 -- 正向遍历
ListIterator<Integer> it = list.listIterator();
while(it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
//使用迭代器遍历 -- 反向遍历
ListIterator<Integer> itr = list.listIterator(list.size());
while(itr.hasPrevious()) {
System.out.print(itr.previous() + " ");
}
}
打印结果为:
1 2 3 4 5
1 2 3 4 5
5 4 3 2 1
模拟实现LinkedList链表
模拟实现可以为了让我们了解代码的逻辑,加深对代码的印象
在Java内部的LinkedList是一个泛型的,为了方便理解,这里使用整形举例👇👇
public class MyLinkedList {
public Node head;//引用头节点
public Node last;//引用尾巴节点
//节点定义成了 静态内部类,
static class Node{
public int val; // 值域
public Node prev; // 用来连接上一个节点
public Node next; //用来连接下一个节点
//一个参数的构造方法,用来传入数据
public Node(int val) {
this.val = val;
}
}
//头插法
public void addFirst(int data){
Node node = new Node(data);
//第一次插入
if (head == null) {
head = node;
last = node;
return;
}
node.next = head;
head.prev = node;
head = node;
}
//尾插法
public void addLast(int data){
Node node = new Node(data);
//第一次插入
if (head == null) {
head = node;
last = node;
return;
}
last.next = node;
node.prev = last;
last = node;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data) throws AddIndexOutExction {
Node node = new Node(data);
Node cur = head;
//判断index的有效性
if (index < 0 || index > size()) {
//如果index无效,则抛出一个异常
throw new AddIndexOutExction("下标无效!");
}
//头插
if (index == 0) {
addFirst(data);
return;
}
//尾插
if (index == size()) {
addLast(data);
return;
}
//中间插
while(index != 0) {
cur = cur.next;
index--;
}
node.next = cur;
node.prev = cur.prev;
cur.prev = node;
node.prev.next = node;
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
Node cur = head;
while(cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//删除第一次出现关键字为key的节点
public void remove(int key){
Node cur = head;
while(cur != null) {
if (cur.val == key) {
if (cur == head) {
//头节点
head = head.next;
if (head != null) {
head.prev = null;
}
} else {
//中间 尾巴节点
if (cur.next != null) {
//中间
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}else {
//尾巴
last = last.prev;
}
}
return;
}
cur = cur.next;
}
}
//删除所有值为key的节点
public void removeAllKey(int key){
Node cur = head;
while(cur != null) {
if (cur.val == key) {
if (cur == head) {
//头节点
head = head.next;
if (head != null) {
head.prev = null;
}
} else {
//中间 尾巴节点
if (cur.next != null) {
//中间
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}else {
//尾巴
last = last.prev;
}
}
}
cur = cur.next;
}
}
//得到单链表的长度
public int size(){
int count = 0;
Node cur = head;
while(cur != null) {
count++;
cur = cur.next;
}
return count;
}
//打印链表 注意:集合类中并没有此方法,为了模拟实现方便观看数据
public void display(){
Node cur = head;
while(cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
public void clear(){
Node cur = head;
while(cur != null) {
Node curNext = cur.next;
cur.prev = null;
cur.next = null;
cur = curNext;
}
head = null;
last = null;
}
}
ArrayList与LinkedList的区别
- 在存储空间上:ArrayList物理上一定连续,LinkedList逻辑上连续,物理上不一定连续
- 随机访问:ArrayList支持,LinkedList不支持
- 头插:ArrayList需要挪动元素效率低O(N),LinkedList不需要挪动元素O(1)
- 插入:ArrayList空间不够需要扩容,LinkedList没有容量概念
- 应用场景:ArrayList元素高效存储和频繁访问,LinkedList任意位置插入和频繁删除