ArrayList<E>
ArrayList
是 Java 集合框架中 List
接口的一个动态数组实现类,位于 java.util
包下。下面详细介绍其底层数据结构和实现原理。
底层数据结构概述
ArrayList
的底层是基于数组实现的,这个数组用于存储元素。在 Java 中,数组是一种连续的内存空间,它可以通过索引快速访问元素,这使得 ArrayList
具备了快速随机访问的能力。
核心属性
ArrayList
类中有几个核心属性,它们在实现动态数组的过程中起着关键作用:
// 存储元素的数组 transient Object[] elementData; // 数组中实际存储的元素数量 private int size;
elementData
:这是一个Object
类型的数组,用于存储ArrayList
中的元素。使用transient
关键字修饰意味着该数组在序列化时会被忽略,ArrayList
有自己的序列化和反序列化逻辑。size
:表示ArrayList
中实际存储的元素数量,而不是数组的长度。
构造方法
ArrayList
提供了多个构造方法,用于初始化 elementData
数组:
// 默认初始容量 private static final int DEFAULT_CAPACITY = 10; // 共享的空数组实例,用于空实例 private static final Object[] EMPTY_ELEMENTDATA = {}; // 共享的空数组实例,用于默认大小的空实例 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 默认构造函数,初始化为空数组,第一次添加元素时会扩容为默认容量 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } // 指定初始容量的构造函数 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); } } // 构造一个包含指定集合元素的列表,顺序由集合的迭代器返回 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; } }
- 默认构造方法:创建一个空的
ArrayList
,初始时elementData
是一个空数组,第一次添加元素时会将数组扩容为默认容量(10)。 - 指定初始容量的构造方法:根据传入的初始容量创建一个指定大小的数组。
- 包含指定集合元素的构造方法:将指定集合中的元素复制到
ArrayList
中。
动态扩容机制
当向 ArrayList
中添加元素时,如果数组的容量不足,就需要进行扩容操作。扩容的主要步骤如下:
// 添加元素的方法 public boolean add(E e) { ensureCapacityInternal(size + 1); // 确保容量足够 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); } // 数组最大容量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 数组扩容方法 private void grow(int minCapacity) { // 获取旧容量 int oldCapacity = elementData.length; // 新容量为旧容量的 1.5 倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果新容量小于所需最小容量,将新容量设置为所需最小容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新容量大于最大数组容量,调用 hugeCapacity 方法处理 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 调用 Arrays.copyOf 方法将旧数组元素复制到新数组 elementData = Arrays.copyOf(elementData, newCapacity); } // 处理大容量需求 private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
- 添加元素:调用
add
方法添加元素时,首先会调用ensureCapacityInternal
方法确保数组容量足够。 - 计算容量:
calculateCapacity
方法会根据数组的初始状态计算所需的最小容量。 - 扩容判断:
ensureExplicitCapacity
方法会比较所需最小容量和当前数组长度,如果容量不足则调用grow
方法进行扩容。 - 扩容操作:
grow
方法会将新容量设置为旧容量的 1.5 倍,如果新容量仍然不足,则将新容量设置为所需最小容量。最后,使用Arrays.copyOf
方法将旧数组元素复制到新数组。
元素的增删改查操作
- 添加元素:可以使用
add(E e)
方法在列表末尾添加元素,也可以使用add(int index, E element)
方法在指定位置插入元素。插入元素时,需要将插入位置之后的元素依次后移。
// 在指定位置插入元素 public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // 确保容量足够 // 将插入位置之后的元素依次后移 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
- 删除元素:可以使用
remove(int index)
方法删除指定位置的元素,也可以使用remove(Object o)
方法删除指定元素。删除元素时,需要将删除位置之后的元素依次前移。
// 删除指定位置的元素 public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); 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 return oldValue; }
- 修改元素:可以使用
set(int index, E element)
方法修改指定位置的元素。
// 修改指定位置的元素 public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
- 查找元素:可以使用
get(int index)
方法根据索引查找元素,也可以使用indexOf(Object o)
方法查找元素的索引。
// 根据索引查找元素 public E get(int index) { rangeCheck(index); return elementData(index); } // 查找元素的索引 public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }
总结
ArrayList
底层基于数组实现,通过动态扩容机制实现了动态数组的功能。它具有快速随机访问的优点,但在插入和删除元素时需要移动大量元素,效率较低。因此,ArrayList
适用于需要频繁随机访问元素,而插入和删除操作较少的场景。
Java集合框架 文章被收录于专栏
Java集合框架是Java提供的一组用于存储和操作数据的类和接口,它位于java.util包中,为开发者提供了强大且灵活的数据存储和处理能力。以下将从整体架构、主要接口、常用实现类、使用场景以及示例代码等方面详细介绍Java集合框架。