源码分析(8)---纯手写基于链表的方式实现LinkedList集合

  • LinkedList和ArrayList相比较,LinkedList适合做增删,而ArrayList适合做查询。因为ArrayList的底层是数组实现的,添加和删除操作会利用拷贝技术实现,LinkedList的底层是用链表为底层实现的。
  • ArrayList会考虑数组的扩容问题,但是LinkedList不需要考虑,因为底层是链表实现的。
  • ArrayList是数组实现,所以是连续的内存;但是LinkedList是链表实现,所以内存不连续。
  • Map集合在JDK1,7是使用数组+链表
  • Map集合在JDK1.8是使用数组+红黑树实现

数组和链表的区别

  • 数组

  • 数组的特性:连续性,有顺序,数组可以使用下标

  • 数组的删除原理就是将数组后面的数据整体复制到前面,然后将最后一位赋值为null(System.arraycopy)。相当于就是把后面的数据向前移动一位,然后把最后一个元素变为空。

  • 数组的指定下标添加的原理就是还是利用复制(System.arraycopy)实现。从指定的位置开始把后面的整体进行复制,将复制的整体覆盖到到指定位置的下一位,然后将指定位置的元素变为添加的元素。

  • 链表

  • 链表分为双向链表和单向链表

  • 链表有头指针和尾指针。

  • 单向链表的特性:当前指针只能找到下一个元素,无法找到上一个元素。

  • 双向链表的特性:当前指针可以找到上一个元素以及下一个元素,可以回到上一个元素。

LinkedList源码分析

  • int size:表示实际存储大小
  • Node first:表示的是第一个存储的元素,头指针
  • Node last:表示最后一个元素,尾指针
  • 如果在链表中只有一个节点,则其头指针和尾指针指向同一个
  • 我们在链表中,first和last指针的作用就是,first是为了表明查询链表的时候从哪个节点开始,last的作用就是便于操作尾部的元素,无需遍历到最后一个元素,直接用last则可以进行尾添加。
  • LinkedList的add方法是尾插,就是在链表的最后进行插入(用last指针实现)。
  • 源码中列表的查询实际上使用的是二分查找。

手写LinkedList

  • 下标为几,就需要next几次。比如下标是1的节点,就需要头结点.next,因为下标从0开
package com.xiyou.myLinkedList;

/**
 * 自定义实现LinkedList
 * LinkedList不需要扩容
 * @author zyf
 */
public class ExtLinkedList<E> {
    /**
     * 存储链表实际的长度
     */
    private Integer size = 0;

    /**
     * 存储头结点
     */
    private Node first;

    /**
     * 存储尾节点
     */
    private Node last;

    /**
     * 定义内部类Node,存储节点信息
     */
    private class Node{

        /**
         * 存放当前节点的信息
         */
        private Object object;

        /**
         * 存放上一个节点
         */
        private Node prev;

        /**
         * 存放下一个节点
         */
        private Node next;
    }


    /**
     * 异常判断。判断传入的索引是否越界
     * @param index
     */
    private void checkElementIndex(int index) {
        if (!isElementIndex(index)) {
            throw new IndexOutOfBoundsException("查询越界啦!");
        }
    }


    /**
     * 如果传入的下标大于等于0 并且 小于当前存入的元素,因为下标是从0开始的,size相当于列表的长度
     * @param index
     * @return
     */
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }


    /**
     * 添加一个元素在LinkedList
     * @param e
     */
    public void add(E e){
        // 创建一个节点
        Node node = new Node();
        // 给当前的节点赋值
        node.object = e;
        // 判断添加的是否是第一个元素
        if (first == null) {
            // 如果头结点是null,表示添加的是第一个元素
            first = node;
        } else {
            // 表示添加的不是第一个元素
            // 当前尾节点的下一个元素指向当前节点
            last.next = node;
            // 当前节点的上一个元素指向当前的尾节点
            node.prev = last;
        }
        // 不管是不是第一个节点,尾节点都要指上新增的节点,因为是尾插
        last = node;
        // 当前存入的数量加一
        size ++ ;
    }


    /**
     * 向指定的位置上添加一个元素
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        // 下标的验证
        checkElementIndex(index);
        // 先得到插入前的指定下标对应的节点
        Node oldNode = getNode(index);
        if (oldNode != null) {
            // 先新建一个node出来
            Node newNode = new Node();
            // 保存当前节点
            newNode.object = e;
            // 保存之前节点的前一个节点
            Node oldPrevNode = oldNode.prev;
            // 新建节点的下一个就是之前的节点
            newNode.next = oldNode;
            // 判断前一个节点是否为null,用来判断是不是第一个节点, 如果是第一个节点则表示oldPrevNode是null
            if (oldPrevNode == null) {
                // 表示当前下标对应的是头结点
                // 在头结点之前插入一个值,就是将新插入的值作为头结点
                first = newNode;
            } else {
                // 传入的index不是第一个
                newNode.prev = oldPrevNode;
                oldPrevNode.next = newNode;
            }

            // 还需要将之前的节点前置指向现在新增的
            oldNode.prev = newNode;
            // 新增了一个
            size ++ ;
        }
    }


    /**
     * 根据下标值获取指定的Node节点
     * @param index
     * @return
     */
    public Node getNode(int index){
        // 下标的验证
        checkElementIndex(index);
        Node node = null;
        if (first != null) {
            // 列表中存在元素
            node = first;
            // 遍历取下标是index的节点
            for (int i = 0; i < index; i++) {
                // 下一个节点赋值给当前节点
                // 就是比如,查找下标是3的,就找到下标是2的元素,然后将其的下一个节点的对象进行赋值
                node = node.next;
            }
        }
        return node;
    }


    /**
     * 根据下标得到具体的节点
     * @return
     */
    public Object get(int index){
        // 下标的验证
        checkElementIndex(index);

        return getNode(index).object;
    }


    /**
     * 返回列表存储了多少个数据
     * @return
     */
    public int getSize(){
        return size;
    }


    /**
     * 指定下标移除元素
     */
    public void remove(int index) {
        checkElementIndex(index);
        // 获取当前下标对应的节点
        Node oldNode = getNode(index);
        // 判断是否为空
        if (oldNode != null) {
            Node oldNextNode = oldNode.next;
            Node oldPrevNode = oldNode.prev;
            if (oldPrevNode == null) {
                // 如果旧节点的上一个为空表示头结点,即表示删除的是头结点
                first = oldNextNode;
            } else {
                // 当前下标对应的前一个节点的下一个节点是旧节点的下一个
                oldPrevNode.next = oldNextNode;
                // 当前下标节点的前一个节点赋值为空
                oldNode.prev = null;
            }

            if (oldNextNode == null) {
                // 表示的是尾节点被删除
                last = oldPrevNode;
            } else {
                oldNextNode.prev = oldPrevNode;
                oldNode.next = null;
            }
            oldNode.object = null;
            size -- ;
        }
    }


    public static void main(String[] args) {
        ExtLinkedList<String> extLinkedList = new ExtLinkedList<String>();
        extLinkedList.add("a");
        extLinkedList.add("b");
        extLinkedList.add("c");
        extLinkedList.add("e");
        extLinkedList.remove(2);
        // System.out.println("删除之前:" + extLinkedList.get(2)); // C
        //extLinkedList.add(3, "f");// fabce
        // System.out.println("删除之后:" + extLinkedList.get(2));// E
        // 其实从头查到尾### 效率不高,查询算法#####折半查找
        for (int i = 0; i < extLinkedList.size; i++) {
            System.out.println(extLinkedList.get(i));
        }
        // System.out.println(extLinkedList.get(2));
    }
}

注意:

  • LinkedList线程不安全
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值