手写LinkedList实现基本功能

2021/7/23 8:06:04

本文主要是介绍手写LinkedList实现基本功能,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

概述
LinkedList

主要 特性:
顺序访问
写快读慢 读的时候需要遍历 底层采用了折半查找提高了效率 但是比起数组来说还是慢的多
查看源码

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

AbstractSequentialList:该抽象类继承自java.util.AbstractList 提供了顺序访问存储结构,只要类似于linkedList这样的顺序访问List 都可以继承该类

List:列表标准接口,列表是一个有序集合,又被称为序列,该接口对内部的每一个元素的插入位置都有精确控制

Deque:双向队列接口,继承自Queue 因为Queue是先进先出的特性 继承后 就看可以在尾部增加数据头部获取数据

Cloneable:标记可克隆对象 ,没有实现该接口的对象在调用Object.clone()方法时会抛出异常 分为浅拷贝和深拷贝 这里默认都是浅拷贝

java.io.Serializable:序列化标记接口 被此接口标记的类 可以实现Java序列化和反序列化

手写内容
成员变量和常量
size标记序列的大小 统计节点的个数
first 链表的头结点
last 链表的尾结点

/**
     * 链表实际长度
     */
    private int size;

    /**
     * 指向第一个节点 为了查询开始
     */
    private Node first;

    /**
     * 指向最后一个节点 为了插入开始
     */
    private Node last;

这里没有和源码一样用transient修饰

transient修饰 的作用是 用于标记无需序列化的成员变量

也就是说 实现了 Serializable接口的LinkedList 一定提供了readObject和writeObject方法 源码如下

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out size
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (Node<E> x = first; x != null; x = x.next)
            s.writeObject(x.item);
    }

    /**
     * Reconstitutes this {@code LinkedList} instance from a stream
     * (that is, deserializes it).
     */
    @SuppressWarnings("unchecked")
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read in size
        int size = s.readInt();

        // Read in all elements in the proper order.
        for (int i = 0; i < size; i++)
            linkLast((E)s.readObject());
    }

Node节点
链表由节点构成 节点又包含 数据域 和指针域

prev 前指针

next 后指针

object就是数据域

class Node{
        /**
         * 前指针 指向上一个节点
         */
        Node prev;

        /**
         * 后指针  指向下一个节点
         */
        Node next;

        Object object;

    }

add方法
创建一个节点 将插入的数据给这个节点的数据域赋值
判断头指针是否为空 为空则说明这个是头指针 不为空则在尾部进行插入
然后让这个节点为最后一个节点
链表长度+1
主要逻辑详见注解

/**
     * 添加数据进入
     * @param e
     */
    public void add(E e){
        Node node = new Node();
        node.object = e;
        //如果第一个节点为空 就插入第一个数据
        if(first == null){
            //添加第一个元素给第一个元素赋值
            first = node;
        }else{//插入第二个及其以上数据
            node.prev = last;
            //将上一个元素的节点的下一个指向node 此时node还不是last
            last.next = node;
        }
        last = node;
        size++;
    }
    /**
     * 插入节点
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        Node newNode = new Node();
        newNode.object = e;
        // 获取原来的节点
        Node oldNode = getNode(index);
        // 获取原来的上一个节点
        Node oldNodePrev = oldNode.prev;
        //将原来的节点的上一个节点为新节点
        oldNode.prev = newNode;
        if (oldNodePrev == null) {
            first = newNode;
        } else {
            //上一个节点的下一个节点是新节点
            oldNodePrev.next = newNode;
        }
        //新节点的下一个节点为原来节点
        newNode.next = oldNode;
        size++;
    }

get方法
源码是这样写的 采用折半查找进行数据的搜索

public E get(int index) {
        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;
        }
    }

我这里简单点 直接遍历查找获取

/**
 * 获取元素值
 * @param index
 * @return
 */
public E get(int index) {
    Node node = getNode(index);
    return (E) node.object;
}

/**
 * 得到节点
 * @param index
 * @return
 */
public Node getNode(int index) {
    Node node = null;
    if (first != null) {
        node = first;
        //源码中采用折半查找 我们这里是直接遍历
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
    }
    return node;
}

set方法
数据的修改

调用前面的获取当前的节点的值get方法获取 然后直接修改值即可

 /**
 * 修改数据
 * @param index
 * @param element
 * @return
 */
public E set(int index, E element) {
    checkElementIndex(index);
    Node node = getNode(index);
    E oldVal = (E) node.object;
    node.object = element;
    return oldVal;
}

越界问题
判断是否越界 当前的索引必须大于0小于当前链表的长度

/**
 * 检查越界
 * @param index
 */
private void checkElementIndex(int index) {
    if (!isElementIndex(index)) {
        throw new IndexOutOfBoundsException("下标越界");
    }
}

/**
 * 判断参数是否是现有元素的索引
 * @param index
 * @return
 */
private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

remove方法
移除元素

判断越界问题

拿到当前要移除的节点

拿到当前要移除节点的上一个节点和下一个节点 方便把这两个节点连接在一块

连接上一个节点和下一个节点 判断边界

长度减1

/**
 * 删除元素
 * @param index
 * @return
 */
public Object remove(int index){
    //检查越界
    checkElementIndex(index);
    //获取当前删除节点
    Node node = getNode(index);
    if (node != null) {
        //得到当前删除节点的上一个节点
        Node prevNode = node.prev;
        //得到当前删除节点的下一个节点
        Node nextNode = node.next;
        // 设置上一个节点的next为当前删除节点的next
        if (prevNode != null) {
            prevNode.next = nextNode;
        }
        // 判断是否是最后一个节点
        if (nextNode != null) {
            nextNode.prev = prevNode;
        }
    }
    size--;
    return  node;
}

clear方法
循环清除整个链表

置为空后将垃圾回收交给垃圾回收器自己判断清除

链表长度置为1

public void clear(){
        //循环清除各个节点之间的联系
        for (Node x = first; x != null; ) {
            Node next = x.next;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
    }

完整代码

import java.util.LinkedList;

/**
 * @author Yu W
 * @version V1.0
 * @ClassName MyLinkedList
 * @Description: 手写LinkedList 学习LinkedList源码
 * @date 2021/7/18 18:00
 */
public class MyLinkedList <E> {

    class Node{
        /**
         * 前指针 指向上一个节点
         */
        Node prev;

        /**
         * 后指针  指向下一个节点
         */
        Node next;

        Object object;

    }
    /**
     * 链表实际长度
     */
    private int size;

    /**
     * 指向第一个节点 为了查询开始
     */
    private Node first;

    /**
     * 指向最后一个节点 为了插入开始
     */
    private Node last;

    /**
     * 添加数据进入
     * @param e
     */
    public void add(E e){
        Node node = new Node();
        node.object = e;
        //如果第一个节点为空 就插入第一个数据
        if(first == null){
            //添加第一个元素给第一个元素赋值
            first = node;
        }else{//插入第二个及其以上数据
            node.prev = last;
            //将上一个元素的节点的下一个指向node 此时node还不是last
            last.next = node;
        }
        last = node;
        size++;
    }

    /**
     * 修改数据
     * @param index
     * @param element
     * @return
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node node = getNode(index);
        E oldVal = (E) node.object;
        node.object = element;
        return oldVal;
    }
    /**
     * 插入节点
     * @param index
     * @param e
     */
    public void add(int index, E e) {
        Node newNode = new Node();
        newNode.object = e;
        // 获取原来的节点
        Node oldNode = getNode(index);
        // 获取原来的上一个节点
        Node oldNodePrev = oldNode.prev;
        //将原来的节点的上一个节点为新节点
        oldNode.prev = newNode;
        if (oldNodePrev == null) {
            first = newNode;
        } else {
            //上一个节点的下一个节点是新节点
            oldNodePrev.next = newNode;
        }
        //新节点的下一个节点为原来节点
        newNode.next = oldNode;
        size++;
    }

    /**
     * 获取元素值
     * @param index
     * @return
     */
    public E get(int index) {
        Node node = getNode(index);
        return (E) node.object;
    }

    /**
     * 得到节点
     * @param index
     * @return
     */
    public Node getNode(int index) {
        Node node = null;
        if (first != null) {
            node = first;
            //源码中采用折半查找 我们这里是直接遍历
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
        }
        return node;
    }

    /**
     * 删除元素
     * @param index
     * @return
     */
    public Object remove(int index){
        //检查越界
        checkElementIndex(index);
        //获取当前删除节点
        Node node = getNode(index);
        if (node != null) {
            //得到当前删除节点的上一个节点
            Node prevNode = node.prev;
            //得到当前删除节点的下一个节点
            Node nextNode = node.next;
            // 设置上一个节点的next为当前删除节点的next
            if (prevNode != null) {
                prevNode.next = nextNode;
            }
            // 判断是否是最后一个节点
            if (nextNode != null) {
                nextNode.prev = prevNode;
            }
        }
        size--;
        return  node;
    }

    /**
     * 检查越界
     * @param index
     */
    private void checkElementIndex(int index) {
        if (!isElementIndex(index)) {
            throw new IndexOutOfBoundsException("下标越界");
        }
    }

    /**
     * 判断参数是否是现有元素的索引
     * @param index
     * @return
     */
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

    /**
     * 从此列表中删除所有元素。此调用返回后,列表将为空
     */
    public void clear(){
        //循环清除各个节点之间的联系
        for (Node x = first; x != null; ) {
            Node next = x.next;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
    }
}

————————————————

版权声明:本文为CSDN博主「鱼爱吃柚子」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/qq_44752800/article/details/118884182



这篇关于手写LinkedList实现基本功能的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程