Java容器
二、List
List:可重复、有序(存取顺序相同)
List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。
2.1 ArrayList
2.1.1 类结构
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
- ArrayList 继承了 AbstractList,实现了List接口。
- ArrayList 实现了 RandomAccess接口,表明具有快速随机访问功能。RandomAccess 接口只是标识,并非实现了 RandomAccess 接口才具有快速随机访问功能。
- ArrayList 实现了 Cloneable接口,即实现克隆功能。
- ArrayList 实现了 Serializable接口,表示支持序列化。
2.1.2 属性
// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//共享的空数组实例,用于默认大小的空实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList元素存储到的数组缓冲区。
transient Object[] elementData; // non-private to simplify nested class access
//ArrayList包含的元素数量
private int size;
2.1.3 构造方法
ArrayList 的底层实现了一个数组,这个数组类型是泛型,表示什么元素类型都可以存放。
ArrayList 的构造方法有以下三种:
-
List<E> list1 = new ArrayList<E>(int n);
携带数字参数,表示初始化容量为n的空列表;如果 n == 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); } }
-
List<E> list2 = new ArrayList<E>();
不携带参数,表示初始化容量为10的空列表
//构造一个初始容量为 DEFAULT_CAPACITY(默认为10) 的空列表 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
-
List<E> list3 = new ArrayList<E>(Collection<> list);
初始化包含一个集合的列表
//构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。 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; } }
2.1.4 常用方法
- 增加元素
-
add(E e);
:添加一个元素到列表的末尾public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
-
e 是传入的参数,elementData 是当前的数组,size 是当前数组的长度
-
数组进行扩容,增加一个空间
-
扩容完成继续执行,将元素 e 存放在第 size+1 个位置
-
存放完成后数组长度+1
-
-
add( int index, E element );
:在指定的位置添加元素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++; }
- index 是指定位置,element 是添加的元素
- rangeCheckForAdd 判断index是否超出数组范围,超出则抛出异常
- 对数组进行扩容,增加一个空间
- 扩容完成进行元素的添加,使用了系统本地方法进行数组的移动
- 存放完成后数组长度+1
- 删除元素
-
remove(int index);
:删除列表中指定位置的元素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; }
- index 是指定位置
- rangeCheck 判断index是否超出数组范围,超出则抛出异常
- 取出下标对应的值,计算将要移动的数量
- 如果移动的数量大于0,则使用了系统本地方法进行数组的移动
- 存放完成后数组长度-1,并将最后一个空间设置为空,返回旧值
-
remove(Object o);
:删除列表中第一个出现o的元素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; }
- 如果 o 为空,则遍历数组,找到第一个空元素的下标
- 否则,遍历数组,找到第一个为 o 的下标
- 没有找到则返回 false
- 如果找到,则执行 fastRemove 方法,将该位置的元素删除,并使用本地方法移动数组
-
clear();
:清除所有的元素,没有返回类型public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
- 遍历整个数组,将每个元素置为空
-
更新元素
set(int index, E element)
:用element替换index位置上的元素public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
- 调用 Object 的检查下标方法,判断下标是否小于0或是否大于数组长度
- 直接将 index 下标的元素转化为传进来的下标的元素
- 最后返回原来的元素
-
查询元素
-
get(int index)
:返回指定索引的元素public E get(int index) { rangeCheck(index); return elementData(index); }
- 判断下标是否有效,有效则直接返回数组的第 index 个元素
-
indexOf(Object o)
:返回此列表中指定元素的第一次出现的索引,如果列表不包含此元素,返回-1public 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; }
- 如果 o 为空,则遍历数组,找到第一个空元素的下标
- 否则,遍历数组,找到第一个为 o 的下标
- 没有找到则返回 -1
-
lastIndexOf(Object o)
:返回此列表中指定元素的最后一次出现的索引,如果列表不包含此元素,返回-1public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; }
- lastIndexOf 则与 indexOf 相反,从尾到头找元素 o 的位置,最终返回最后一次出现 o 的位置
- 其他操作
size()
:返回此列表的元素数sort(Comparator c)
:按照指定的排序规则排序subList( int from , int to )
:返回从from到to之间的列表toArray()
:将列表转化为数组
2.1.5 扩容机制
-
通过
ensureCapacity(int minCapacity)
方法判断扩容量public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }
- 如果当前数组不是默认大小的空实例,则最小扩容量为0
- 如果当前数组是默认大小的空实例,则最小扩容量为默认大小10
- 如果要求扩容量 大于 最小扩容量,则调用
ensureExplicitCapacity(minCapacity)
-
通过
ensureExplicitCapacity(int minCapacity)
方法判断是否需要扩容private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
列表所需的最小长度大于当前列表的长度,则调用
grow(minCapacity)
方法 -
通过
grow(int minCapacity)
方法,结合hugeCapacity(int 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); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
-
定义新列表的长度为原列表的1.5倍( (oldCapacity >> 1) 为右移运算,右移一位相当于除以2)
-
如果新数组的长度依旧小于列表所需的长度
-
- 如果原列表为空,则返回 DEFAULT_CAPACITY 和列表所需长度的最大值,DEFAULT_CAPACITY 默认为10
- 如果新列表的长度小于0,则报 OOM 异常(内存溢出异常,将会在JVM中学习)
- 否则返回列表所需长度
-
判断新数组是否比 MAX_ARRAY_SIZE 小(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8)
-
- 是,返回新数组长度
- 否,进行 hugeCapacity 判断,返回 MAX_ARRAY_SIZE 或是 Integer.MAX_VALUE
-
扩容操作需要调用
Arrays.copyOf()
把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
-
2.1.6 Fail-Fast
protected transient int modCount = 0;
-
java.util.ArrayList 不是线程安全的,
-
modCount 用来记录 ArrayList 结构发生变化的次数。
-
结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
-
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出ConcurrentModificationException。
-
在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 ArrayList。
-
遍历那些非线程安全的数据结构时,尽量使用迭代器
2.1.7 线程安全
ArrayList是线程不安全的。在其迭代器iteator中,如果有多线程操作导致modcount改变,会执行fast-fail。抛出异常。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
2.1.8 优缺点
优点:
- ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快
- ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已
缺点:
- 删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
- 插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
因此,ArrayList比较适合顺序添加、随机访问的场景。