
本文共 9538 字,大约阅读时间需要 31 分钟。
简介
ArrayList是一个动态数组,与普通数组相比,他可以动态的增加或者减少里面的元素(当然不是无限制的)。它extends AbstractList,implements List,提供了增加、修改、删除、遍历这些方法。
它实现了Cloneable接口,即覆盖了函数clone(),表示能被克隆。 实现了java.io.Serializable接口,意味着ArrayList支持序列化,能通过序列化去传输。 实现了RandmoAccess接口,提供了快速访问功能,即通过序号来访问对象。 需要额外注意的是,与Vector不同,ArrayList不是线程安全的。在多线程的环境下,还是使用Vector吧。先看看基本属性
public class ArrayListextends AbstractList implements List , RandomAccess, Cloneable, Serializable { // 序列化id private static final long serialVersionUID = 8683452581122892189L; // 默认初始的容量10 private static final int DEFAULT_CAPACITY = 10; // 实例化一个空对象,如果指定的ArrayList为0,就返回它 private static final Object[] EMPTY_ELEMENTDATA = new Object[0]; // 一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0]; // 当前数据对象存放地方,当前对象不参与序列化。是个动态数组,ArrayList的容量就是这个数组的大小。该值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,当第一次添加元素进入 ArrayList 中时,数组将扩容值 DEFAULT_CAPACITY(10) transient Object[] elementData; // 当前数组长度 private int size; // 数组最大长度 private static final int MAX_ARRAY_SIZE = 2147483639; // 省略方法。。}
我们可以着重关注一下elementData,它是存放List元素的数组,所以添加到ArrayList中的元素都保存于此。后面扩容也会说到它。
构造方法
无参数构造方法
如果不传入参数的话,默认使用该构造方法创建ArrayList对象。
//默认容量0,当加入第一个元素后,扩容成默认容量10 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
int类型的构造方法
传入int类型的参数,即代表我们就指定了要创建的ArrayList的初始长度。如果initialCapacity>=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); } }
Collection对象构造函数
把collection对象转换成数组,把数组对象给elementData;判断size作出对应处理…
//参数为Collection集合 public ArrayList(Collection c) { //集合转数组,再把地址给elementData elementData = c.toArray(); if ((size = elementData.length) != 0) { // 如果不是Object类型的数组,就转换成Object类型 if (elementData.getClass() != Object[].class) //把collection对象的内容(可以理解为深拷贝)copy到elementData中 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { //把空对象EMPTY_ELEMENTDATA的地址赋给elementData this.elementData = EMPTY_ELEMENTDATA; } }
从构造函数这些来看,就知道elementData的作用有多大了吧。据说,之所以elementData要被transient修饰,是因为elementData里面并不是所有的元素都有数据,要防止空的元素被”无辜“序列化。
常规操作
add方法
add方法有两个,分别是带一个参数和两个参数的。
add(E e) 方法
先以串联的形式来分析一下add(E e)
//不指定位置,添加到数组末尾 public boolean add(E e) { //确保添加的元素有地方存 ensureCapacityInternal(size + 1); // Increments modCount!! //确保新增的元素有位置存后,把新元素放上去 elementData[size++] = e; return true; }
//第一次添加的时候会将当前elementData数组的长度变为10,看DEFAULT_CAPACITY private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } //判断是否需要扩充数组长度 ensureExplicitCapacity(minCapacity); }
//将修改次数自增1,并判断是否需要扩充数组长度 private void ensureExplicitCapacity(int minCapacity) { modCount++;//修改次数自增1 // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity);//增长数组长度 }
//该方法用于增长数组长度,这里的arg0就是size+1的 private void grow(int arg0) { int arg1 = this.elementData.length; int arg2 = arg1 + (arg1 >> 1); if (arg2 - arg0 < 0) { arg2 = arg0; } if (arg2 - 2147483639 > 0) { arg2 = hugeCapacity(arg0);//判断大小 } //得到arg2,已使用size+1>数组长度就去扩容为原来的1.5倍 this.elementData = Arrays.copyOf(this.elementData, arg2); }
private static int hugeCapacity(int var0) { if (var0 < 0) { //如果传入的小于0的话 报错 throw new OutOfMemoryError(); } else { //大于0的话 如果大于 ((2^31)-1) = 2147483647-8 = 2147483639 return var0 > 2147483639 ? 2147483647 : 2147483639; } }
add(int index, E element)
这个方法是指定了位置,但是仔细看一下,跟上面那个不知道位置的方法差不太多。
public void add(int index, E element) { //判断插入的位置是否在容量范围内 rangeCheckForAdd(index); //判断是否需要扩容 ensureCapacityInternal(size + 1); //确保有足够的容量之后,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位。这样的移动方法,代价是不是太大了? System.arraycopy(elementData, index, elementData, index + 1, size - index); //在指定的位置添加元素 elementData[index] = element; //改变size size++; }
像确保元素有位置存储、判断是否扩充数组及扩容数组等方法跟上面一样,这里就不过多赘述了。
get方法
//传入index,返回指定位置的元素 public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); }
set方法
public E set(int index, E element) { rangeCheck(index);//检查set的位置 //把index上的元素交给oldValue E oldValue = elementData(index); //将传进来的element放在指定位置上 elementData[index] = element; //return之前存的oldValue return oldValue; }
remove方法
根据传入的参数删除某一元素,有两种,分别为根据索引和对象。删除后,该index位置后的元素都要统一前移一位。只是把最后一位置空,并不会影响数组长度。
根据索引remove元素
很明显,这里我们需要传入的是index索引位置,根据这个index去删除。
public E remove(int index) { rangeCheck(index);//判断这个index有没有越界? //修改次数自增处理 modCount++; //把指定index上的元素交给oldValue 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; }
根据对象remove
针对所有对象进行遍历,得到目标index,根据index去remove
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])) { //执行remove操作 fastRemove(index); return true; } } return false; }
fastRemove方法同样不会影响数组长度的!
private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) //index后面位置的元素统一前移 System.arraycopy(elementData, index+1, elementData, index, numMoved); //置空最后位置 elementData[--size] = null; // clear to let GC do its work }
contains方法
查看ArrayList是否包含有某一元素
//找到对应元素就返回true,否则return false public boolean contains(Object o) { //调用indexOf方法,遍历每个元素作对比 return indexOf(o) >= 0; }
//返回的值再去跟0作比较,从而得到true or false 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; }
clear方法
置空所有位置的元素,不影响数组容量!
public void clear() { modCount++;//操作次数自增处理 // 循环遍历所有元素,置空处理 for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
trimToSize方法
添加元素可能要扩容,但是删除元素并不会改变容量。如果要减少容量,就要调用该方法。
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
interator方法
返回一个内部类
public Iteratoriterator() { return new Itr(); }
使用iterator进行遍历,调用next的时候,修改次数与调用iterator方法时候(这回期望修改次数已经确定,之后add、remove会改变modCount)的修改次数不一致,就会发生ConcurrentModificationException
@SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; }
final void checkForComodification() { //修改次数和期望修改次数不等,就要抛出这个异常了 if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
Arrays.copyOf方法
底层调用System.arraycopy实现
//基本数据类型public static int[] copyOf(int[] original, int newLength) { int[] copy = new int[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
为啥ArrayList线程不安全?
先猜一下,应该是两个线程在操作,一个修改了,另一个没统计上…这里得重提一下两个重要的成员变量了:
transient Object[] elementData;//ArrayList基于动态数组实现,用此保存数据,容量就是该数组的长度 private int size;//保存当前数组中已经添加了多少元素 不知道大家还记不记得,当初我们add的时候,要先判断空间够不够,不够就扩容,然后再把这个元素添加进去… 总结一下就是:1.检查elementData容量是否够大;2.在elementData对应位置上添加值; 首先是对elementData容量的判断,再多线程情景下,线程A和线程B同时调用add方法。如果线程A判断需要扩容,且数组真的扩容后,若线程B判断不需扩容,此时就会抛出数组越界的异常。 第二个就是elementData[size++] = e,这个操作可不是原子操作,是分为两步的:1.elementData[size] = e;2.size = size + 1;在多线程的环境中,比方说线程A要在index=0的位置上放元素,碰巧线程B也要往这添加,更巧的是size=0;于是乎,线程B也把元素放在了index=0的位置上。理想情况是index=0的位置为元素1,index=1的位置放的是元素2。但是实际可不是这样,这样的话,就gg了…如何使ArrayList线程安全?
1.使用synchronized
2.Collections.synchronizedList(),比方: List list = Collections.synchronizedList(new ArrayList());发表评论
最新留言
关于作者
