本文是配合部分源码分析的,若有错误之处,欢迎大家指出,共同学习,共同进步,谢谢!(ps:翻译不到位的地方大家忽略 ==、)废话不多说,咱们直接开始!
进入ArrayList类,大致看看上面描述:
/** Resizable-array implementation of the <tt>List</tt> interface. Implements
* all optional list operations, and permits all elements, including
* <tt>null</tt>. In addition to implementing the <tt>List</tt> interface,
* this class provides methods to manipulate the size of the array that is
* used internally to store the list. (This class is roughly equivalent to
* <tt>Vector</tt>, except that it is unsynchronized.) */
list接口可变大小数组的实现。可以操作集合内所有元素,包含null。除了实现接口, 这个类
还提供了操作内部储存集合的数组大小的方法。(这个类大体和vector相似,但是它是不同步的。)
/** Note that this implementation is not synchronized.</strong>
* If multiple threads access an <tt>ArrayList</tt> instance concurrently,
* and at least one of the threads modifies the list structurally, it
* <i>must</i> be synchronized externally. (A structural modification is
* any operation that adds or deletes one or more elements, or explicitly
* resizes the backing array; merely setting the value of an element is not
* a structural modification.) This is typically accomplished by
* synchronizing on some object that naturally encapsulates the list. */
注意该实现是不同步的,如果有多个线程同时访问集合实例,且至少有一个线程改变了集合构造,
那么必须在外部同步。(修改集合构造的操作有添加、删除一个或多个元素,以及变化集合容量等,
设置元素值不包含在内)这一般通过自然封装对象的方式来完成同步。
/** If no such object exists, the list should be "wrapped" using the
* {@link Collections#synchronizedList Collections.synchronizedList}
* method. This is best done at creation time, to prevent accidental
* unsynchronized access to the list:
* List list = Collections.synchronizedList(new ArrayList(...));*/
如果不存在这样的对象,那么集合应该用(Collections.synchronizedList)list这种方式转换。
这个最好在开始创建之前就完成,以防止出现对列表的非同步访问:
该集合继承了AbstractList<E>类并实现了List<E>, RandomAccess, Cloneable, java.io.Serializable等接口,其中 RandomAccess可以保证我们可以随机对集合元素进行访问,这也是为什么AbstractList查询速度快的原因。(JDK对RandomAccess的官方解释:RandomAccess
是 List
实现所使用的标记接口,用来表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。)
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
基本属性:
private static final long serialVersionUID = 8683452581122892189L;//序列化ID
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;//数组大小
无参构造方法:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
有参构造方法(int类型参数):
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);
}
}
判断数值大小,大于0则初始化一个int大小的集合,,等于0则初始化为空集合,小于0抛异常
有参构造方法(collections集合):
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;
}
}
将集合转换为数组并赋给elementData,判断集合长度,若为0则初始化空集合,不为0则将数据完整赋给elementData
1、调整集合容量: trimToSize():
/**
* Trims the capacity of this <tt>ArrayList</tt> instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an <tt>ArrayList</tt> instance.
*/
/** 调整ArrayList实例的容量为当前list的大小,程序可以使用这个操作来最小化这个ArrayList实例的存储 */
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
该方法一般用于:扩容后会产生很多填充的null,这个方法可以将这些null都去掉以节省内存,具体做法就是
判断将扩容后数组的值复制到新创建的数组中(Arrays.copyOf()),保证空间的高效利用。
集合扩容,当添加元素时,会调用判断集合大小的函数,若为第一次添加,则初始化大小为10,若添加元素后容量大小满足要求,则直接添加元素,若添加元素后容量大小不满足,则需要扩容,扩展容量为原容量的1.5倍,若扩容后仍然不满足,则执行hugeCapacity(int minCapacity)函数,返回结果为Integer.MAX_VALUE 或者 MAX_ARRAY_SIZE,其中MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ;
以下方法为扩容部分代码:
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
添加操作时判断(size + 1)容量大小:
private void ensureCapacityInternal(int minCapacity) {
//判断是否需要扩容
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)/*与elementData比较*/) ;
与当前elementData对象比较:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//若为第一次添加,则容量初始化为10,否则返回size+1
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
判断集合容量大小,是否需要扩容:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//修改次数加一
// overflow-conscious code
if (minCapacity - elementData.length > 0)//如果增加元素后size+1大于elementData长度
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;
int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容为原来1.5倍(ps:位运算oldCapacity >> 1) = oldCapacity/2,即:newCapacity = 1.5倍oldCapacity )
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//扩容后容量
if (newCapacity - MAX_ARRAY_SIZE > 0)//扩容后容量不够
newCapacity = hugeCapacity(minCapacity);//执行hugeCapacity()
// minCapacity is usually close to size, so this is a win:
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;
}
2、ArrayList添加方法:add(E e),若为第一次添加,则容量初始化为10。
public boolean add(E e) {
//以下ensureCapacityInternal()见扩容部分
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;//将元素放在size+1位置
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//若为第一次添加,则容量初始化为10,否则返回size+1
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
添加(指定位置):
public void add(int index, E element) {
rangeCheckForAdd(index);//判断下标是否符合规范
//见扩容部分
ensureCapacityInternal(size + 1); // Increments modCount!!
//将插入位置后的元素统一向后挪一位
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));
}
3、ArrayList删除方法:
(1)remove(int index)。其中rangeCheck(index);首先判断下标是否合法,不合法则抛出异常;然后根据下标删除对应位置的元素,并将后面元素整体向前移一位,以空出最后一个null位置供垃圾回收器回收。
根据下标删除
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;
}
(2) remove(Object o):先遍历并找到相匹配数据下标,然后删除下标对应数据并返回true,否则返回false。
删除对象:
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;//否则返回false
}
fastRemove(int index):根据删除元素下标,将后面元素统一向删除元素位置方向移动一位,并将最后一个元素置为null,供垃圾回收器回收。
快速删除:
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
}
4、clear():遍历集合将所有下标对应元素置为null,并将size置为0,供垃圾回收器回收。
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
5、get(int index):根据元素下标,返回对应元素。
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
6、set(int index, E element):首先判断下标是否合法,不合法则抛出异常;合法则获取到对应下标的原始数据,并用新数据替换原始数据,并返回原始数据。
public E set(int index, E element) {
rangeCheck(index);//判断下标是否合法
E oldValue = elementData(index);//获取到原始数据
elementData[index] = element;//新数据赋值对应下标
return oldValue;//返回原始数据
}
7、contains(Object o):判断集合内是否包含已知对象 。此方法主要根据indexOf(Object o)判断,如果对象为null,则遍历集合查询是否有对应数据并返回其下标;不为null,则通过equals方法进行比对,判断是否有相同的元素值并返回对应下标;否则返回-1上层函数根据里层函数返回值确定是否存在。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)//null判断
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))//通过equals比较元素值
return i;
}
return -1;
}
8、iterator():方法返回一个内部类。内部类里三个常量分别是:cursor下一个元素索引,lastRet上一个元素索引,expectedModCount预期修改次数(对应modCount);类中提供一个无参构造函数Itr() {},以及我们常用的hasNext(),next(),remove()方法。
hasNext()方法很容易看出,只要下一个元素索引比集合size小,那么就存在下一个元素。
next()方法也不难得出,只要返回cursor对应索引的元素即可,然后将然后将cusor值赋值给lastRet,并将cursor+1,以此类推。但是要注意的是checkForComodification()方法,该方法用来判断集合在迭代过程中是否被修改,这在多线程操作集合时容易抛出并发修异常的原因。
remove()方法实际调用的是集合自身remove方法实现删除lastRet索引位置的元素,然后修改cusor及expectedModCount 。
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;//判断下一个索引是否等于集合容量,不等于则存在下一个元素
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();//判断修改次数是否合法,用来确定迭代过程中集合是否被修改
int i = cursor;
if (i >= size)//下一个元素索引大于size抛异常
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)//下一个元素索引大于elementData长度抛异常
throw new ConcurrentModificationException();
cursor = i + 1;//cursor加一
return (E) elementData[lastRet = i];//返回索引对应的元素并将索引赋值给lastRet
}
public void remove() {
if (lastRet < 0)//判断数值是否合法
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);//调用集合自身remove方法
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
以上是我基于JDK8的ArrayList源码学习,在这里记录下来,希望同大家交流学习,欢迎大家指出其中不足之处,谢谢大家。下期预告:Vector源码学习(基于jdk8)。
参考资料:ArrayList 源码分析,非常感谢!
传送门:
如果喜欢本文,请为我点赞,您的支持是我继续下去的动力,您也可以在评论区与我探讨或指正错误,最后别忘了关注一下我哦,谢谢。