集合是最常用的数据结构之一。在日常开发中,ArrayList也算是我们使用频率较大的数据存储类了。
它底层基于数组实现容量大小动态变化。允许 null 的存在。同时还实现了 RandomAccess、Cloneable、Serializable 接口,所以ArrayList 是支持快速访问、复制、序列化的。
至于为么它不是线程安全的,看完源码便不言而喻了。
下面是基于JDK1.8 的 ArrayList。
主要属性
//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
/**
* 有参构造函数的空数组赋值
* 空实例的共享空数组实例
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 无参构造函数的空数组赋值
* 与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存储元素的数组
* 不序列化这个字段
* ArrayList实现序列化反序列化方法writeObject、readObject
*/
transient Object[] elementData;
//存放元素的个数,不是elementData的长度
private int size;
构造函数
public ArrayList(int initialCapacity) {
/**
* 有参构造函数
* initialCapacity > 0 : 初始一个大小为 initialCapacity 的 elementData 数组
* initialCapacity = 0 : 把 EMPTY_ELEMENTDATA 赋值给 elementData
*/
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() {
/**
* 无参构造函数
* 把 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementData
*/
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;
}
}
主要实现
下面主要看一下核心方法:add()、remove()、set()、get()方法。
add(增)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
//检查index合法性
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//通过 System.arraycopy() 方法将旧数组元素拷贝至一个新的数组中去
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void ensureCapacityInternal(int minCapacity) {
/**
* 这里就可以看出 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 和 EMPTY_ELEMENTDATA 的区别
* 当第一次调用add,判断如果是无参构造函数创建的对象,此时 minCapacity=1
* 则将 DEFAULT_CAPACITY 作为 初始容量
*/
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果 minCapacity 大于 elementData 的长度,则对集合进行扩容。
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 扩容操作
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 右移一位(除以2),等价于扩容至原来容量的 1.5 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果 newCapacity < minCapacity ,取 minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
/**
* 如果1.5倍太大或者我们需要的容量太大,
* 则 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 进行扩容
*/
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 复制到新的数组
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;
}
MAX_ARRAY_SIZE 为么是 Integer.MAX_VALUE -8 ?
因为自己作为数组,除了存储数据本身以外,还需要32 bytes的大小来存储对象头(objectheader)信息。Java每个对象都包含了对象头,HotSpot虚拟机中对象头的大小不会超过32 bytes,所以最大容量减8才不会溢出。 Java对象在堆内存中的存储布局可以分为三部分:对象头(object header),实例数据(Instance Data)和对齐填充(Padding)。
remove(删)
public E remove(int index) {
// 合法性检查
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
// 判断删除的元素是否是最后一个位置
if (numMoved > 0)
//将从 index + 1 开始向后所有的元素都向前挪一个位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// size - 1 ,将最后一个元素置空
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++)
// 遍历删除元素为null的位置
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) {
// 删除逻辑和remove基本一致
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
}
在add()和remove() 有一个操作数组数据的工具方法 System.arraycopy() ,它是如何实现的呢?
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
| 方法入参 | 参数含义 |
|---|---|
| Object src | 原数组 |
| int srcPos | 从原数组的起始位置 |
| Object dest | 目标数组 |
| int destPos | 从目标数组的起始位置 |
| int length | 复制的数组长度 |
arraycope()在ArrayList中主要是为了实现添加或删除元素的时候,右移(添加元素时)或左移(删除元素时)数组数据,下面简单模拟一下添加元素。
public class Test {
public static void main(String[] args) {
String[] arr =new String[10];
arr[0] = "0";
arr[1] = "1";
arr[2] = "2";
arr[3] = "3";
arr[4] = "5";
int size = 5;
/**
* 在index = 1 的位置添加数据 ”x“
* 源码中,添加时的操作: System.arraycopy(elementData, index, elementData, index + 1,size - index);
*/
int index = 1;
System.out.println(Arrays.toString(arr));
System.arraycopy(arr, index, arr,index+1,size - index);
arr[index] = "x";
System.out.println(Arrays.toString(arr));
}
}
日志输出 >>>>
[0, 1, 2, 3, 5, null, null, null, null, null]
[0, x, 1, 2, 3, 5, null, null, null, null]
set(改)
set() 和 get() 方法,都很简单,不多解释。
public E set(int index, E element) {
//检查合法性
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
get(查)
public E get(int index) {
//检查合法性
rangeCheck(index);
return elementData(index);
}
trimToSize
在日常使用ArrayList的时候,不常使用这个方法。但是这方法,也解答了我心中的疑惑,ArrayList扩容后,是不会自动缩小容积的,可以调用trimToSize()方法,实现缩小数容积。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
手撕代码实现ArrayList
/**
* 仿照ArrayList,简单实现MyArrayList
*/
public class MyArrayList<E> {
private E[] elementData;
private int size = 0;
private E[] DEFAULT_ELEMENT_DATA = (E[]) new Object[10];
/**
* 构造函数
*/
public MyArrayList(){
this.elementData = DEFAULT_ELEMENT_DATA;
}
public MyArrayList(int initialCapacity){
elementData = (E[]) new Object[initialCapacity];
}
/**
* 增
*/
public boolean add(E e){
ensureCapacity(size + 1);
elementData[size++] = e;
return true;
}
public boolean add(int index, E e){
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
ensureCapacity(size + 1);
for (int i = size ; i > index; i--) {
elementData[i] = elementData[i-1];
}
elementData[index] = e;
size++;
return true;
}
/**
* 删
*/
public boolean remove(int index){
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
elementData[index] = null;
for(int i=index; i<size; i++){
elementData[i] = elementData[i+1];
}
elementData[size-1] = null;
size--;
return true;
}
/**
* 改
*/
public boolean set(int index, E e){
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
elementData[index] = e;
return true;
}
/**
* 查
*/
public E get(int index){
if(index < 0 || index >= size){
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
return elementData[index];
}
/**
* 判断数组容量 & 扩容2倍
*/
private void ensureCapacity(int needCapacity){
if(needCapacity > elementData.length){
int newCapacity = elementData.length * 2;
elementData = Arrays.copyOf(this.elementData,newCapacity);
}
}
public Object[] toArray(){
/**
* 返回size长度数组
*/
return Arrays.copyOf(elementData, size);
}
/**
* 数组越界异常信息
* @param index
* @return
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
@Override
public String toString() {
return "MyArrayList{" +
"elementData=" + Arrays.toString(toArray()) +
", size=" + size +
", length=" + elementData.length +
'}';
}
}
class MyArrayListTest{
public static void main(String[] args) {
MyArrayList<Integer> myArrayList = new MyArrayList<>();
System.out.println("插入10个元素:");
for (int i=0; i<10 ; i++){
myArrayList.add(i);
}
System.out.println(myArrayList.toString());
System.out.println("插入100:");
myArrayList.add(100);
System.out.println(myArrayList.toString());
System.out.println("在index=7插入77:");
myArrayList.add(7,77);
System.out.println(myArrayList.toString());
System.out.println("删除index=2数据:");
myArrayList.remove(2);
System.out.println(myArrayList.toString());
System.out.println("修改index=8数据:");
myArrayList.set(8,88);
System.out.println(myArrayList.toString());
System.out.println("获取index=9数据:" + myArrayList.get(9));
System.out.println("获取index=10数据:" + myArrayList.get(11));
}
}
日志输出
插入10个元素:
MyArrayList{elementData=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], size=10, length=10}
插入100:
MyArrayList{elementData=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100], size=11, length=20}
在index=7插入77:
MyArrayList{elementData=[0, 1, 2, 3, 4, 5, 6, 77, 7, 8, 9, 100], size=12, length=20}
删除index=2数据:
MyArrayList{elementData=[0, 1, 3, 4, 5, 6, 77, 7, 8, 9, 100], size=11, length=20}
修改index=8数据:
MyArrayList{elementData=[0, 1, 3, 4, 5, 6, 77, 7, 88, 9, 100], size=11, length=20}
获取index=9数据:9
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 11, Size: 11
at com.yx.arraylist.MyArrayList.get(MyArrayList.java:77)
at com.yx.arraylist.MyArrayListTest.main(MyArrayList.java:138)