java Linkedlist

Linkedlist底层原理

LinkedList的底层结构是基于双向链表实现的,优势是插入数据效率很高,缺点是遍历数据效率低。LinkedList没有长度限制,所以不需要提供初始化大小的构造方法。
功能方面:
1)查找方面先是在双向链表里找到节点的位置index,找到之后,再对这个节点进行一系列操作,双向链表在查找index位置的节点时,如果index<双向链表长度的1/2,那么就从前面往后面查找,反之,则从哪个后面往前面查找。
2)关于删除数据,双向链表只要先查找到指定位置的index,然后执行node.previous.next = node.next;node.next.previous = node.previous;node = null的操作,就可以将这个节点移出链表,不需要对其它的节点进行更改,所以删除数据的效率很高。
3)关于增加数据,和删除数据差不多,都是先查找指定位置的index,然后执行previous.next=node;node.previous=previous;next.previous=node;node.next=next;node=value;就可以将新节点插入链表
4)遍历数据效率很低,因为每次都要从头结点开始查找,因此,查找并且获取指定位置数据的速度比较慢

LinkedList的节点结构
源码如下:

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

添加元素的方式
Node是在LinkedList中定义的一个静态内部类,代表了链表中节点的基本结构,一个完整的Node节点包含数据域item,前驱指针prev,和后继指针next。
以下是插入表头节点的实现源码

private void linkFirst(E e) {
        final Node<E> f = first;
		//当前节点的前驱指为null,后继节点指向原来的头节点
        final Node<E> newNode = new Node<>(null, e, f);
		//让头指针指向当前插入的新头结点
        first = newNode;
		//如果原来有头节点,则更新原来节点的前驱指针,否则更新尾指针
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
}

add方法

public boolean add(E e) {
	//默认向链表尾部添加节点
     linkLast(e);
     return true;
}

在链表尾部添节点的原理类似
源码为:

void linkLast(E e) {
        final Node<E> l = last;
//当前节点的前驱指向尾节点,后继指向 null
        final Node<E> newNode = new Node<>(l, e, null);
		//尾节点指向新插入的节点
        last = newNode;
//如果原来有尾节点,则更新原来节点的后继指针,否则更新头指针
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
}

在指定index位置插入节点的方式

在指定节点前插入一个节点,将指定节点作为当前节点的后继,指定节点的前驱节点为当前节点的前驱,然后,修改指定节点的前驱为当前节点,以及指定节点的前驱节点的后继为当前节点。源码如下:

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;
		//修改指定节点的前驱节点的后继为当前节点
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

删除节点源码

public boolean remove(Object o) {
	//遍历列表,找到item == o 的节点,在调用unlink()方法删除
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
}

unlink方法

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

总结:
LinkedList,可以当做集合、队列和栈来使用,由于底层是双向链表,因此随机访问性比较差,插入和删除数据的效率特别高。如果需要经常的插入、删除操作可以考虑使用LinkedList集合。如果要遍历LinkedList的元素,应该采用迭代器来实现。LinkedList是线程不安全的集合,如果多个线程同时访问,那么可以使用Collections将集合包装成线程安全的集合。

posted @ 2020-08-21 22:44  TidalCoast  阅读(90)  评论(0编辑  收藏  举报