扩展JDK源码:Java手撕ArrayList【超详细注释】

发布时间:2023年12月30日

引言

算法和数据结构可以说是程序的核心,不仅是程序高效运行的基础,而且对于解决复杂问题至关重要。ArrayList因其灵活性和高效性在Java编程中占有重要地位。它是一个基于数组实现的动态大小的列表,能够根据需要自动扩展和收缩

在这篇文章中,我们将尝试从头开始实现一个类似于Java标准库(JDK)中ArrayList的数据结构。这个自定义的MyArrayList类将支持泛型,并提供动态数组的核心功能。通过这个过程,我们能更好地理解ArrayList的内部工作原理

目录

  • 概述
  • JDK ArrayList的实现原理
  • JDK ArrayList关键特性
  • JDK ArrayList类图概览
  • 基本实现
  • 创新方法,拓展JDK源码
  • 写在最后

JDK ArrayList 的实现原理

在深入到我们自己的ArrayList实现之前,让我们先快速了解一下Java标准库中ArrayList的工作原理。

Java中的ArrayList是基于数组实现的动态列表。它通过一个内部数组来存储元素,当这个数组的容量不足以容纳更多元素时,ArrayList会创建一个新的更大的数组,并将旧数组中的元素复制到新数组中。这个过程被称为“动态扩容”

JDK ArrayList关键特性:

  • 动态数组:ArrayList使用数组作为其内部数据结构,但与普通数组不同的是,可以动态地扩容
  • 随机访问:由于基于数组,ArrayList支持快速的随机访问,即可以在O(1)时间访问任何位置的元素
  • 自动扩容机制:当添加元素到ArrayList时,如果内部数组已满,ArrayList会自动创建一个更大的数组,并将所有元素复制到新数组中

JDK ArrayList 类图概览:

下面的类图简单展示了ArrayList的结构:
ArrayList uml类图
下文我们将手撕这些方法,并扩展一些jdk没有的方法

基本实现

定义类和构造函数

/**
 * 自定义的ArrayList实现,支持泛型
 * 
 * @param <T> ArrayList中存储的元素类型
 */
public class MyArrayList<T> {
	// 用于存储元素的数组
    private T[] array;  
    // 实际存储的元素数量
    private int size;  

    /**
     * 构造一个初始容量为10的空MyArrayList
     */
    public MyArrayList() {
        // 初始容量设为10
        array = (T[]) new Object[10]; 
        // 初始时,数组中没有元素
        size = 0;                     
    }
}

添加元素

/**
 * 向MyArrayList中添加一个新元素
 * 如果内部数组已满,则会触发自动扩容
 *
 * @param element 要添加的元素
 */
public void add(T element) {
    // 检查是否需要扩容。
    if (size == array.length) {
        resize();
    }
    // 在数组的下一个可用位置添加元素,并递增size
    array[size++] = element;
}

/**
 * 扩容
 * 当数组无法容纳更多元素时,调用此方法将数组容量增加一倍
 */
private void resize() {
    // 创建一个容量是当前数组两倍的新数组
    T[] newArray = (T[]) new Object[array.length * 2];
    // 将旧数组的内容复制到新数组
    System.arraycopy(array, 0, newArray, 0, array.length);
    // 更新内部数组引用到新数组
    array = newArray;
}

添加集合

/**
* 添加一个集合中的所有元素到MyArrayList
*
* @param c 要添加的元素集合
* @return 如果MyArrayList由于调用而改变,则返回true
*/
public boolean addAll(Collection<? extends T> c) {
   boolean modified = false;
   for (T element : c) {
       add(element);
       modified = true;
   }
   return modified;
}

获取元素

/**
 * 获取MyArrayList中指定索引处的元素
 *
 * @param index 要获取元素的索引
 * @return 在指定索引处的元素
 * @throws IndexOutOfBoundsException 如果索引超出数组的当前大小范围
 */
public T get(int index) {
    // 检查索引是否在有效范围内
    if (index >= size || index < 0) {
        throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
    }
    // 返回指定索引处的元素
    return array[index];
}

删除元素

/**
 * 从MyArrayList中移除指定索引处的元素
 *
 * @param index 要移除元素的索引
 * @return 被移除的元素
 * @throws IndexOutOfBoundsException 如果索引超出数组的当前大小范围
 */
public T remove(int index) {
    // 使用get方法获取元素同时检查索引有效性
    T element = get(index);

    // 将指定索引之后的元素向前移动一个位置
    for (int i = index; i < size - 1; i++) {
        array[i] = array[i + 1];
    }

    // 释放最后一个元素并减小size
    array[--size] = null;

    // 返回被移除的元素
    return element;
}

contains 方法

/**
 * 检查ArrayList是否包含特定元素
 * @param element 要检查的元素
 * @return 如果找到了元素,返回true,否则返回false
 */
public boolean contains(T element) {
    for (int i = 0; i < size; i++) {
        if ((element == null && array[i] == null) ||
            (element != null && element.equals(array[i]))) {
            return true;
        }
    }
    return false;
}

size 方法

/**
 * 返回MyArrayList中的元素数量
 *
 * @return 当前存储的元素数量
 */
public int size() {
    return size;
}

clear方法

/**
 * 清空MyArrayList,移除所有元素
 */
public void clear() {
    // 遍历数组,将每个元素设置为null
    for (int i = 0; i < size; i++) {
        array[i] = null;
    }
    // 重置元素数量为0
    size = 0;
}

isEmpty 方法

/**
 * 检查MyArrayList是否为空
 *
 * @return 如果列表没有元素则返回true,否则返回false
 */
public boolean isEmpty() {
    return size == 0;
}

isNotEmpty 方法

/**
 * 检查MyArrayList是否不为空
 *
 * @return 如果列表有元素则返回true,否则返回false
 */
public boolean isNotEmpty() {
    return size > 0;
}

indexOf 方法

/**
 * 查找元素在MyArrayList中的索引
 *
 * @param element 要查找的元素
 * @return 如果找到元素,则返回其索引;否则返回-1
 */
public int indexOf(T element) {
    // 遍历数组,寻找匹配的元素
    for (int i = 0; i < size; i++) {
        if (element.equals(array[i])) {
        	// 返回找到的元素的索引
            return i;  
        }
    }
    // 如果没有找到元素,返回-1
    return -1; 
}

lastIndexOf 方法

/**
 * 查找元素在MyArrayList中最后一次出现的索引
 *
 * @param element 要查找的元素
 * @return 如果找到元素,则返回其最后一次出现的索引;否则返回-1
 */
public int lastIndexOf(T element) {
    // 从数组的末尾开始向前遍历
    for (int i = size - 1; i >= 0; i--) {
        // 元素找到时返回当前索引
        if (element.equals(array[i])) {
            return i;
        }
    }
    // 未找到元素
    return -1;
}

removeRange 方法

public void removeRange(int fromIndex, int toIndex) {
    // 检查索引范围的有效性
    if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) {
        throw new IndexOutOfBoundsException();
    }

    // 计算需要移动的元素数量
    int numMoved = size - toIndex;
    // 将toIndex之后的元素向前移动,覆盖掉要移除的部分
    System.arraycopy(array, toIndex, array, fromIndex, numMoved);

    // 计算新的大小
    int newSize = size - (toIndex - fromIndex);
    // 将末尾多余的元素置为null
    for (int i = newSize; i < size; i++) {
        array[i] = null;
    }
    // 更新size
    size = newSize;
}

subList 方法

/**
 * 获取MyArrayList中指定范围内元素的子列表
 *
 * @param fromIndex 子列表的起始索引(包含)
 * @param toIndex 子列表的终止索引(不包含)
 * @return 指定范围内的元素构成的子列表
 * @throws IndexOutOfBoundsException 如果索引范围不合法
 */
public MyArrayList<T> subList(int fromIndex, int toIndex) {
    // 检查索引范围的有效性
    if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) {
        throw new IndexOutOfBoundsException();
    }

    // 创建新的MyArrayList作为子列表
    MyArrayList<T> sub = new MyArrayList<>();
    // 将指定范围内的元素添加到子列表中
    for (int i = fromIndex; i < toIndex; i++) {
        sub.add(array[i]);
    }
    // 返回子列表
    return sub;
}

replace 方法

/**
 * 替换MyArrayList中指定索引处的元素,并返回原来的元素
 *
 * @param index 要替换元素的索引
 * @param element 新元素
 * @return 原来在指定索引处的元素
 * @throws IndexOutOfBoundsException 如果索引超出数组的当前大小范围
 */
public T replace(int index, T element) {
    // 使用get方法获取当前索引处的元素,同时检查索引有效性
    T oldElement = get(index);

    // 将新元素设置到指定索引处
    array[index] = element;

    // 返回被替换的旧元素
    return oldElement;
}

sort默认自然排序

/**
* 根据元素的自然顺序对MyArrayList进行排序
*/
public void sort() {
	// 将MyArrayList转换为数组
	Object[] a = this.toArray();
	// 使用Arrays.sort方法进行排序,没有提供Comparator,将使用元素的自然顺序
	Arrays.sort(a);
	// 更新MyArrayList的内部数组
	System.arraycopy(a, 0, array, 0, size);
}

sort指定比较器排序

/**
* 根据指定比较器对MyArrayList进行排序
*
* @param comp 用于元素比较的Comparator
*/
public void sort(Comparator<? super T> comp) {
   // 将MyArrayList转换为数组
   Object[] a = this.toArray();
   // 使用Arrays.sort方法进行排序
   Arrays.sort(a, (Comparator) comp);
   // 更新MyArrayList的内部数组
   System.arraycopy(a, 0, array, 0, size);
}

创新方法,拓展JDK源码

批量添加元素

public void batchAdd(T... elements) {
    for (T element : elements) {
        add(element);
    }
}

翻转列表

public void reverse() {
    for (int i = 0; i < size / 2; i++) {
        T temp = array[i];
        array[i] = array[size - 1 - i];
        array[size - 1 - i] = temp;
    }
}

随机打乱列表

随机打乱列表中的元素顺序

public void shuffle() {
    Random rnd = new Random();
    for (int i = size; i > 1; i--) {
        swap(i - 1, rnd.nextInt(i));
    }
}

private void swap(int i, int j) {
    T temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

查找最大/最小元素

假设元素实现了Comparable接口,可以找到最大或最小的元素

public T findMax() {
    if (isEmpty()) {
        return null;
    }
    T max = array[0];
    for (int i = 1; i < size; i++) {
        if (max.compareTo(array[i]) < 0) {
            max = array[i];
        }
    }
    return max;
}

public T findMin() {
    if (isEmpty()) {
        return null;
    }
    T min = array[0];
    for (int i = 1; i < size; i++) {
        if (min.compareTo(array[i]) > 0) {
            min = array[i];
        }
    }
    return min;
}

去除重复元素

public void removeDuplicates() {
    HashSet<T> set = new HashSet<>();
    int writePointer = 0;
    for (int readPointer = 0; readPointer < size; readPointer++) {
        if (set.add(array[readPointer])) {
            array[writePointer++] = array[readPointer];
        }
    }
    for (int i = writePointer; i < size; i++) {
        array[i] = null;
    }
    size = writePointer;
}

交集

/**
 * 计算当前MyArrayList与另一个MyArrayList的交集
 *
 * @param otherList 要比较的另一个MyArrayList
 * @return 两个列表共有元素组成的新MyArrayList
 */
public MyArrayList<T> intersection(MyArrayList<T> otherList) {
    MyArrayList<T> result = new MyArrayList<>();
    // 遍历当前列表的每个元素
    for (T item : this.array) {
        // 如果另一个列表也包含这个元素,则添加到结果列表中
        if (otherList.contains(item)) {
            result.add(item);
        }
    }
    return result;
}

并集

/**
 * 计算当前MyArrayList与另一个MyArrayList的并集
 *
 * @param otherList 要比较的另一个MyArrayList
 * @return 两个列表所有元素(不包括重复)组成的新MyArrayList
 */
public MyArrayList<T> union(MyArrayList<T> otherList) {
    MyArrayList<T> result = new MyArrayList<>();
    // 将当前列表的元素添加到结果列表中
    for (T item : this.array) {
        result.add(item);
    }
    // 将另一个列表中不重复的元素添加到结果列表中
    for (T item : otherList.array) {
        if (!result.contains(item)) {
            result.add(item);
        }
    }
    return result;
}

差集

/**
 * 计算当前MyArrayList与另一个MyArrayList的差集
 *
 * @param otherList 要比较的另一个MyArrayList
 * @return 存在于当前列表但不在另一个列表中的元素组成的新MyArrayList
 */
public MyArrayList<T> difference(MyArrayList<T> otherList) {
    MyArrayList<T> result = new MyArrayList<>();
    // 遍历当前列表的每个元素
    for (T item : this.array) {
        // 如果另一个列表不包含这个元素,则添加到结果列表中
        if (!otherList.contains(item)) {
            result.add(item);
        }
    }
    return result;
}

MyArrayList类图

MyArrayList uml类图
可以看到我们基本上实现了jdk所有的方法,并且拓展了很多JDK并不支持的方法

写在最后

通过这篇文章,探索了如何从零开始构建一个高效的ArrayList。希望在这个过程中你不仅学到了如何实现这个强大的数据结构,而且也对Java ArrayList操作有了更深刻的理解

编程其实不仅仅是关于写代码,更是关于理解背后的原理,这样我们才能写出更高效、更可靠的代码。希望这篇文章能够激发你们对深入学习Java以及算法数据结构的决心

如果喜欢这篇文章,记得点赞和关注。你的支持是我不断创作和分享的动力!同时,如果你有任何问题或想法,欢迎在评论区留言,让我们一起讨论和成长。

感兴趣的读者也可以看看往期我写的文章:

java实现十大经典排序算法【GIF动画+完整代码+详细注释
Java设计模式实战:从If-Else到策略+工厂方法的演变
Java并发编程深度解析:掌握CompletableFuture的高效异步模式
Lambda表达式深度解析:提升代码效率与可读性
https://blog.csdn.net/weixin_38522648/article/details/135186933
一文搞懂JAVA单例设计模式的所有实现
Redis缓存八大模式,项目应用选型?这篇文章就够了
Redis缓存的八大模式,项目应用如何选型?这篇文章就够了
git的工作原理,实战案例,这篇文章就够了
深入剖析事务ACID实现的底层原理

  • 程序员三毛
文章来源:https://blog.csdn.net/weixin_38522648/article/details/135258373
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。