java自定义动态数组和链表(ArrayList、LinkedList)

核心目标

  • 通过手动实现简化版ArrayListLinkedList,深入理解Java集合框架底层原理。
  • 对比两种数据结构的性能差异及适用场景。

自定义ArrayList实现

1.底层结构与初始化
  • 基于动态数组实现,核心成员变量:
    对象数组 object[] obj
    数组存储数据的个数 int size
	private static final int DEFAULT_CAPACITY = 10;//初始容量
    private Object[] obj;
    private int size;
  • 构造方法设计(默认容量、指定容量)。

默认容量

public MArrayList() {
        obj = new Object[DEFAULT_CAPACITY];
        size = 0;
    }

指定容量

public MArrayList(int capacity) {
        if (capacity > 0) {
            obj = new Object[capacity];
            size = 0;
        }
    }
2.关键方法实现

动态扩容机制

  • 触发条件:插入元素时检查size == obj.length
  • 扩容策略:新数组大小为原容量的1.5倍(模拟JDK实现)。
  • 思路:创建一个 数组长度为原数组1.5倍的 新数组,复制obj从0~size的数据 到 newObj 的0~size位置
//扩容
    public void resize() {
        int newSize = (int) (obj.length * 1.5);
        Object[] newObj = new Object[newSize];
        //更新数组,复制obj从0-size的数据 到 newObj的0-size位置
        if (size >= 0)
            System.arraycopy(obj, 0, newObj, 0, size);
        obj = newObj;
    }

增删改查操作

  • add(E element):尾插法。
    1.判断是否需要扩容。
    2.将新元素直接添加在数组末尾,size++。
//添加到末尾
    public void add(E data) {
        //对原数组扩容
        if (size == obj.length) {
            resize();
        }
        obj[size] = data;
        size++;
    }
  • add(int index, E element):索引检查、数据搬移(System.arraycopy)。
    判断是否需要扩容。
    1.创建一个 数组长度为 原数组长度 的新数组,size++。
    2.将index前的数据复制到对应位置:复制obj从0~index的数据 到 newObj 的0~index位置,
    3.将data插入数组的index位置。
    4.将剩余元素后移一位,需要复制的数据个数为 size-index。
//插入到指定位置
    public void insert(int index, E data) {
    	if (index < 0 || index > size)
            throw new IndexOutOfBoundsException();
        if (size == obj.length) {
            resize();
        }
        Object[] newObj = new Object[obj.length];
        size++;
        //index前数据复制到对应位置
        System.arraycopy(obj, 0, newObj, 0, index);
        Object temp = obj[index];
        newObj[index] = data;
        //后移一位,需要复制的数据个数为 size-index
        if (size - index >= 0)
            System.arraycopy(obj, index, newObj, index + 1, size - index);
        newObj[index + 1] = temp;
        obj = newObj;
    }
  • remove(int index):边界校验、元素左移。
    1.检验index是否超出边界
    2.如果index是obj的最后一位(index从0开始,所以最后一位的下标为size-1),直接将最后一个元素赋为null,size- -,返回index对应位置存的数据。
 //移除指定位置数据
    public E remove(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException();
        if(index == size-1) {
            Object temp = obj[index];
            obj[--size] = null;
            return (E) temp;
        }
        Object temp = obj[index];
        for (int i = index; i < size; i++) {
            obj[i] = obj[i + 1];
        }
        obj[size] = null;
        size--;
        return (E) temp;
    }

  • removeFirst(E element):删除指定数据,如果有多个相同数据,按数组下标的顺序删除第一个找到的
    1.查找数组中是否有data元素,如果找到了保存下标
    2.从index开始,将后面数组前移
//移除指定数据
    public boolean removeFirst(int data) {
        int index = -1;
        for (int i = 0; i < size; i++) {
            if (obj[i].equals(data)) {
                index = i;
            }
        }
        if (index == -1)
            return false;
        for (int i = index; i < size; i++) {
            obj[i] = obj[i + 1];
        }
        obj[size] = null;
        size--;
        return true;
    }

  • get(int index):时间复杂度O(1)的直接访问。
//获取
    public E get(int index) {
        if (index < 0 || index >= size)
            return null;
        return (E) obj[index];
    }

泛型
本文在实现ArraList时使用了泛型,允许不同数据类型使用该类,但是插入ArrayList的这组元素必须都为声明时的类型。

public class MArrayList<E> {
}

例如,

MArrayList<Integer> list = new MArrayList<>();

此时插入的元素必须都为Integer类型。


自定义LinkedList实现

1.节点结构与初始化
  • 节点类定义:(本文使用单向链表实现LinkedList)
class Node {
    public Object data;
    public Node next;
    public Node(Object data) {
        this.data = data;
    }
    public Node() {
    }
}
  • 核心成员变量
    private Node root;//根节点,root.next为头结点
    public Node last;
    public int size;

  • 构造方法

   public LinkList() {
        root = new Node();
        size = 0;
    }
2.关键方法实现

插入与删除

  • add(int index, E element):尾插法
    1.如果还没有插入节点,则此结点为头节点。
    2.如果已插入节点,在last后面插入新节点。
    3.size++
public void add(E data) {
        Node node = new Node(data);
        Node head = root.next;
        if (head == null) {
            root.next = node;  //头节点
            last = node;
        } else {
            last.next = node;
            last = node;
        }
        size++;
    }
  • insert(int index,E data)在指定位置插入元素
    1.如果index=0,则插入的是头结点。
    2.如果index不为0,先从头遍历至index的前一个节点,重新在index处建立关联,如果index=size,则此节点为尾节点。
    3.size++
- public void insert(int index, E data) {
       if (index < 0 || index > size) {
           System.out.println("Index out of bounds");
           return;
       }
       Node newNode = new Node(data);
       if (index == 0) {
           newNode.next = root.next;
           root.next = newNode;
           if (size == 0)
               last = newNode;
       } else {
           Node prev = root;
           for (int i = 0; i < index; i++) {
               prev = prev.next;
           }
           newNode.next = prev.next;
           prev.next = newNode;
           if (index == size)
               last = newNode;
       }
       size++;
   }
  • remove(int index):解除节点关联、处理头尾边界。逻辑和insert类似。
    1.如果index=0,去除头结点root.next,重新建立关联。
    2.从头遍历值index的前一个节点,保存index节点数据,重新建立与后继结点的关联。
    3.size- -,返回remove的数据。
public E remove(int index) {
       Node pre = root.next;
       if (index == 0) {
           root.next=pre.next;
           return (E)pre.data;
//            if (size == 0)
//                System.out.println("List is empty");
       }
       else if (index < 0 || index > size) {
           System.out.println("Index out of bounds");
           return null;
       }
       for (int i = 0; i < index-1; i++) {
           pre = pre.next;
       }
       Node remove = pre.next;
       pre.next = remove.next;
       if (index == size - 1)
           last = pre;
       size--;
       return (E)remove.data;
   }

遍历性能分析

  • get(int index):根据索引位置选择从头遍历(时间复杂度O(n/2))。
public Object get(int index) {
        if (index < 0 || index >= size) {
            System.out.println("Index out of bounds");
            return -1;
        }
        Node cur = root.next;
        for (int i = 0; i < index; i++) {
            cur = cur.next;
        }
        return cur.data;
    }
3. 其他方法
  • reverse()实现链表的翻转。
public void reverse() {
        Node pre = null;
        Node cur = root.next;
        Node tmp = null;
        while (cur != null) {//cur不为尾节点
            tmp = cur.next;//cur与cur.next交换位置
            cur.next = pre;
            pre = cur;
            cur = tmp;
        }
        root.next = pre;
    }
  • display()展示链表内容,便于直观地看到修改后的效果。
public void display() {
        Node cur = root.next;
        int index = 1;
        for (int i = 0; i < size; i++) {
            if (cur != null) {
                System.out.print( cur.data + " ");
                cur = cur.next;
                index++;
            }
        }
        System.out.println();
    }

性能对比与应用场景

1.时间复杂度对比
操作ArrayListLinkedList
随机访问O(1)O(n)
头部插入O(n)O(1)
中间删除O(n)O(n)
2. 适用场景建议
  • ArrayList:读多写少、需要频繁随机访问。
  • LinkedList:频繁在头部/尾部插入删除、无需随机访问。

总结

自定义实现Arraylist·和Linkedlist的意义:通过实现这两个基础集合类,开发者能够更直观地理解底层数据结构,并且能够根据需求优化特定场景的性能。

若发现错误之处,恳请读者批评指正,我将及时修正完善。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值