手写LinkedList实现基本功能
2021/7/23 8:06:03
本文主要是介绍手写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实现基本功能的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-16ShardingSphere 如何完美驾驭分布式事务与 XA 协议?
- 2024-11-16ShardingSphere如何轻松驾驭Seata柔性分布式事务?
- 2024-11-16Maven资料入门指南
- 2024-11-16Maven资料入门教程
- 2024-11-16MyBatis Plus资料:新手入门教程与实践指南
- 2024-11-16MyBatis-Plus资料入门教程:快速上手指南
- 2024-11-16Mybatis资料入门教程:新手必看指南
- 2024-11-16MyBatis资料详解:新手入门与初级实战指南
- 2024-11-16MyBatisPlus资料:初学者入门指南与实用教程
- 2024-11-16MybatisPlus资料详解:初学者入门指南