Java集合学习六 LinkedList

一 LinkedList继承关系

 

首先 ,从继承关系来看,继承了一个抽象的AbstractSequectialList 这个抽象类,还有一个Deque接口。所以在详细看LinkedList之前时,先看下继承关系上层的结构。

二 AbstractSequectialList

从结构来看像是实现了 get set add remove 等等之类的方法。进一步看下。


public E get(int index) {
        try {
            return listIterator(index).next(); //通过迭代器方法得到元素
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }


public E set(int index, E element) {
        try {
            ListIterator<E> e = listIterator(index); 
            E oldVal = e.next(); //返回旧值
            e.set(element); 通过迭代器set方法,可以通过之前的lastRet保存的也就是index的位置设置该元素
            return oldVal;
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

public void add(int index, E element) {
        try {
            listIterator(index).add(element);
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }


public E remove(int index) {
        try {
            ListIterator<E> e = listIterator(index);
            E outCast = e.next();
            e.remove();
            return outCast; 
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }


public boolean addAll(int index, Collection<? extends E> c) {
        try {
            boolean modified = false;
            ListIterator<E> e1 = listIterator(index);
            Iterator<? extends E> e2 = c.iterator();
            while (e2.hasNext()) {
                e1.add(e2.next());
                modified = true;
            }
            return modified;
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }


public Iterator<E> iterator() {
        return listIterator();
    }

public abstract ListIterator<E> listIterator(int index);

总结下:所以通过看源码可以知道,这里实现的方法都是间接通过ListIterator来实现的。因为不知道是抽象类不知道具体的实现,而且这个抽象类的名字看了就知道他是有序列表,和RandomAccess 的类正好相反。看了该类的文档注释 大概是这个意思:

1.这个类提供了一个基本的List接口实现,为实现序列访问的数据储存结构的提供了所需要的最小化的接口实现。对于支持随机访问数据的List比如数组,应该优先使用AbstractList。

2.这里类是AbstractList类中与随机访问类相对的另一套系统,采用的是在迭代器的基础上实现的get、set、add和remove方法。

3.为了实现这个列表。仅仅需要拓展这个类,并且提供ListIterator和size方法。 
4.对于不可修改的List,编程人员只需要实现Iterator的hasNext、next和hasPrevious、previous和index方法 
5.对于可修改的List还需要额外实现Iterator的的set的方法 
6.对于大小可变的List,还需要额外的实现Iterator的remove和add方法

三 看下Queue 和 Deque 接口

      <<<<<<<<<<<<<<<<<<<<<<<Queue接口       

参考文章:https://blog.csdn.net/qq591009234/article/details/89330492

boolean offer(E e);  插入元素到队列尾部


//区别  : 队列为空时,remove会抛出NoSuchElementException异常,但是poll 返回 null

E remove();          删除头部元素  


E poll();            删除头部元素
  


//区别  : 队列为空时,element会抛出NoSuchElementException异常,但是peek返回 null

E element();         仅仅得到头部元素

E peek();            仅仅得到头部元素

 

  <<<<<<<<<<<<<<<<Deque接口

这是一个双向队列,和上面的的区别就是 可以操作头和尾。方法都是成对出现的。

参考文章:https://blog.csdn.net/qq_29951983/article/details/82116796

boolean removeLastOccurrence(Object o)从此deque中删除指定元素的最后一次出现。 如果deque不包含元素,则它不变。 更正式地,删除最后一个元素e ,使(o==null ? e==null : o.equals(e)) (如果这样的元素存在)。 如果此deque包含指定的元素(或等效地,如果此deque由于调用而更改),则返回true 。 
参数 
o - 要从此deque移除的元素(如果存在) 
结果 
true如果一个元素被删除作为这个调用的结果 
异常 
ClassCastException - 如果指定元素的类与此deque不兼容( optional ) 
NullPointerException - 如果指定的元素为空,此deque不允许空元素( optional ) 
boolean removeLastOccurrence(Object o)从此deque中删除指定元素的最后一次出现。 如果deque不包含元素,则它不变。 更正式地,删除最后一个元素e ,使(o==null ? e==null : o.equals(e)) (如果这样的元素存在)。 如果此deque包含指定的元素(或等效地,如果此deque由于调用而更改),则返回true 。 
参数 
o - 要从此deque移除的元素(如果存在) 
结果 
true如果一个元素被删除作为这个调用的结果 
异常 
ClassCastException - 如果指定元素的类与此deque不兼容( optional ) 
NullPointerException - 如果指定的元素为空,此deque不允许空元素( optional )

 

void push(E e); 等价于addFirst

E pop();        等价于removeFirst

E getFirst();   等价于peekFirst

E getLast();    等价于peekLast

//元素在迭代器中将以从尾部到头部的顺序返回
Iterator<E> descendingIterator();

 

 

四 LinkedList内部结构

五 LinkedList变量,数据结构

元素大小
transient int size = 0;

    /**
        
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
transient Node<E> first; //指向第一个节点的指针

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
transient Node<E> last; //指向最后一个节点的指针

//Node的类结构
private static class Node<E> {
        E item;  //元素
        Node<E> next;  //指向下一个节点的指针
        Node<E> prev;  //指向前一个节点的指针

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

六 LinkedList方法

/**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

传入一个新的元素作为头部
    private void linkFirst(E e) {
        final Node<E> f = first;  //f指向头
        final Node<E> newNode = new Node<>(null, e, f); //创建新的头结点
        first = newNode;  //将first头结点指向新创建的节点
        if (f == null)    //如果原来的头节点是null,则将头指针和尾指针都指向新节点
            last = newNode;
        else              //不是NUll的话,则将原来的头指针的所指向的头结点中的prev指向新的头结点
            f.prev = newNode;
        size++;  //数量++
        modCount++; //修改量++
    }

传入一个新的元素作为尾部 ,原理同上
void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

在指定succ节点前插入元素e
void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev; //记下要插入元素的前一个元素指针
        final Node<E> newNode = new Node<>(pred, e, succ); //创建新节点,向前指向上面记下的插入元素前一个元素,向后指向插入元素的指针
        succ.prev = newNode;//将succ节点的前置指针指向插入的节点
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

//删除头部节点
private E unlinkFirst(Node<E> f) { 
        // assert f == first && f != null;
        final E element = f.item;  //得到头部元素
        final Node<E> next = f.next; 得到头部节点后一个节点指针
        f.item = null; //将头部元素置空
        f.next = null; // help GC 回收
        first = next; //将first指针指向next
        if (next == null) //空链表
            last = null;
        else //将头结点的前置指针置空
            next.prev = null;
        size--;
        modCount++;
        return element; //返回删除的元素
    }

private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; // help GC
        last = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }


E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) { //前置节点为空,则将first指向删除节点的后一个节点
            first = next;
        } else {            //前置节点不为空,将前置节点的后置指针指向next,将被删除的节点的前置指针置空
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {//后置节点为空,则将last指向删除节点的前一个节点
            last = prev;
        } else {           //后置节点不为空,将后置节点的前置指针指向prev节点,将被删除的节点的后置指针值为空
            next.prev = prev;
            x.next = null;
        }

        x.item = null; //将被删除节点的元素置位空
        size--;
        modCount++;
        return element; //返回被删除的元素 
    }

//判断索引参数是不是正常的索引值
private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
//判断对于迭代器来说参数是不是正确的索引值
private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

//通过索引返回整个节点
Node<E> node(int index) {
        // assert isElementIndex(index);

        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;
        }
    }

private LinkedList<E> superClone() {
        try {
            return (LinkedList<E>) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

 

总结 :应用文章:https://blog.csdn.net/weixin_42468526/article/details/81178698

Arraylist:底层是基于动态数组,根据下表随机访问数组元素的效率高,向数组尾部添加元素的效率高;但是,删除数组中的数据以及向数组中间添加数据效率低,因为需要移动数组。例如最坏的情况是删除第一个数组元素,则需要将第2至第n个数组元素各向前移动一位。而之所以称为动态数组,是因为Arraylist在数组元素超过其容量大,Arraylist可以进行扩容(针对JDK1.8  数组扩容后的容量是扩容前的1.5倍),Arraylist源码中最大的数组容量是Integer.MAX_VALUE-8,对于空出的8位,目前解释是 :①存储Headerwords;②避免一些机器内存溢出,减少出错几率,所以少分配③最大还是能支持到Integer.MAX_VALUE(当Integer.MAX_VALUE-8依旧无法满足需求时)。

可以看到,只要ArrayList的当前容足够大,add()操作向数组的尾部的效率非常高的,当向数组指定位置添加数据时,会进行大量的数组移动复制操作。而数组复制时,最终将调用System.arraycopy()方法,因此add()操作的效率还是相当高的。尽管这样当向指定位置添加数据时也还是比Linkedlist慢,后者添加数据只需要改变指针指向即可。Arraylist删除数组也需要移动数组,效率较慢。
Linkedlist基于链表的动态数组,数据添加删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历。

1.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
2、各自效率问题
    ArrayList是线性表(数组)
    get()直接读取第几个下标,复杂度0(1)
    add(E)添加元素,直接在后面添加,复杂度O( 1 )
    add(index, E)添加元素,在第几个元素后面插入,后面的元素需要向后移动,复杂度0(n)
    remove( )删除元素,后面的元素需要逐个移动,复杂度0(n)
    LinkedList是链表的操作
    get()获取第几个元素,依次遍历,复杂度0(n)
    add(E)添加到末尾,复杂度0(1)
    add(index, E)添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度0(n)
    remove ( )删除元素,直接指针指向操作,复杂度0(1)
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值