源码学习——ArrayList

本文详细解析LinkedList的源码,涵盖继承关系、成员变量、构造方法、添加与删除操作,以及与ArrayList的区别。重点讲解了链表结构的优势和不足,适合进阶学习者深入理解数据结构实现。

源码学习——LinkedList

前言

将常用的步骤解释的较为详尽易懂,若有错误麻烦指正。个人认为甚至些许啰嗦,建议有基础的同学直接看源码,源码更为直接了当易懂,且不会产生语句上的歧义。

继承关系

UML 图示
UML

  • 实现 List 接口,属于列表类
  • 实现 RandomAccess 接口,具备快速随机访问的能力。(所有的 list 都可以支持随机访问,但是不一定支持快速随机访问,有的可以通过迭代器,如 linkedList,与底层实现有关)
  • 实现 Cloneable 接口,可以重写 Object.clone() 方法,具有深度克隆的能力。

成员变量


/**
 * 默认的初始化容量,如果构造方法未传入初始容量,则用 10
 */
private static final int DEFAULT_CAPACITY = 10;
/**
 * 构造方法传入初始容量为 0 时使用的空数组
 */
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
 * 构造方法未传入初始容量,使用默认容量时的空数组
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
 * 数据元素真正存储数组位置的指针
 */
transient Object[] elementData;
/**
 * ArrayList 的大小,即存储元素的数量,与容量区分开
 */
private int size;
/**
 * 数组分配的最大大小
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
 * 从父类继承的,记录列表在结构上被修改的次数,add or remove
 */
protected transient int modCount = 0;

构造方法

无参构造,未指定初始化容量,预备一个默认容量的空数组

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

传入初始化容量

  • 大于 0,新建一个该容量大小的数组
  • 等于 0,预备一个空数组(与未指定初始化容量提供的空数组区分开了)
  • 小于 0,异常抛出,提供了非法参数
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

传入一个集合,将其转化成数组

  • 集合为空,使用预备的空数组
  • 集合不为空,将其转化为 Object[]
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        // toArray 返回值可能不是 Object[],新建一个 Object[],将其存入
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

添加

添加到列表末尾

添加到列表末尾,返回是否成功

public boolean add(E e) {
	// 去确保列表能够支持再添加一个元素
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

确保容量足够,传入当前需要的最小容量

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

计算实际需要的容量

  • 当前使用的是默认容量,与当前需要的最小容量相比,返回大的那个
  • 如果不是,返回当前需要的最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

确保容量足够,传入计算出的实际需要的容量。

如果实际需要的容量比当前数组长度大,数组扩容

private void ensureExplicitCapacity(int minCapacity) {
	// 记录下,列表结构即将被修改一次
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

扩容函数,传入实际需要的容量。

扩容到原数组长度的 1.5 倍,如果扩容 1.5 倍还不够(addAll 场景下),就扩容到需要的容量。新建一个数组,将原数组复制过去。

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 如果 oldCapacity = 10 = 1010(二进制)
    // oldCapacity >> 1 = 0101 = 5
    // 扩容 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

添加到固定位置

public void add(int index, E element) {
	// 检查是否数组越界
    rangeCheckForAdd(index);
    // 如上文解析
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将 index 位置及之后元素,向后挪一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 当前元素放入
    elementData[index] = element;
    size++;
}

检查是否数组越界

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

System.arraycopy()

/**
 * 数据源数组
 * copy 开始的起始索引
 * 目标数组
 * copy 到的起始索引
 * copy 的长度
 */
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

添加一个集合

// 添加一个集合
public boolean addAll(Collection<? extends E> c)
// 向某个索引开始,插入一个集合
public boolean addAll(int index, Collection<? extends E> c)

这两个方法与添加一个元素相差无几,只是数量上的改变,可自行参阅源码

删除

删除一个元素

区分删除的元素是否为 null,防止在比较的过程中报空指针异常

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

快速删除当前索引的元素

即将当前索引后面的元素,统统向前挪一位,将其覆盖

private void fastRemove(int index) {
	// 即将改变列表的结构
    modCount++;
    // 计算需要移动的长度,即当前索引后面的元素数量
    int numMoved = size - index - 1;
    if (numMoved > 0)
    	// 当前索引后面的元素,统统向前挪一位
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 末尾置为空,方便 gc
    elementData[--size] = null; // clear to let GC do its work
}

删除部分元素

从 fromIndex 到 toIndex(不包括 to 本身)

protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    // to 索引及后面的元素覆盖到 from 索引及之后
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);

    // 置为 null,方便 gc
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}

查看

public E get(int index) {
	// 是否数组越界
    rangeCheck(index);
    
    return elementData(index);
}
E elementData(int index) {
    return (E) elementData[index];
}

修改

返回旧数据

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

LinkedList 和 ArrayList 的区别

  • 底层数据结构的区别,一个基于动态数组,一个基于链表。
  • 上述区别使得随机访问元素的形式不同,ArrayList 可以通过数组下标随机访问数组元素,可以实现 RandomAccess 接口;LinkedList 只能使用迭代器进行访问。
  • 新增和删除元素,ArrayList 可能需要扩容,LinkedList 不需要。
  • 空间浪费的体现不同,ArrayList 需要留足够的容量方便插入,LinkedList 需要维持链表间的关系和对象开销。

源码学习——LinkedList

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值