java集合之LinkedList

目录

LinkedList概述

简介

类图

LinkedList数据结构

LinkedList核心源码解读(JDK1.8)

重要内部类

重要成员变量

构造方法

添加元素

删除元素

修改元素

查找

其他常用方法

遍历方式

通过 Iterator 逐个遍历

通过 for 循环遍历

通过增强 for 循环遍历

耗时

测试代码

总结


LinkedList概述

简介

我们知道 ArrayList 是基于数组实现的,对于查找的效率极高,但是对于删除效率(涉及到元素的移动)就比较低了。而 LikedList 是基于双向链表实现的,所以和ArrayList 正好相反,对于查找效率相对较低,但是删除效率比 ArrayList 快了很多(不涉及到元素的移动)。LinkedList是非同步的。

类图

图片出处:https://www.cnblogs.com/skywang12345/p/3308807.html

LinkedList数据结构

LinkedList底层数据结构是 双向链表 实现。双向链表就是由一个一个节点(node)通过指针(引用)连接起来的一种链式结构。如下图

图中每一个节点都包含 prev、data、next 三部分,通过prev 、next 和其它节点关联起来,组成了一种链式的结构。注意,双向链表还会包含一个头指针和一个尾指针,目的在于方便遍历链表中的数据,如想要在头部插入一条记录就可以以头指针为参考。

LinkedList核心源码解读(JDK1.8)

重要内部类

//存储元素的节点
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;
        }
}

重要成员变量

//元素个数
transient int size = 0;

//指向链表的第一个节点
transient Node<E> first;

//指向链表的最后一个节点
transient Node<E> last;

构造方法


    //创建一个空的LinkedList
    public LinkedList() {
    }

    //创建一个包含指定集合 c 中数据的 LinkedList,且元素顺序与 c 迭代的顺序一致
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

添加元素

//在头部添加一个元素(头插法)
public void addFirst(E e) {
        linkFirst(e);
}

private void linkFirst(E e) {
        //保存之前的节点
        final Node<E> f = first;
        //创建一个新节点,且 next 指向旧头结点
        final Node<E> newNode = new Node<>(null, e, f);
        //头指针前移(使新创建的节点成为头结点)
        first = newNode;
        if (f == null)
            //当前只有一个节点,该节点既是头结点,也是尾节点
            last = newNode;
        else
            //旧头结点的 prev 指针指向新节点
            f.prev = newNode;
        size++;
        modCount++;
}

//添加一个尾节点(尾插法)
public void addLast(E e) {
        linkLast(e);
}

void linkLast(E e) {
        //保存旧尾节点
        final Node<E> l = last;
        //创建新节点,且 pre 指向就尾节点
        final Node<E> newNode = new Node<>(l, e, null);
        //尾指针后移(使新创建的节点成为新的尾节点)
        last = newNode;
        if (l == null)
            //当前只有一个节点,该节点既是头结点,也是尾节点
            first = newNode;
        else
            //旧尾节点 next 指向新尾节点
            l.next = newNode;
        size++;
        modCount++;
}

删除元素

//删除指定元素 o
public boolean remove(Object o) {
        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;
}

//删除指定的节点 x 
E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        //保存 x 的 next 节点
        final Node<E> next = x.next;
        //保存 x 的 prev 节点
        final Node<E> prev = x.prev;

        if (prev == null) {
            //如果 pre 节点为空,表示 x 是头结点,所有头指针后移
            first = next;
        } else {
            // x 节点的 pre 节点指向 x 节点的 next 节点(相当于绕过 x 节点)
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            //如果 next 节点为空,表示 x 是尾结点,所以尾指针前移
            last = prev;
        } else {
            // x 节点的 next 节点指向 x 节点的 prev 节点(相当于绕过 x 节点)
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
}

修改元素

//修改指定 index 处的元素
public E set(int index, E element) {
        // 检查是否满足 (0 <= index < size)
        checkElementIndex(index);
        
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
}

//获取指定 index 处的节点
Node<E> node(int index) {
        // assert isElementIndex(index);
        // 若 index 是小于 size/2,则从头节点开始找;反之,则从尾节点开始找
        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;
        }
}

查找

//获取指定 index 处额元素
public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
}

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

这里查找操作用了一小技巧,先判断 index 是否大与 size/2,再以此来决定是从头还是从尾开始查找。

其他常用方法

//获取头节点,但不删除
public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
}

//获取头结点,且要删除
public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
}

//获取节点,且要删除
public E pop() {
        return removeFirst();
}

//插入一个头结点
public void push(E e) {
        addFirst(e);
}

//返回true:成功移除指定元素
public boolean removeFirstOccurrence(Object o) {
        return remove(o);
}

由于 jdk 版本迭代的原因,导致 LinkedList 里面有比较多功能相同的方法,不要太在意,选一个名字更漂亮的用就 ok 了。

遍历方式

通过 Iterator 逐个遍历

    Iterator<Integer> l = linkList.iterator();
		while(l.hasNext()){
			t=l.next();
    }

通过 for 循环遍历

    for (int i = 0; i < num; i++) {
        t = linkList.get(i);
    }

通过增强 for 循环遍历

    for (Integer tem : linkList) {
        t = tem;
    }

耗时

第一轮:iterator consume: 5,for consume: 11904,增强for consume: 3

第二轮:iterator consume: 10,for consume: 11089,增强for consume: 4

第三轮:iterator consume: 6,for consume: 12094,增强for consume: 33

从上面的结果可知,iterator方式和挣钱for的方式平均下来效率是差不多的,所以选哪一种都可行。但是 for 循环是一如既往的慢,所以遍历LinkedList,一定不要用 for 循环的方式

测试代码

public class LinkedListTest {

	public static void main(String[] args) {
		int t = 0;
		LinkedList<Integer> linkList = new LinkedList<Integer>();
		int num = 100000;
		for (int i = 0; i < num; i++) {
			linkList.add(i);
		}
		long startTime =0;
		startTime = System.currentTimeMillis();
		Iterator<Integer> l = linkList.iterator();
		while(l.hasNext()){
			t=l.next();
		}
		System.out.println("iterator consume: " + (System.currentTimeMillis() -   startTime));

		
		startTime = System.currentTimeMillis();
		for (int i = 0; i < num; i++) {
			t = linkList.get(i);
		}
		System.out.println("for consume: " + (System.currentTimeMillis() - startTime));
		
		startTime = System.currentTimeMillis();
		for (Integer tem : linkList) {
			t = tem;
		}
		System.out.println("增强for consume: " + (System.currentTimeMillis() - startTime));
	}

}

总结

LinkedList 具备以下特点:删除效率高(不涉及元素移动)、查找效率低(每次都需要遍历链表)、不是线程安全的、没有固定大小的容量。如果需要保证线程安全,可以通过下面实现

List list = Collections.synchronizedList(new LinkedList(...));

Thanks:

http://www.cnblogs.com/skywang12345/p/3308556.html

https://blog.csdn.net/u011240877/article/details/52876543

猜你喜欢

转载自blog.csdn.net/yhs1296997148/article/details/83627195