上篇介绍了ArrayList常用的方法以及ArrayList的简单模拟实现,通过源码知道ArrayList底层是由数组来存储数据的,由于底层是一段连续的空间,当在中间插入元素的时候,需要把后边儿所有的元素向后移,复杂度为O(N),其效率是非常低的。因此ArrayList不适合做任意位置插入或删除,为了提高效率,Java中提供了LinkedList集合类,即链表结构。
链表是一种在物理存储上非连续的存储结构链表中的逻辑顺序是通过链表中节点的引用链接实现的。
链表的结构非常多样,以下组合起来共有8种。
重点掌握的两种:
1.单向不带头非循环:结构简单,一般不会用来单独存数据,更多的作用是作为其他数据结构中的子结构。
2.双向不带头循环:在Java集合框架中的LinkedList底层就是双向不带头循环链表
每个节点里面有三个或者两个属性,分别用于存储和引用 值 和 下一个节点地址 和 上一个节点地址
LinkedList底层是双向不带头循环链表,因为链表没有将元素存储在一个连续的空间内,元素存储在节点中,然后通过节点中的引用属性连接起来,所有在进行插入或者删除元素的时候,只需改变指向即可不需要移动数据,因此效率比较高。
在集合框架中,LinkedList也实现了List接口,具体框架如下图:
说明:
方法 | 解释 |
---|---|
LinkedList() | 无参构造方法 |
public LinkedList(Collection extends E> c) | 使用其他集合容器元素构造 |
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
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
模拟实现可以为了让我们了解代码的逻辑,加深对代码的印象
在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;
}
}