LinkedList源码详解

LinkedList源码详解

上文对ArrayList进行详情介绍,不少小伙伴表示学到了学到了。这次对LinikedList 源码得介绍,也希望大家会喜欢。
先提出几个面试问题,看看大家都答得怎么样?

1. 浅谈ArrayList 和 LinkedList 得区别?

2. LinkedList 底层数据结构是什么?加入你回答出来了,是循环双向链表吗?你又回答出来了,为什么不选择循环双向链表?

3. 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;

LinkedList 重要得属性就这三个,size 表示链表长度(大小),first,last 头尾节点。那first,last具体是什么呢?

/**
* linkedList 内部类 
* Node 是链表数据结构,更准确的说,是双向链表。它不仅有 item 值,next 下一个节点,它
* 还有一个prev 表示上一个节点的属性。这样就是它前后都可以遍历
*/
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 的构造方法比ArrayList 简单
初始化方式:

/***
* 我们常用的就是一个无惨构造方法
*/
public LinkedList() {
}

三、核心add方法

整体流程图:
LinkedList源码详解_第1张图片

/**
* 字面意思可以看出:尾插法
**/
public boolean add(E e) {
    linkLast(e);
    return true;
}




void linkLast(E e) {
    final Node<E> l = last;
    // 实例化新Node
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    // 判断此前是否有节点
    if (l == null)
        first = newNode;
    else
        // 设置上一个节点的next
        l.next = newNode;
    // 大小加1
    size++;
    modCount++;
}

从add()方法可得知,linkedList 是尾插法

对于linkedList 来说,不存在扩容一说,每次新增节点,只需要指定当前节点的next属性以及新节点的prev属性,就可实现新增。

四、核心get方法

get 方法相对于add方法是比较耗时,这也是ArrayList 和 LinkedList 的区别之一。
ArrayList 可以快速定位,而LinkedList 不可以。主要原因是两个底层的数据结构性质不同,一个是数组(天生的快速定位),一个链表(快速修改)。
不过LinikedList 也针对查找进行了优化,当index (下标)小于size一半的时候,从头first 开始搜索,否则从尾last。

public E get(int index) {
    // 下标检查(你不可以是小于0的吧,你也不可以大于size吧)
    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;
    }
}

五、核心remove 方法

1. remove() 从头删

public E remove() {
    return removeFirst();
}


/**
* 移除头结点
*/
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}


/**
 * 删除头节点
**/
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    // 帮助GC (交给GC了)
    f.item = null;
    f.next = null; // help GC
    first = next;
    // 如果没有next节点,说明空了 last为null
    if (next == null)
        last = null;
    else
        // 不空 说明 ,next节点要作为头节点了
        next.prev = null;
    size--;
    modCount++;
    return element;
}

2. remove(int i) 删除指定下标

LinkedList 的 remove(下标) 方法,是根据下标找到 node,根据node进行删除remove(node)
而ArrayList 的remove(Object o ) 是先定位下标,再remove(下标)直接删除

public E remove(int index) {
    checkElementIndex(index);
    // 根据下标找到 node 再删除node
    return unlink(node(index));
}

/**
 * 对于列表来说删除的逻辑很简单
 * 将前一个节点(假如存在)的next 指定 后一个节点
 * 将后一个节点(假如存在)的prev 指定 前一个节点
**/
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 = next;
    } else {
        // 前节点存在,前节点的next 为 后节点
        prev.next = next;
        x.prev = null;
    }
       
    if (next == null) {
        // 尾节点不存在,当前节点就是尾节点,此时将前节点设置为尾节点
        last = prev;
    } else {
        // 尾节点存在 尾节点的 prev 为 前节点
        next.prev = prev;
        x.next = null;
    }
       
    x.item = null;
    size--;
    modCount++;
    return element;
}

3. remove(Obejct o) 删除指定对象

/**
* 核心还是 unlink方法
**/
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;
}

注意:remove(Obejct o) 只会删除一个。

六、 contains() 方法

判断是否包含指定元素,主要是遍历链表,对一个元素一个元素进行判断。

public boolean contains(Object o) {
    return indexOf(o) != -1;
}



public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

#七、总结
这样看来,LinkedList 也不是想象中的那么复杂。如果有时间的话了,下一篇就是HashMap吧。

你可能感兴趣的:(java,链表,java,数据结构)