世人都说ArrayList好是一个人见人夸的好姑娘:
她善解人意(可以动态的扩容),思路灵活(元素可随机访问),心思缜密(元素有序且可以重复)。这篇文章让我们来深入的了解她的内心世界。
上一篇总结了阅读源码的方法,这一篇开始阅读ArrayList的源码。同样,参考YSOcean的优质文章JDK1.8源码(五)——java.util.ArrayList 类。
本文的目录
- ArrayList的使用
- ArrayList的定义
- ArrayList的字段属性(五个)
- ArrayList的构造函数(三个)
- ArrayList的主要功能
- 增
- 删(根据索引删除、直接删除)
- 查(根据索引查找、根据元素查找)
- 改
- 使用ArrayList的遍历方法
- ArrayList类的其他方法(trimToSize方法)
ArrayList的使用
在阅读源码之前,我们要知道ArrayList是做什么的,如果精通使用它,那么我们就可以更好的理解它的源码了。所以在最后边,我贴出使用ArrayList的使用,方便理解。
ArrayList的定义
ArrayList 是一个用数组实现的集合,支持随机访问,元素有序且可以重复。(他的特点在于动态扩容)
- 实现了RandomAccess接口
这是一个标记接口,一般标记接口用于List实现,以表明它们支持快速(通常是恒定时间)的随机访问。该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或顺序访问列表时提供良好的性能。 - 实现了Cloneable接口
Cloneable 和 RandomAccess 接口一样也是一个标记接口,接口内无任何方法体和常量的声明,也就是说如果想克隆对象,必须要实现 Cloneable 接口,表明该类是可以被克隆的。 - 实现了Serializable接口
也是标记接口,表示能被序列化。 - 实现了List接口
这个接口是 List 类集合的上层接口,定义了实现该接口的类都必须要实现的一组方法
ArrayList的字段属性(五个)
- 集合的默认大小:private static final int DEFAULT_CAPACITY = 10;
- 空的数组实例:private static final Object[] EMPTY_ELEMENTDATA = {};
- 这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解数组第一次添加元素时数组膨胀多少:private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
- 存储 ArrayList集合的元素,集合的长度即这个数组的长度 :transient Object[] elementData;
- 1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空 ArrayList
- 2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
- 表示集合的长度:private int size;
ArrayList的构造函数(三个)
- 此无参构造函数将创建一个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 声明的数组,注意此时初始容量是0,而不是大家以为的 10。
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
- 初始化集合大小创建 ArrayList 集合。当大于0时,给定多少那就创建多大的数组;当等于0时,创建一个空数组;当小于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); } }
- 这是将已有的集合复制到 ArrayList 集合中去。
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的主要功能(增删查改)
增
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}
- 源码的代码流程:
- 添加元素之前,我们要首先调用 ensureCapacityInternal 方法来确定集合的大小,如果集合满了,则要进行扩容操作。
- (ensureCapacityInternal方法 )调用ensureExplicitCapacity方法
- (ensureExplicitCapacity方法)首先对修改次数modCount加一
- 判断minCapacity是否大于当前ArrayList内部数组长度,大于的话调用grow方法对内部数组elementData扩容
- (grow方法扩容)
- (ensureCapacityInternal方法 )调用ensureExplicitCapacity方法
- 添加元素
- 返回true
- 添加元素之前,我们要首先调用 ensureCapacityInternal 方法来确定集合的大小,如果集合满了,则要进行扩容操作。
- 实际样例流程
- ①、当通过 ArrayList() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
- ②、第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。
- ③、第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。
- ④、第 Integer.MAX_VALUE - 8 = 2147483639,然后 2147483639%1.5=1431655759(这个数是要进行扩容) 次添加元素,为了防止溢出,此时会直接创建一个 1431655759+1 大小的数组,这样一直,每次添加一个元素,都只扩大一个范围。
- ⑤、第 Integer.MAX_VALUE - 7 次添加元素时,创建一个大小为 Integer.MAX_VALUE 的数组,在进行元素添加。
- ⑥、第 Integer.MAX_VALUE + 1 次添加元素时,抛出 OutOfMemoryError 异常。
- 注意:能向集合中添加 null 的,因为数组可以有 null 值存在。
//ArrayList源码 Add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}
删
- 根据索引删除元素
- 首先判断给定个索引的范围,小于0或超过集合大小会抛出异常
- 调用快速删除方法,返回true
- 直接删除指定元素(删除第一次出现的元素,然后使用arraycopy进行数组自身拷贝)
- 如果为空,则遍历,找到第一个为空的元素,快速删除,返回true
- 如果不为空,遍历,通过equals方法判断是否相等,找到第一个出现的,快速删除,返回true
- 如果上边两个都没执行,返回false
- 前两者都用到了快速删除方法:fastRemove(int index)
- 计算index之后的数组的长度numMoved
- 通过arraycopy方法将老数组index+1之后长度为numMoved的元素拷贝到从老数组index开始的位置,并将最后一个元素设置为null便于垃圾回收
//ArrayList源码 remove方法
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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;
}
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
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;
}
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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
}
查
- 根据索引查找元素
- 判断索引范围
- 返回索引的值
- 根据元素查找索引
- 如果对象为空,遍历,找到第一个为空的对象,返回索引
- 如果对象不为空,遍历,找到第一个值为查找的对象的值,返回索引
- 没有找到,返回-1
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* Checks if the given index is in range. If not, throws an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an ArrayIndexOutOfBoundsException if index is negative.
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such 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;
}
改
- 判断索引范围
- 指定索引 index 处的元素替换为 element
- 返回原数组的被修改的那个元素
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
使用ArrayList的遍历方法
- 使用for
- 迭代器iterator
- 迭代器的变种 forEach
- 迭代器 ListIterator
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
//使用for
for(String str : list){
System.out.print(str + " ");
}
//使用迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.print(str+" ");
}
//使用foreach
for(String str : list){
System.out.print(str + " ");
}
//迭代器 ListIterator
ListIterator<String> listIt = list.listIterator();
//向后遍历
while(listIt.hasNext()){
System.out.print(listIt.next()+" ");//a b c
}
//向后前遍历,此时由于上面进行了向后遍历,游标已经指向了最后一个元素,所以此处向前遍历能有值
while(listIt.hasPrevious()){
System.out.print(listIt.previous()+" ");//c b a
}
其他方法
trimToSize方法:该方法用于回收多余的内存。也就是说一旦我们确定集合不在添加多余的元素之后,调用 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.
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
//ArrayList的使用
public static void main(String[] args) {
// 创建一个空的数组链表对象list,list用来存放String类型的数据
ArrayList<String> list = new ArrayList<String>();
// 增加元素到list对象中
list.add("Item1");
list.add("Item2");
list.add(2, "Item3"); // 此条语句将会把“Item3”字符串增加到list的第3个位置。
list.add("Item4");
// 显示数组链表中的内容
System.out.println("The arraylist contains the following elements: "
+ list);
// 检查元素的位置
int pos = list.indexOf("Item2");
System.out.println("The index of Item2 is: " + pos);
// 检查数组链表是否为空
boolean check = list.isEmpty();
System.out.println("Checking if the arraylist is empty: " + check);
// 获取链表的大小
int size = list.size();
System.out.println("The size of the list is: " + size);
// 检查数组链表中是否包含某元素
boolean element = list.contains("Item5");
System.out
.println("Checking if the arraylist contains the object Item5: "
+ element);
// 获取指定位置上的元素
String item = list.get(0);
System.out.println("The item is the index 0 is: " + item);
// 遍历arraylist中的元素
// 第1种方法: 循环使用元素的索引和链表的大小
System.out
.println("Retrieving items with loop using index and size list");
for (int i = 0; i < list.size(); i++) {
System.out.println("Index: " + i + " - Item: " + list.get(i));
}
// 第2种方法:使用foreach循环
System.out.println("Retrieving items using foreach loop");
for (String str : list) {
System.out.println("Item is: " + str);
}
// 第三种方法:使用迭代器
// hasNext(): 返回true表示链表链表中还有元素
// next(): 返回下一个元素
System.out.println("Retrieving items using iterator");
for (Iterator<String> it = list.iterator(); it.hasNext();) {
System.out.println("Item is: " + it.next());
}
// 替换元素
list.set(1, "NewItem");
System.out.println("The arraylist after the replacement is: " + list);
// 移除元素
// 移除第0个位置上的元素
list.remove(0);
// 移除第一次找到的 "Item3"元素
list.remove("Item3");
System.out.println("The final contents of the arraylist are: " + list);
// 转换 ArrayList 为 Array
String[] simpleArray = list.toArray(new String[list.size()]);
System.out.println("The array created after the conversion of our arraylist is: "
+ Arrays.toString(simpleArray));
}