LinkedList

LinkedList

Author : lss 路漫漫其修远兮,不止于代码

简介

​ LinkedList 是是由双线链表构成 ( 这里是围绕 JDK1.8开展 )。同时实现了 List 和 Deque 接口, 也就是说它既是一个顺序容器也是一个队列(queue), 与此同时也可以看成是一个栈 (Stack)。 虽然说它也可以当作队列 和 栈,但是并不是我们首先要考虑从的。在队列和栈的结构上应该优先考虑 ArrayDeque 。它有着比 LinkedList 有好的性能。 这里不做详细比较。

​ 本次只对 构造方法,删除,插入,获取做详细介绍,关于 LinkedList 的 队列(queue) 和 栈 (Stack) 下期再做解析。

构造方法

LinkedList ()

//  无参构造中没有做任何事情  
public LinkedList() {
 
}

​ 再看这段源码的时候,请分别一个东西 前指针(前驱节点),后指针 (后驱节点) 头节点 , 尾节点。

在这里插入图片描述

LinkedList(Collection<? extends E> c)

  public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

 public boolean addAll(int index, Collection<? extends E> c) {
     	// 校验下标
        checkPositionIndex(index);
		// 将参数集合转化为数组
        Object[] a = c.toArray();
     	// 获取数组长度
        int numNew = a.length;
        if (numNew == 0)
            return false;
		// pred 为 succ 的前驱节点
        Node<E> pred, succ;
     	// 没有数据的情况下
        if (index == size) {
            succ = null;
            // pred 为尾节点
            pred = last;
        } else {
            // 通过下标寻找节点 赋值给 succ
            succ = node(index);
            // 将 succ的前驱节点改为 pred
            pred = succ.prev;
        }
		// 遍历数组
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            // 将值 包装为 Node  
            Node<E> newNode = new Node<>(pred, e, null);
            // == null 代表为 first 节点 (头节点)
            if (pred == null)
                // 头节点为当前插入的值
                first = newNode;
            else
                // 不是 first 头节点 将 尾指针指向当前插入的值
                pred.next = newNode;
            // 前驱为 当前插入的值
            pred = newNode;
        }
		// 因succ是pred的后指针 如果 succ为 null 则代表是尾节点
        if (succ == null) {
            // 尾节点 指向 pred
            last = pred;
        } else {
            // 不是尾节点  只需要保证 pred 是 succ 的前指针即可
            pred.next = succ;
            succ.prev = pred;
        }
		// size = 当前集合长度 + 新添加集合长度
        size += numNew;
     	// 添加记录
        modCount++;
        return true;
    }

添加方法

add(E e)

void linkLast(E e) {
    	// 将 l 设置为 尾节点
        final Node<E> l = last;
    	// 将插入的值 包装为 Node 
        final Node<E> newNode = new Node<>(l, e, null);
    	// 将尾节点 修改为 当前插入的节点
        last = newNode;
    	// 为 null 表示还没有数据插入 
        if (l == null)
            //将头节点改为 当前插入的节点
            first = newNode;
        else
            //有数据存在   将之前的尾节点的后指针改为 当前节点。
            l.next = newNode;
   		// 长度 + 1
        size++;
        modCount++;
  }

add(int index, E element)

在这里插入图片描述

友情提示 : 这副图着实是有点太丑了, 不过将就的看一下吧 。 这是根据下标插入元素时, 各个节点指针的改动。请先看这块指针指向的改变。在往下看源码,不然你可能会被绕晕在源码里。

 public void add(int index, E element) {
     	// 校验下标
        checkPositionIndex(index);
		// 下标 == 集合长度 代表从尾节点正常插入   也就是继续顺序插入
        if (index == size)
            // 这个方法在上面写过, 不在展开介绍
            linkLast(element);
        else
            // 在中间进行插入   
            // node( index ) 是根据下标找对应的节点  寻找过程在后续
            linkBefore(element, node(index));
    }

	/**
	  * succ 为根据下标 取出的节点。 这里是将他的前驱节点 换成 要插入的节点,将要插入的节点前驱节点
	  * 设为 succ 的前驱节点, 后驱节点为 succ。 将 succ的前驱节点的后驱节点设置为 当前插入的节点
	  */
 void linkBefore(E e, Node<E> succ) {
        // assert succ != null;    assert 是一个断言操作。  关于断言主要用户测试,这里不做详细介绍。
     	// 拿到该节点的前驱节点
        final Node<E> pred = succ.prev;
     	// 将插入的值包装成 Node
        final Node<E> newNode = new Node<>(pred, e, succ);
     	// 将当前节点的 前驱节点 改为 当前插入的节点
        succ.prev = newNode;
     	// 若最初拿到的 前驱节点去 null 则代表他为 头节点
        if (pred == null)
            first = newNode;
        else
            // 将最初节点的 后驱节点改为当前插入的节点。
            pred.next = newNode;
        size++;
        modCount++;
    }

	
	// 这块看下 node(inedx) 的源码 他是如何快速的找到该 Node 节点
	
 Node<E> node(int index) {
        // assert isElementIndex(index);
     
     	// 如果这个下标的位置在前半部分
        if (index < (size >> 1)) {
            // 从 first 头节点开始去寻找
            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(int index)

public E remove(int index) {
        checkElementIndex(index);
    	// node(index) 上面已经解释过了这里不在多说
        return unlink(node(index));
    }


 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;
		// 若前驱节点为 null  代表该节点为头节点
        if (prev == null) {
            // 头节点设置为 当前节点的后驱节点
            first = next;
        } else {
            // 将当前节点前驱节点的后驱节点 设置为 当前节点的后驱节点
            prev.next = next;
            // 将当前节点的前驱节点 置 null
            x.prev = null;
        }
		// 若当前节点的后驱节点 为 null 代表为尾节点
        if (next == null) {
            //将当前节点的前驱节点设置为  尾节点
            last = prev;
        } else {
           	// 将当前节点的后驱节点的前驱节点 设置为 当前节点前驱节点
            next.prev = prev;
            // 将当前节点的后驱节点 置 null
            x.next = null;
        }
		// 将当前节点 元素 置null 上期已经说过 这里是为了明确告诉 GC 
        x.item = null;
     	// 改变集合长度
        size--;
        modCount++;
        return element;
    }

remove(Object o)

public boolean remove(Object o) {
        if (o == null) {
            // 删除集合中的 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;
    }

获取方法 get

 public E get(int index) {
     	// 这两个方法以上都有详细解释
        checkElementIndex(index);
        return node(index).item;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值