手写LinkedList集合

前言

上一篇介绍了ArrayList的一个源码,这遍我们来介绍在List下的另一个常用的类,LinkedList,和ArrayList总是用来比较,实现方式和ArrayList还有很多不同的。

构造器:

首先,我们应该知道两类集合的底层实现是不同的,ArrayList的底层结构是数组,所以需要在初始化的时候,去申请一块连续的内存空间,空间不够了还要去选择扩容,而LinkedList的底层结构是链表,也就导致了他无需连续的内存空间,只要有空间他就能插入元素,离散型空间分布。

public ExLinkedList() {}

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

链表不用在初始化的时候做别的事情,所以较为简单。

添加方法

LinkedList的三个关键的成员变量,分别是第一个节点的引用,最后一个节点的引用,和当前链表的实际长度。

    //第一个节点,用于查找或者遍历的功能,从第一个节点一直可next到需要的地方
    private Node<E> firstNode;
    //最后一个节点,用于添加元素,可以直接在最后面加入元素
    private Node<E> lastNode;
    //集合实际使用的大小
    private int size = 0;

同时我们需要创建一个私有静态成员类,Node类存储了上个节点、下个节点和当前的节点的数据内容。

private static class Node<E> {
        //该节点的上一个节点
        private Node<E> preNode;
        //该节点的下一个节点
        private Node<E> nextNode;
        //该节点的存储的数据
        private E item;

        public Node(Node<E> preNode, E item, Node<E> nextNode) {
            this.preNode = preNode;
            this.nextNode = nextNode;
            this.item = item;
        }
    }

add(E e):

//直接添加对象
public boolean add(E e) {
    //在最后一个节点后添加元素
    linkLast(e);
    return true;
}

LinkedList实现的双向链表的概念,即当前链表有上个节点的引用,也有下个链表的引用,添加的时候只需要将添加的链表,和当前最后一个链表进行关系,即可以实现添加

void linkLast(E e) {
    //获得当前最后一个节点
     final Node<E> oldLastNode = lastNode;
     //构造一个新节点:
     //   第一个参数:新节点的上一个节点为当前的最后一个节点
     //   第二个参数:存储的元素
     //   第三个参数:null
     final Node<E> newAddNode = new Node<>(oldLastNode, e, null);
     //将最后一个节点设置为当前新节点
     lastNode = newAddNode;
     if (firstNode == null) {
         //如果第一个节点还未设置值,则第一个节点为新节点
         firstNode = newAddNode;
     } else {
         //旧的最后一个节点的下一个节点设置为当前新节点
         oldLastNode.nextNode = newAddNode;
     }
 }

add(int index, E element):
这里我们要实现根据下标找到链表中对应节点的方法,链表的查找方法,就是需要第一个节点或最后一个节点,一个需要一直next,一个需要pred,来进行节点的一个查找,所以链表的查找效率是远远低于数组的,他需要遍历整个链表才能找到对应的节点,这里还用了二分法查询来优化查找的效率。

//根据下标获得值
    Node<E> node(int index) {
        // 判断该下标是在相对中间的左边还是在右边,二分法查询
        // 即是顺时针查更快查到,还是逆时针更快查到
        if (index < (size << 1)) {
            Node node = firstNode;
            for (int i = 0; i < index; i++) {
                //一直不断的遍历链表,获得你需要的值
                //如需要下标为0的节点,firstNode需要nextNode0次(就是本身)
                //  需要下标为1的节点,firstNode需要nextNode1次
                //  需要下标为2的节点,firstNode需要nextNode2次
                //  。。。。
                //  需要下标为N的节点,firstNode需要nextNodeN次
                node = node.nextNode;
            }
            //根据规律,循环结束返回的值就是需要的值
            return node;
        } else {
            Node node = lastNode;
            for (int i = size - 1; i > index; i--) {
                //倒序,获得上一个节点
                node = node.preNode;
            }
            return node;
        }
    }

指定下标处插入元素,这里我需要分为两种逻辑,一种就是常见的直接在链表尾部追加元素,一种就是在链表中间插入元素,这时候你需要查出当前下标的节点,然后得到当前节点的上一个节点,之后你要添加的新节点需要和这两个节点建立关系,完成中间插入,链表的中间添加操作,就无需像数组那样移动元素。

public void add(int index, E element) {
        //1.先检查传入参数的准确性
        checkPositionIndex(index);

        //2.根据下标值判断是在链表之后加元素,还是在之前添加元素
        if (size == index) {
            //直接在链表之后追加元素
            linkLast(element);
        } else {
            linkBefore(element, node(index));
        }
    }
private void linkBefore(E element, Node<E> cur) {
        Node pred = cur.preNode;

        Node newNode = new Node(pred, element, cur);
        cur.preNode = newNode;
        if (pred == null) {
            firstNode = newNode;
        } else {
            pred.nextNode = newNode;
        }

        size++;
        modCount++;
    }

addAll(int index, Collection c):
addAll(Collection c)方法就不详细介绍了,内部还是使用的addAll(int index, Collection c)方法,addAll(int index, Collection c)其实也是上个添加方法也非常相似,不断的建立新旧节点之间的关系,来完成添加。

public boolean addAll(int index, Collection c) {
        //1.先检查传入参数的准确性
        checkPositionIndex(index);

        Object[] objects = c.toArray();
        int numNew = objects.length;
        if (numNew == 0) {
            return false;
        }

        //2.获得该下标的节点,和该节点的上一个节点
        Node<E> pred, cur;
        if (index == size) {
            //如果是在集合最后追加节点
            pred = lastNode;
            cur = null;
        } else {
            //查出当前节点
            cur = node(index);
            pred = cur.preNode;
        }

        //3.将传入的集合的数据插入到数组中,并完成和pred的联系
        for (Object object : objects) {
            //构造一个新节点
            Node newNode = new Node(pred, object, null);
            //如果要插入的节点的上一个节点为空,说明是在头节点前面插入
            if (pred == null) {
                //头节点就是新添加的第一个节点
                firstNode = newNode;
            } else {
                pred.nextNode = newNode;
            }
            //此时需要更改上一个节点的值,即下一个新节点的上一个节点为当前插入的节点
            pred = newNode;
        }

        //4.已经完成插入的值和上个节点的连接关系,现在完成和cur的连接关系
        if (cur == null) {
            //如果cur节点为null,说明一直是在尾部追加,即最后一个节点为pred
            lastNode = pred;
        } else {
            pred.nextNode = cur;
            cur.preNode = pred;
        }

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

删除方法:

E remove(int index):
根据下标去删除节点的大概思路是,如果你删除的节点在中间部分的话,你需要将上一个节点和他的下一个节点建立关系,将需要删除的节点从终孤立出去,即该链表访问不到该节点就即可。

public E remove(int index) {
        //1.先检查传入参数的准确性
        checkPositionIndex(index);
        return unlink(node(index));
    }
private E unlink(Node<E> node) {
        //获得需要删除节点的数据
        E element = node.item;
        //获得需要删除节点的上一个节点
        Node pred = node.preNode;
        //获得需要删除节点的下一个节点
        Node next = node.nextNode;
        //如果删除的为第一个节点
        if (pred == null) {
            //将第一个节点设置为下一个节点
            firstNode = next;
        } else {
            //将上一个节点的下一个节点设置为next
            pred.nextNode = next;
            node.preNode = null;
        }

        //如果删除的为最后一个节点
        if (next == null) {
            //将最后一个节点设置为上一个节点
            lastNode = pred;
        } else {
            //将下一个节点的上一个节点设置为pred
            node.nextNode = null;
            next.preNode = pred;
        }

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

总结:

通过介绍,我们可以知道:
ArrayList更加适合查找元素,通过下标即可定位到元素本身,而在中间进行操作的情况下,效率上就很难比的过LinkedList的,很大原因是因为数组需要移动数据,进行数据的一个copy,而LinkedList只需要改变节点的上下引用即可,而对于随机的get,set效率就显得没那么快,因为他需要进行链表的一个遍历。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值