Java集合之LinkedList

目录

基本介绍 

常用方法

源码解析

1. LinkedList的底层结构,Node双向链表

2. LinkedList的几个内部变量

3. getFirst()

4. removeFirst()

5. addFirst(E e)

6. contains(Object o)

7. add(E e)

8. remove(Object o)

9. addAll(int index, Collection c)

10. get(int index)

11. spliterator()

总结


基本介绍 

LinkedList是实现了List和Deque接口的双向链表,实现了所有可选列表的操作,允许存放所有元素(包括null)

LinkedList是非线程安全的,因此在多线程的情况下,需要加同步锁来保证线程安全,或者使用Collections.synchronizedList 

常用方法

public LinkedList()
public LinkedList(Collection<? extends E> c)
public E getFirst()  获取第一个元素
public E getLast()  获取最后一个元素
public boolean add(E e)  添加一个元素
public void add(int index, E element)  在指定位置插入元素
public void addFirst(E e)  在集合前部增加一个元素
public void addLast(E e)  在集合尾部增加一个元素
public boolean addAll(Collection<? extends E> c)  把另一个集合全部添加到当前集合
public boolean addAll(int index, Collection<? extends E> c) 在指定位置把另一个集合全部添加到当前集合
public boolean contains(Object o)  判断是否包含
public int size()  返回集合大小
public boolean remove(Object o)  根据指定对象删除元素
public E remove(int index)  根据指定索引删除元素
public E removeFirst()  删除第一个元素
public E removeLast()  删除最后一个元素
public E get(int index)  根据索引获取元素
public E set(int index, E element)  在指定索引处添加一个元素(覆盖原数据)
public int indexOf(Object o)  返回指定元素索引
public int lastIndexOf(Object o)  返回指定元素出现的最后一个索引

源码解析

1. LinkedList的底层结构,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;
    }
}

2. LinkedList的几个内部变量

transient int size = 0;

// 标记链表第一个元素
transient Node<E> first;

// 标记链表最后一个元素
transient Node<E> last;

// 链表结构修改的次数
protected transient int modCount = 0;

3. getFirst()

public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

这个方法比较简单,其实就是取链表的first节点,getLast()方法类似,不再赘述

4. removeFirst()

public E removeFirst() {
    // 拿到first节点
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    // 把需要删除的节点的值置为空,并且把其引用也置为空
    f.item = null;
    f.next = null; // help GC
    // 把next节点置为first节点
    first = next;
    // 如果next节点为空,说明整个链表为空
    if (next == null)
        // 则把last节点也置为空
        last = null;
    else
        // 把next节点的前置引用置为空,因为prev节点已经删除,next节点已经为头节点
        // 此时原来的first节点的引用已经全部置空,下一次GC时会把这个节点回收
        next.prev = null;

    // 容器的size - 1
    size--;
    modCount++;
    return element;
}
removceLast()方法类似,只是反过来将最后一个元素置空。

5. addFirst(E e)

public void addFirst(E e) {
    linkFirst(e);
}

private void linkFirst(E e) {
    final Node<E> f = first;
    // 实例化一个Node节点,设置前置节点为null,原来的first节点置为当前节点的next节点
    final Node<E> newNode = new Node<>(null, e, f);

    // 把链表的first节点置为当前节点
    first = newNode;
    // 如果当前链表为空
    if (f == null)
        // 则将last节点也置为新实例化的节点
        last = newNode;
    else
        // 否则,将原有的first节点的前置节点置为当前节点
        f.prev = newNode;
    size++;
    modCount++;
}

addLast() 也类似,不再详细说明了

6. contains(Object o)

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

7. add(E e)

public boolean add(E e) {
    // 将元素添加到链表的最后一位
    linkLast(e);
    return true;
}

void linkLast(E e) {
    final Node<E> l = last;
    // 实例化一个Node节点,将原来的last节点置为当前节点的前置节点,后置节点置为null
    final Node<E> newNode = new Node<>(l, e, null);
    // 将last节点置为当前节点
    last = newNode;
    // 如果原先的last节点为空,说明之前链表为空
    if (l == null)
        // 将first节点置为当前节点
        first = newNode;
    else
        // 否则将之前last节点的next节点置为当前节点
        l.next = newNode;
    size++;
    modCount++;
}

8. remove(Object o)

public boolean remove(Object o) {
    // 跟其他方法很类似,先判断元素是否为null
    if (o == null) {
        // 循环找到对应元素,并执行unlink方法
        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;
}


E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    
    // 如果前置节点为空,说明当前待删除节点是头节点
    if (prev == null) {
        // 把first节点置为当前节点的next节点
        first = next;
    } else {
        // 否则就把当前节点的前置节点的next节点置为当前节点的next节点
        prev.next = next;
        // 把待删除节点的前置节点置为null
        x.prev = null;
    }

    // 如果待删除节点的next节点为null,说明当前节点是最后一个节点
    if (next == null) {
        // 将当前链表的last节点置为待删除节点的前置节点
        last = prev;
    } else {
        // 把当前节点的next节点的前置节点置为当前节点的前置节点
        next.prev = prev;
        // 当前节点的next节点置为null
        // 此时待删除节点的引用关系已经全部置空
        x.next = null;
    }
    // 把当前节点的数据置为null
    x.item = null;
    size--;
    modCount++;
    return element;
}

9. addAll(int index, Collection<? extends E> c)

/**
 * 在索引位置插入新的集合
 *
 * @Param index 索引
 * @Param c 需要添加的集合
 */
public boolean addAll(int index, Collection<? extends E> c) {
    // 校验index是否超出范围
    checkPositionIndex(index);

    // 将集合转成数组
    Object[] a = c.toArray();
    int numNew = a.length;

    // 如果需要添加的集合为空,直接return false
    if (numNew == 0)
        return false;

    Node<E> pred, succ;

    // 如果索引位置等于当前集合的size,说明要将新的集合添加在链表的尾部
    if (index == size) {
        succ = null;
        pred = last;
    } else {
        // 把当前索引的元素赋值给succ
        succ = node(index);
        // 当前索引的前置节点赋值给pred
        pred = succ.prev;
    }

    // 循环数组
    for (Object o : a) {
        Node<E> newNode = new Node<>(pred, e, null);
        // 如果pred为null,则将新node对象赋值给first
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        // 把pred置为当前元素,继续添加下一个元素
        pred = newNode;
    }
    
    // 如果是在队尾添加元素
    if (succ == null) {
        // 则将链表的last节点置为pred,因为上面在循环末尾会将pred = newNode,此时pred就是最后一个元素
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;
    modCount++;
    return true;
}

private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        // 如果超出范围就抛出IndexOutOfBoundsException
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

10. get(int index)

public E get(int index) {
    // 校验索引是否越界,如果越界则抛异常
    checkElementIndex(index);
    // 返回当前节点的元素
    return node(index).item;
}

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

private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

Node<E> node(int index) {
    // 判断索引是靠近前半部分还是后半部分
    if (index < (size >> 1)) {
        // 如果索引靠前,则从头开始遍历,直到当前索引
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        // 然后返回当前索引对应的node
        return x;
    } else {
        Node<E> x = last;
        // 如果索引靠后,则从末尾开始向前遍历
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

11. spliterator()

LinkedList中还有spliterator拆分器,该拆分器是后期绑定的,并且使用与链接列表相同的元素快速失效。后期绑定拆分器绑定到元素源意味着在第一次遍历、第一次拆分或第一次查询估计大小时链接列表,而不是在创建拆分器时。它可以和 Java 8 中的 Streams 一起使用。此外,它还可以单独和批量遍历元素。Spliterator 是遍历元素的更好方法,因为它对元素提供了更多的控制。

这个拆分器感觉在我平时的工作中用的也不多,就不做解析了,如果大家想看看这个方法的解析的话给我留言,我再单独写一篇这个的解析。

总结

1. LinkedList底层是由双向链表实现的

2. LinkedList插入和删除比较快,因为正常插入都是插入链表的最后一个元素,只需要改变前置节点的next指向和当前节点的前置指向即可,不需要想ArrayList一样移动数组

3. LinkedList如果按照索引查询会很慢,因为他需要从头或从尾开始逐个遍历直到对应索引位置,不支持随机访问

4. LinkedList是非线程安全的

5. LinkedList元素允许为null,允许重复元素

6. LinkedList是基于双向链表实现的,所以不存在容量不足的情况,不需要扩容

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑着猪猪去旅行A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值