java集合之LinkList解析

LinkeList是基于双向链表的实现,同时它也实现了Queue队列,Deque栈的实现,使得我们可以进行双向队列的实现,上一节java集合之ArrayList解析中说过ArrayList查询效率比较高,添加和删除集合中的元素效率比较低,而LinkList正好相反.
LinkList组织结构图

这里写图片描述
LinkList继承自AbstractSequentialList(ArrayList继承AbstractList)同时实现了Serializable和Cloneable标记接口,相比于ArrayList缺少了RandomAccess,说明进行随机访问效率比较低,多了Deque及Queue可以进行栈和队列的操作.

队列Queue
public interface Queue<E> extends Collection<E> {
    boolean add(E var1);

    boolean offer(E var1);

    E remove();

    E poll();

    E element();

    E peek();
}
LinkList实现了队列Queue,Queue扩展了Collection,队列的特点是先进先出,在尾部添加数据,在头部删除数据.
  • 使用add,offer在尾部添加数据
  • 使用remove,poll获取头部数据,并删除头部数据,会改变队列结构
  • 使用element,peek仅仅是获取头部数据,不会改变队列结构

每种操作都有两种形式,它们的区别是什么?区别就在于针对队列特殊情况的处理上,所谓队列的特殊情况即队列满和队列空.

当队列满的时候执行add操作会抛出IllegalStateException,而offer只会返回false.

当队列空的时候执行remove和element会抛出NoSuchElementException,而poll和peek会返回null。

LinkList队列没有长度的限制,其他的特定队列是有长度的。

LinkList作为队列使用:

 Queue<Integer> queue=new LinkedList<>();
       queue.offer(1);
       queue.offer(2);
       queue.offer(3);

       while (queue.peek()!=null){
           System.out.println(queue.poll());//输出1>2>3
       }
   }
Deque双端队列

栈和队列都可以进行双端操作,只是栈只能操作头部,而队列可以操作头部和尾部,在尾部添加数据,在头部删除数据.

LinkList实现了栈,栈在开发中很常见,栈的特点是先进后出.Deque继承了队列Queue,它同样是个接口。

public interface Deque<E> extends Queue<E> {


    boolean add(E var1);

    boolean offer(E var1);

    E remove();

    E poll();

    E element();

    E peek();

    void push(E var1);

    E pop();

    int size();

    Iterator<E> iterator();

    Iterator<E> descendingIterator();
    void addFirst(E e);
    void addLast(E e);
    E getFirst();
    E getLast();
    boolean offerFirst(E e);
    boolean offerLast(E e);
    E peekFirst();
    E peekLast();
    E pollFirst();
    E pollLast();
    E removeFirst();
    E removeLast();
}

Deque栈扩展了队列,实现入栈和出栈操作。

push表示入栈,在头部添加数据,如果这时栈满,报IllegalStateException异常.

pop表示出栈,返回头部数据,并且从栈中删除,如果栈是空的,报NoSuchElementException异常

peek表示获取栈中头部数据,并返回头部数据,不改变栈结构,若栈是空的,就返回null.

xxxFirst操作头部,xxxLast操作尾部。与队列类似,每种操作有两种形式,区别也是在队列为空或满时,处理不同。为空时,getXXX/removeXXX会抛出异常,而peekXXX/pollXXX会返回null。队列满时,addXXX会抛出异常,offerXXX只是返回false。

栈和队列只是双端队列的特殊情况,它们的方法都可以使用双端队列的方法替代,不过,使用不同的名称和方法,概念上更为清晰

Iterator descendingIterator();迭代器方法,可以从后往前遍历

LinkList作为栈使用

Deque<Integer> deque=new LinkedList<>();
       deque.push(1);
       deque.push(2);
       deque.push(3);

       while (deque.peek()!=null){
           System.out.println(deque.pop());//输出3>2>1
       }

在java中Stack类也用于表示栈,具体的实现细节大家可以自己看看源码。LinkedList的用法是比较简单的,与ArrayList用法类似,支持List接口,只是,LinkedList增加了一个接口Deque,可以把它看做队列、栈、双端队列,方便的在两端进行操作。


LinkList源码分析:

ArrayList内部的实现是基于动态数组,数据元素在内存中是连续存放的.而LinkList是基于双向链表实现的,在物理上链表中的数据元素在内存中的形态是非连续的,它是靠链表的前后节点Node指向进行关联的,下面是双链表的结构图.

这里写图片描述

LinkList中的内部类Node

Node类表示节点,item表示当前的数据元素,prev指向前一个节点,next指向下一个节点.

First节点也叫头节点,它的prev是null

Last节点又叫尾节点,它的next是null.

private static class Node {
    E item;
    Node next;
    Node prev;

    Node(Node prev, E element, Node next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}    

LinkList中的成员变量,分别代表如下含义:

size表示链表的长度即数据元素的个数

first代表头节点,last代表尾节点,默认都是null

LinkList的所有操作都是围绕上面这三个成员变量展开的。

transient int size;
transient LinkedList.Node<E> first;
transient LinkedList.Node<E> last;
LinkList的add操作
public boolean add(E e) {
        this.linkLast(e);
        return true;
    }

(1)在执行add操作的时候调用了添加数据元素e到链表尾部的方法linkLast,并返回true.

void linkLast(E e) {
    final Node l = last;
    final Node newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

(2)得到当前链表的尾节点并赋值给节点l

(3)创建一个新节点newNode,将节点l设置为它的前驱节点,数据元素e为其数据,null为后继节点。

(4)将newNode节点设置为last节点.

(5)如果l为null,表明当前列表是空链表,这时设置头节点first为newNode否则执行第6步。

(6)设置头节点的next节点为newNode
(7)让size加1,modCount加1,modCount的作用代表修改次数与ArrayList相同。

LinkList的remove操作
public E remove(int index) {
        this.checkElementIndex(index);
        return this.unlink(this.node(index));
    }

(1)首先调用checkElementIndex方法检查索引是不是合法,如果不合法抛出IndexOutOfBoundsException异常,否则执行第2步。

(2)调用unlink(node(index))方法,其中node(index)表示根据索引查询链表中的node元素.

Node node(int index) {
    if (index < (size >> 1)) {
        Node x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

(3)如果索引index

E unlink(Node x) {
    final E element = x.item;
    final Node next = x.next;
    final Node 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;
}

(5)删除node节点x的基本思路是就是把x节点的前驱节点与后继节点直接关联起来,然后修改x.item=null以及size进行减1,modCount加1操作。


至此LinkList的add与remove操作源码分析完了,其他的get/size等方法也就可以很容易的看懂了,由于时间的关系这里不再赘述了。

LinkList的总结
  • LinkedList内部是用双向链表实现的,维护了长度、头节点和尾节点
  • 按需分配空间,不需要预先分配很多空间
  • 不可以随机访问,按照索引位置访问效率比较低,必须从头或尾顺着链接找,效率为O(N/2).
  • 不管列表是否已排序,只要是按照内容查找元素,效率都比较低,必须逐个比较,效率为O(N)。
  • 在两端添加、删除元素的效率很高,为O(1)。
  • 在中间插入、删除元素,要先定位,效率比较低,为O(N),但修改本身的效率很高,效率为O(1)。
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值