JAVA集合学习之ArrayList
ArrayList类中的属性
属性 | 作用 | 重点 |
---|---|---|
private static final int DEFAULT_CAPACITY = 10 | 默认元素数组长度为10 | 当使用默认的无参构造函数创建一个ArrayList对象时,并不会在生成对象时就初始化一个为10的元数组 |
private static final Object[] EMPTY_ELEMENTDATA = {} | 用于赋值给元素数组长度为0的集合对象 | 当初始化ArrayList对象时,直接或间接指明了集合长度为0时,把该数组赋值给元素数组。(直接:使用new ArrayList(0);间接:使用一个长度为0的集合赋值给ArrayList进行初始化) |
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {} | 使用无参构造函数时,用于实例化元素数组 | 对应上面使用无参构造函数时,并不会立即初始化元素数组的容量为10,而是直接把该数组赋值给元素数组 |
transient Object[] elementData; | 元素数据数组(元素数组),这个Object数组就是真正存储对象的地方 | transient:该关键字修饰的属性将不被序列化。因为元素数组会预留一些容量,这些空间实际上是没有数据的,所以使用元数组就行序列化会浪费空间和时间。在ArrayList中提供了readObject和writeObject来进行序列化和反序列化。 |
private int size; | 集合的实际存储的元素的数量 |
在这些属性中定义了两个空数组,区别就在于是否在初始化ArrayList时指定了容量是空还是0,是空就使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为’0’ 就使用 EMPTY_ELEMENTDATA ,而这两种初始化的巧妙之处就在于可以按照不同的基数进行ArrayList自动扩容。
构造函数
构造函数 | 简介 |
---|---|
public ArrayList(int initialCapacity) | 指定元素数组容量的构造函数。 |
public ArrayList() | 空参构造函数,当第一个元素添加到集合中时,元素数组会初始化为长度为10的数组。 |
public ArrayList(Collection<? extends E> c) | 通过集合进行初始化集合。 |
如果在已经知道集合中存入的元素数量的情况下,优先使用指定容量的构造函数就行ArrayList的初始化,这个可以保证集合不会频繁的扩容数组,ArrayList的扩容是吧原来的数组拷贝的到更大的数组中,是很消耗资源的,所有当元素的数量已知或者知道大概的数量时使用指定初始容量的构造函数可以提高性能。
ArrayList(int initialCapacity)
/**
* 当初始化容量大于0时就直接实例化一个对应容量的Object数组;
* 但是如果指定的初始容量为0就把 EMPTY_ELEMENTDATA 定义的静态数组赋值给元素数组;
* 如果为负数就会抛出异常。
*/
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() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList(Collection<? extends E> c)
/**
* 这个进行类型判断的原因:当c的类型是Arrays.asList(...), c.toArray返回数组是该
* 数组的实际类型,而不是Object数组。所以需要把数组装换为Object数组。
*
*/
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;
}
}
为什么会有两个值为空数组的静态变量?
1.先了解这两个静态变量在什么时候被使用了
当使用无参构造函数时就会把 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementData 。
当初始化ArrayList的时候初始容量为0时把 EMPTY_ELEMENTDATA 赋值给 elementData
2.两种初始化的分水岭
使用两种方法初始化后端ArrayList集合中的elementData其实都是空数组,但是为什么会使用两个一样的空数组?通过源码进行解析。
/*当我们初始化完成一个ArrayList后向集合中添加一个元素*/
/**第一步
* 添加元素方法:
* size=0
* 进入ensureCapacityInternal(1)方法中
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**第二步
* 这个方法就是确保elementData的容量足够添加一个元素。
* minCapacity=1
* 进入calculateCapacity(elementData, minCapacity)方法中
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(
calculateCapacity(elementData, minCapacity)
);
}
/**第三步
* 通过传入的最小容量和当前的elementData的长度进行比较。
* minCapacity=1
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果使用的无参构造函数就会进入if中
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果插入的元数据小于默认10就返回10,否则返回插入元数据个数
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果不是使用的无参构造函数就直接返回该最小容量
return minCapacity;
}
通过第三步就可以看出使用 new ArrayList() 初始化的elementData进入了if语句内,返回的是默认的初始容量为10,而使用的是 new ArrayList(0) 初始化的返回的容量就是 1(添加第一个元素时)。
- 继续向下走
/**第四步
* 当前的elementData的容量是否满足最小容量,如果不满足进行扩容。
* 使用的无参构造函数:minCapacity=10
* 使用的是指定集合容量为0:minCapacity=1
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//如果插入的元数据个数大于当前数组大小就行数组扩容
if (minCapacity - elementData.length > 0)
//扩容数组
grow(minCapacity);
}
/**第五步
* 对elementData进行扩容。
* 使用的无参构造函数:minCapacity=10
* 使用的是指定集合容量为0:minCapacity=1
*/
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);//进行数组的扩容
}
ArrayList的扩容机制 : int newCapacity = oldCapacity + (oldCapacity >> 1); 就是在原来的容量上增加原来的50%。
通过扩容我们得到了两个容量大小分别的10和1的ArrayList集合,形成这个原因是因为参入的扩容的基数不同,导致基数不同是因为在初始化时使用了两个变量名不同的空数组。
3.扩容基数不同带来的差异
基数 | 扩容结果 |
---|---|
10 | 10–>15–>22–>33… |
1 | 1–>2–>3–>4–>6–>9… |
通过两个基数的扩容结果可以明显的发现两个的步长差距十分的大。所有当使用ArrayList存储少量的元素数据是通过指定集合的容量的方式进行初始化可以提高空间的利用率。但是如果是大量的数据要添加进集合就要把初始化容量(基数)适量的增大,从而减少集合的频繁的扩容而影响性能。当没对应添加的元素数量没有一个明确的数量时选择默认的10为基数也是个不错的选择。
常用的方法
方法 | 说明 |
---|---|
添加 | |
boolean add(Element e) | 增加指定元素到链表尾部 |
void add(int index, Element e) | 增加指定元素到链表指定位置 |
boolean addAll(Collection<? extends E> c) | 将指定collection中的所有元素插入到ArrayList中 |
boolean addAll(int index, Collection<? extends E> c) | 从指定的位置开始,将指定collection 中的所有元素插入到ArrayList中 |
删除 | |
void clear() | 从链表中删除所有元素 |
E remove(int index) | 删除链表中指定位置的元素,并返回该元素 |
boolean removeAll(Collection<?> c) | 移除ArrayList中Collection所包含的所有元素 |
boolean remove(Object o) | 移除ArrayList中首次出现的指定元素(如果存在则移除并返回true,否则返回false) |
boolean removeIf(Predicate<? super E> filter) | 根据重写Predicate类的test方法选择删除集合中的元素 |
查询 | |
E get(int index) | 获取链表中指定位置处的元素 |
Object[] toArray() | 获取一个装满元素的数组 |
boolean contains(Object o) | 如果链表包含指定元素,返回true |
int indexOf(Object o) | 返回元素在链表中第一次出现的位置,如果返回-1,表示链表中没有这个元素 |
int lastIndexOf | 返回元素在链表中最后一次出现的位置,如果返回-1,表示链表中没有这个元素 |
boolean isEmpty() | 返回true表示链表中没有任何元素. 判断逻辑是size == 0 |
int size() | 返回链表长度(链表包含元素的个数) |