LinkedList简述
LinkedList底层使用一个Node数据结构,有前后两个指针,双向链表实现的。相对数组,链表插入效率较高,只需要更改前后两个指针即可;另外链表不存在扩容问题,因为链表不要求存储空间连续,每次插入数据都只是改变last指针;另外,链表所需要的内存比数组要多,因为他要维护前后两个指针;它适合删除,插入较多的场景。非线程安全。
LinkedList 是一个继承于AbstractSequentialList的双向链表。
- 它也可以被当作堆栈、队列或双端队列进行操作;
- 继承了
AbstractSequentialList
,这个类提供了一个基本的List接口实现,为实现序列访问的数据储存结构的提供了所需要的最小化的接口实现。对于支持随机访问数据的List比如数组,应该优先使用AbstractList。 - 实现 List 接口,List是Collection的子接口,它是一个元素有序(按照插入的顺序维护元素顺序)、可重复、可以为null的集合;
- 实现 Deque 接口,即能将LinkedList当作双端队列使用;
- 实现了Cloneable接口,即覆盖了函数clone(),能克隆;
- 实现java.io.Serializable接口,支持序列化;
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {...}
手写实现
Node类
在MyLinkedList类中定义Node节点类(静态内部类):
private static class Node<E> {
//前节点
Node<E> prev;
//当前节点
E item;
//后节点
Node<E> next;
public Node(Node<E> prev, E item, Node<E> next) {
this.item = item;
this.prev = prev;
this.next = next;
}
}
定义MyLinkedList的属性
size(长度)、first(头节点)、last(尾节点)
public class MyLinkedList<E> {
//链表的长度
int size = 0;
//链表的头部
Node<E> first;
//链表的尾部
Node<E> last;
...
}
定义增删改查方法
插入
/**
* 在链表尾部插入
* @param e
*/
public void add(E e) {
//创造节点,将节点连接链表
Node<E> newNode = new Node<E>(last, e, null);
//链表连接节点
if (last == null) {
//链表为空
//新节点赋值给头节点
first = newNode;
} else {
//链表不为空
//将当前链表last的下一个节点指向新节点(连接) last-->newNode
last.next = newNode;
}
//将新节点变为尾节点
last = newNode;
size++;
}
/**
* 根据索引插入
*
* @param index
* @param e
*/
public void add(int index, E e) {
//当前索引下的节点
Node<E> succ = getNode(index);
//前继节点
Node<E> prev = succ.prev;
//创建新节点并连接链表
Node<E> newNode = new Node<>(prev, e, succ);
//将链表连接新节点
succ.prev = newNode;
if (prev == null) {
//如果prev为空,则表示链表为空,则新节点赋值给首节点
first = newNode;
} else {
//prev的前节点连接新节点
prev.next = newNode;
}
size++;
}
/**
* 将集合c中数据在链表末尾添加
*
* @param c
* @return
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
* 将集合c中数据在链表指定位置添加
*
* @param index
* @param c
* @return
*/
public boolean addAll(int index, Collection<? extends E> c) {
//检查index是否合法
if (index < 0 && index > size) {
throw new IndexOutOfBoundsException("指定的位置不合法:" + index);
}
Object[] items = c.toArray();
int length = items.length;
if (length == 0) {
return false;
}
Node<E> succ, prev;
//获得当前索引下的节点和前节点
if (index == size) {
succ = null;
prev = last;
} else {
succ = getNode(index);
prev = succ.prev;
}
//插入
for (Object item : items) {
E e = (E) item;
Node<E> newNode = new Node<E>(prev, e, null);
if (prev == null) {
first = newNode;
} else {
prev.next = newNode;
}
prev = newNode;
}
//定义尾节点
if (succ == null) {
last = prev;
} else {
prev.next = succ;
succ.prev = prev;
}
size += length;
return true;
}
查询
/**
* 根据索引获取节点
*
* @param index
* @return
*/
Node<E> getNode(int index) {
//首先判断index是否合法
//size>>1 相当于 size/(2的1次方)
if (index < (size >> 1)) {
//从头部开始找
Node<E> x = first;
for (int i = 0; i < index; i++) {
x = x.next;
}
return x;
} else {
//从尾部开始找
Node<E> x = last;
for (int i = size - 1; i > index; i--) {
x = x.prev;
}
return x;
}
}
/**
* 根据索引查询数据
*
* @param index
* @return
*/
public E get(int index) {
//首先判断index是否合法
checkElementIndex(index);
return getNode(index).item;
}
修改
/**
* 根据索引修改
*
* @param index
* @param e
* @return 返回先前位于指定位置的元素
*/
public E set(int index, E e) {
checkElementIndex(index);
//获取当前索引下的节点,并且修改为新数据
Node<E> succ = getNode(index);
E oldVal = succ.item;
succ.item = e;
return oldVal;
}
删除
/**
* 根据索引删除
*
* @param index
* @return
*/
public E remove(int index) {
checkElementIndex(index);
//当前索引下的节点
Node<E> succ = getNode(index);
//前继节点
Node<E> prev = succ.prev;
//后继节点
Node<E> next = succ.next;
if (prev == null) {
//删除的为头节点
first = next;
} else {
prev.next = next;
succ.prev = null;
}
if (next == null) {
//删除的为尾节点
last = prev;
} else {
next.prev = prev;
succ.next = null;
}
size--;
return succ.item;
}
实现代码(全)
package com.clh.test.mylist;
import java.util.Collection;
/**
* 手写LinkedList
* LinkedList是java中的双向链表, 是List接口链表的实现,特点是增删快,查找慢。
*
* @author 华华
* @date 2020/4/27
**/
public class MyLinkedList<E> {
/**
* 链表的长度
*/
int size = 0;
/**
* 链表的头部
*/
Node<E> first;
/**
* 链表的尾部
*/
Node<E> last;
public MyLinkedList() {
}
public MyLinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
public int size() {
return size;
}
/**
* 将集合c中数据在链表末尾添加
*
* @param c
* @return
*/
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
/**
* 将集合c中数据在链表指定位置添加
*
* @param index
* @param c
* @return
*/
public boolean addAll(int index, Collection<? extends E> c) {
//检查index是否合法
if (index < 0 && index > size) {
throw new IndexOutOfBoundsException("指定的位置不合法:" + index);
}
Object[] items = c.toArray();
int length = items.length;
if (length == 0) {
return false;
}
Node<E> succ, prev;
//获得当前索引下的节点和前节点
if (index == size) {
succ = null;
prev = last;
} else {
succ = getNode(index);
prev = succ.prev;
}
//插入
for (Object item : items) {
E e = (E) item;
Node<E> newNode = new Node<E>(prev, e, null);
if (prev == null) {
first = newNode;
} else {
prev.next = newNode;
}
prev = newNode;
}
//定义尾节点
if (succ == null) {
last = prev;
} else {
prev.next = succ;
succ.prev = prev;
}
size += length;
return true;
}
/**
* 在链表尾部插入
*
* @param e
*/
public void add(E e) {
//创造节点,将节点连接链表
Node<E> newNode = new Node<E>(last, e, null);
//链表连接节点
if (last == null) {
//表示链表为空时新节点赋值给首节点
first = newNode;
} else {
//将当前链表last的下一个节点指向新节点(连接) last-->newNode
last.next = newNode;
}
//将新节点变为尾节点
last = newNode;
size++;
}
/**
* 根据索引插入
*
* @param index
* @param e
*/
public void add(int index, E e) {
Node<E> succ = getNode(index);
Node<E> prev = succ.prev;
Node<E> newNode = new Node<>(prev, e, succ);
succ.prev = newNode;
if (prev == null) {
first = newNode;
} else {
prev.next = newNode;
}
size++;
}
/**
* 根据索引查询数据
*
* @param index
* @return
*/
public E get(int index) {
//首先判断index是否合法
checkElementIndex(index);
return getNode(index).item;
}
/**
* 根据索引获取节点
*
* @param index
* @return
*/
Node<E> getNode(int index) {
//首先判断index是否合法
//size>>1 相当于 size/(2的1次方)
if (index < (size >> 1)) {
//从头部开始找
Node<E> x = first;
for (int i = 0; i < index; i++) {
x = x.next;
}
return x;
} else {
//从尾部开始找
Node<E> x = last;
for (int i = size - 1; i > index; i--) {
x = x.prev;
}
return x;
}
}
/**
* 判断参数是否为当前链表的索引
*
* @param index
*/
private void checkElementIndex(int index) {
if (index >= 0 && index < size) {
return;
} else {
throw new IndexOutOfBoundsException("index不合法!!index:" + index + ",size:" + size);
}
}
/**
* 根据索引修改
*
* @param index
* @param e
* @return 返回先前位于指定位置的元素
*/
public E set(int index, E e) {
checkElementIndex(index);
Node<E> succ = getNode(index);
E oldVal = succ.item;
succ.item = e;
return oldVal;
}
/**
* 根据索引删除
*
* @param index
* @return
*/
public E remove(int index) {
checkElementIndex(index);
Node<E> succ = getNode(index);
Node<E> prev = succ.prev;
Node<E> next = succ.next;
if (prev == null) {
first = next;
} else {
prev.next = next;
succ.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
succ.next = null;
}
size--;
return succ.item;
}
/**
* 节点类
*
* @param <E>
*/
private static class Node<E> {
/**
* 前节点
*/
Node<E> prev;
//当前节点
E item;
//后节点
Node<E> next;
public Node(Node<E> prev, E item, Node<E> next) {
this.item = item;
this.prev = prev;
this.next = next;
}
}
}
总结
LinkedList的插入思路就是将新节点连接链表、再将链表连接新节点,如果是根据索引插入就要判断索引是否合法,是否在首部或尾部进行插入。删改查都差不多