Java集合源码学习(3)LinkedList

栏目: 数据库 · 发布时间: 6年前

内容简介:ArrayList,数组是顺序存储结构,存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1),数组的特点是寻址容易,插入和删除困难。 LinkedList使用链表作为存储结构,链表是线性存储结构,在内存上不是连续的一段空间,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N),链表的特点是寻址困难,插入和删除容易。在JDK1.7之前,LinkedList是采用双向环形链表来实现的,在1.7及之后,Oracle将LinkedList做了优化,将环形链表改成了

ArrayList,数组是顺序存储结构,存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1),数组的特点是寻址容易,插入和删除困难。 LinkedList使用链表作为存储结构,链表是线性存储结构,在内存上不是连续的一段空间,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N),链表的特点是寻址困难,插入和删除容易。

在JDK1.7之前,LinkedList是采用双向环形链表来实现的,在1.7及之后,Oracle将LinkedList做了优化,将环形链表改成了线性链表。

public class LinkedList<E>
    extends AbstractSequentialList<E>
     implements List<E>, Deque<E>, Cloneable, java.io.Serializable
复制代码

LinkedList继承了AbstractSequentialList,实现了List,Deque,Cloneable,Serializable 接口

(1)继承和实现

继承AbstractSequentialList类,提供了相关的添加、删除、修改、遍历等功能。

实现List接口,提供了相关的添加、删除、修改、遍历等功能。 实现 Deque 接口,即能将LinkedList当作双端队列使用,可以用做队列或者栈。 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。 实现java.io.Serializable接口,LinkedList支持序列化,能通过序列化传输。

(2)线程安全

LinkedList是非同步的,即线程不安全,如果有多个线程同时访问LinkedList,可能会抛出ConcurrentModificationException异常。

final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }
复制代码

(3)节点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;
    }
}
复制代码

(4)addAll方法:

//addAll ,在尾部批量增加
复制代码

public boolean addAll(Collection<? extends E> c) { return addAll(size, c);//以size为插入下标,插入集合c中所有元素 } //以index为插入下标,插入集合c中所有元素 public boolean addAll(int index, Collection<? extends E> c) { checkPositionIndex(index);//检查越界 [0,size] 闭区间

Object[] a = c.toArray();//拿到目标集合数组
int numNew = a.length;//新增元素的数量
if (numNew == 0)//如果新增元素数量为0,则不增加,并返回false
    return false;

Node<E> pred, succ;  //index节点的前置节点,后置节点
if (index == size) { //在链表尾部追加数据
    succ = null;  //size节点(队尾)的后置节点一定是null
    pred = last;//前置节点是队尾
} else {
    succ = node(index);//取出index节点,作为后置节点
    pred = succ.prev; //前置节点是,index节点的前一个节点
}
//链表批量增加,是靠for循环遍历原数组,依次执行插入节点操作。对比ArrayList是通过System.arraycopy完成批量增加的
for (Object o : a) {//遍历要添加的节点。
    @SuppressWarnings("unchecked") E e = (E) o;
    Node<E> newNode = new Node<>(pred, e, null);//以前置节点 和 元素值e,构建new一个新节点,
    if (pred == null) //如果前置节点是空,说明是头结点
        first = newNode;
    else//否则 前置节点的后置节点设置问新节点
        pred.next = newNode;
    pred = newNode;//步进,当前的节点为前置节点了,为下次添加节点做准备
}

if (succ == null) {//循环结束后,判断,如果后置节点是null。 说明此时是在队尾append的。
    last = pred; //则设置尾节点
} else {
    pred.next = succ; // 否则是在队中插入的节点 ,更新前置节点 后置节点
    succ.prev = pred; //更新后置节点的前置节点
}

size += numNew;  // 修改数量size
modCount++;  //修改modCount
return true;
}
//根据index 查询出Node,
Node<E> node(int index) {
    // assert isElementIndex(index);
//通过下标获取某个node 的时候,(增、查 ),会根据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 void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;  //插入时的检查,下标可以是size [0,size]
}
复制代码

在构造方法中调用addAll方法,相当于是向一个空链表中添加集合c中的元素。 如果是在已有元素的链表中调用addAll方法来添加元素的话,就需要判断指定的添加位置index是否越界,如果越界会抛出异常;如果没有越界,根据添加的位置index,断开链表中index位置的节点前后的引用,加入新元素,重新连上断开位置的前后节点的引用。过程如下图:(此处用了xiaoyanger的图)

Java集合源码学习(3)LinkedList

(5)toArray()方法:

public Object[] toArray() {
    //new 一个新数组 然后遍历链表,将每个元素存在数组里,返回
    Object[] result = new Object[size];
    int i = 0;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    return result;
    }
复制代码

这个方法的实现很简单。

(6)总结:

LinkedList 是双向列表。

链表批量增加,是靠for循环遍历原数组,依次执行插入节点操作。对比ArrayList是通过System.arraycopy完成批量增加的。增加一定会修改modCount。 通过下标获取某个node 的时候,(add select),会根据index处于前半段还是后半段 进行一个折半,以提升查询效率 删也一定会修改modCount。 按下标删,也是先根据index找到Node,然后去链表上unlink掉这个Node。 按元素删,会先去遍历链表寻找是否有该Node,如果有,去链表上unlink掉这个Node。 改也是先根据index找到Node,然后替换值。改不修改modCount。 查本身就是根据index找到Node。 所以它的CRUD(Create,Retrieve,Update,Delete)操作里,都涉及到根据index去找到Node的操作。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

无懈可击的Web设计

无懈可击的Web设计

西德霍姆 / 刘建宁 / 清华大学出版社 / 2009-4 / 59.90元

一个网站,无论视觉上多么美观,内容多么丰富,如果不能面向最广泛的用户群,那它就不算是真正成功的网站。《无懈可击的Web设计:利用XHTML和CSS提高网站的灵活性与适应性》是Web标准设计领域的公认专家Dan Cederholm的倾力之作,向您描述了基于Web标准的设计策略,以适应各种各样的用户浏览方式。书中每一章的开头都给出了一个基于传统HTML技术的实例,然后对它进行重构,指出它的局限性,并利......一起来看看 《无懈可击的Web设计》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码