认真研究Java集合之ArrayList的实现原理

简介: 认真研究Java集合之ArrayList的实现原理

ArrayList底层基于数组实现容量大小动态变化,允许 null 的存在。同时还实现了 RandomAccess、Cloneable、Serializable 接口,所以ArrayList 是支持快速访问、复制、序列化的。在频繁插入和删除场景中其性能不如LinkedList。


【1】核心属性和构造

① 核心属性

// 默认初始化数组大小
private static final int DEFAULT_CAPACITY = 10;
//空的数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储实际元素的数组
transient Object[] elementData;
//实际元素的个数
private int size;
//数组的最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8



② 构造函数

实例化时指定初始化大小

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);
   }
}


默认的无参构造,这里elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA而不是EMPTY_ELEMENTDATA

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


使用给定集合进行实例化

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

【2】核心方法实现

① add(E e)


代码如下所示,size表示实际元素的个数elementData[size++] = e表示先往elementData[size]位置放上e元素,然后size+=1

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}


ensureCapacityInternal用来确保数组的大小,必要时会引起数组扩容。

private void ensureCapacityInternal(int minCapacity) {
// minCapacity 最小为1
   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
       minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
   }
   ensureExplicitCapacity(minCapacity);
}


ensureExplicitCapacity确保明确的容量,也就是判断minCapacity是否会导致扩容。如果会则触发grow引起扩容。

private void ensureExplicitCapacity(int minCapacity) {
  // modCount用来记录结构的改变,在fail-fast机制会用到
    modCount++;
    // overflow-conscious code
    //如果溢出了,则扩容数组
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}


② 数组的扩容

grow用来实现数组的扩容,默认是1.5倍扩容。即oldData*1.5=newData。

private void grow(int minCapacity) {
   // overflow-conscious code
   int oldCapacity = elementData.length;
   // 1.5倍扩容
   int newCapacity = oldCapacity + (oldCapacity >> 1);
   //如果newCapacity 小于minCapacity ,则重新赋值
   if (newCapacity - minCapacity < 0)
       newCapacity = minCapacity;
  //如果newCapacity大于MAX_ARRAY_SIZE,则赋予Integer.MAX_VALUE
   if (newCapacity - MAX_ARRAY_SIZE > 0)
       newCapacity = hugeCapacity(minCapacity);
   // minCapacity is usually close to size, so this is a win:
   //数组拷贝,实例化新数组将元素拷贝进来
   elementData = Arrays.copyOf(elementData, newCapacity);
}

hugeCapacity是用来确定最大值的。如果容量大于MAX_ARRAY_SIZE那么就返回 Integer.MAX_VALUE,否则就采用MAX_ARRAY_SIZE。

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

③ 指定位置添加元素

public void add(int index, E element) {
   rangeCheckForAdd(index);
//这个逻辑同上,需要注意的是会导致modCount++
   ensureCapacityInternal(size + 1);  // Increments modCount!!
 //size - index 也就是目标位置到最后的个数
 //目标位置元素及后,往后顺延一个位置
   System.arraycopy(elementData, index, elementData, index + 1,
                    size - index);
   //index位置放上目标元素
   elementData[index] = element;
   //总个数+1
   size++;
}


rangeCheckForAdd用来判断目标位置是否存在,不存在则抛出IndexOutOfBoundsException异常。

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

如下这行代码会把index开始 (size-index)个数据,移动到index + 1位置及后续位置。

System.arraycopy(elementData, index, elementData, index + 1,size - index);

④ 获取元素

由于底层是数组,则根据index查找十分高效。

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

rangeCheck会判断index是否合适,不合适抛出IndexOutOfBoundsException异常。

private void rangeCheck(int index) {
   if (index >= size)
       throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

⑤ 移除指定位置元素

移除指定位置元素同样是导致结构发生了变化,modCount将会+1。

public E remove(int index) {
  //检测index是否合法
    rangeCheck(index);
  //结构变化计数器+1
    modCount++;
    //获取移除的元素
    E oldValue = elementData(index);
  //计算需要移动的元素个数
    int numMoved = size - index - 1;
    //数组拷贝
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //数组的最后一个位置置为null
    elementData[--size] = null; // clear to let GC do its work
  //返回旧值--移除的那个元素
    return oldValue;
}

⑥ 移除元素

移除元素的第一步是遍历,从第一个位置开始往后顺序遍历,找到第一个目标就结束遍历。

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;
}

fastRemove方法如下所示,其与remove(int index)很类似,不过不再检测index,也不再获取index位置的对象。

private void fastRemove(int index) {
       modCount++;
       int numMoved = size - index - 1;
       if (numMoved > 0)
           System.arraycopy(elementData, index+1, elementData, index,
                            numMoved);
       elementData[--size] = null; // clear to let GC do its work
   }


目录
相关文章
|
10天前
|
Java 索引 容器
Java ArrayList扩容的原理
Java 的 `ArrayList` 是基于数组实现的动态集合。初始时,`ArrayList` 底层创建一个空数组 `elementData`,并设置 `size` 为 0。当首次添加元素时,会调用 `grow` 方法将数组扩容至默认容量 10。之后每次添加元素时,如果当前数组已满,则会再次调用 `grow` 方法进行扩容。扩容规则为:首次扩容至 10,后续扩容至原数组长度的 1.5 倍或根据实际需求扩容。例如,当需要一次性添加 100 个元素时,会直接扩容至 110 而不是 15。
Java ArrayList扩容的原理
|
7天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
15 2
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
11天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
11天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
11天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
11 0
|
6月前
|
存储 安全 Java
java集合框架及其特点(List、Set、Queue、Map)
java集合框架及其特点(List、Set、Queue、Map)
|
3月前
|
存储 安全 Java
【Java集合类面试二十五】、有哪些线程安全的List?
线程安全的List包括Vector、Collections.SynchronizedList和CopyOnWriteArrayList,其中CopyOnWriteArrayList通过复制底层数组实现写操作,提供了最优的线程安全性能。
|
3月前
|
Java
【Java集合类面试二十三】、List和Set有什么区别?
List和Set的主要区别在于List是一个有序且允许元素重复的集合,而Set是一个无序且元素不重复的集合。
|
23天前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
52 5