从何说起呢?ArrayList简单易用及强大的功能,注定了我们使用java语言编程时,用得最多的容器类非它莫属。正是因为它的简单,很多并没有认真了解过它,对它的理解并不深刻。也正是因为它是最常用,最基础的容器类,所以就从ArrayList说起吧!本文不会逐一去解释ArrayList的所有方法,仅挑选当中几个我们平时容易忽略的方面进行说明。
ArrayList,顾名思义,这个list是通过Array进行存储。那么它占用的内存空间是怎么进行分配,扩展以及收缩的呢?
首先,每当JVM执行以下这条语句时:
ArrayList list = new ArrayList();
分配给这个list的初始空间大小(DEFAULT_CAPACITY
)为10。
每当往list中添加对象时,即调用add
或addAll
时,为了保证空间足够使用而不至于抛出异常,都需要对空间大小进行判断,如果现有空间足够大,那么直接插入,否则需要先扩展内存。ArrayList中判断内存是否足够以及进行内存扩展的方法有:
//保证空间至少为minCapacity
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//当所需最小空间大于现有空间大小时,需要进行内存扩展
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//计算新的内存大小
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果计算所得内存大小仍小于所需最小值,则将新的内存大小设为所需最小值,
//一般只有addAll会用到
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新的大小超过最大可分配空间,则将其设置为最大可分配空间
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//内存中开辟一片新的大小为newCapacity的空间,并将现有内容拷贝过去
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;
}
从上面这段代码,我们可以得出结论:
Integer.MAX_VALUE
,即 2147483647。如果逐个往list中添加对象,那么list的空间大小呈阶梯状增长,如下图所示:
值得注意的是,当我们调用ArrayList的remove
,removeAll
以及retainAll
方法时,这些方法都是在原本的内存空间上进行操作(下文会详述)。因此,如若我们删除一些元素后,空间并没有得到释放。如果我们需要对空间进行回收,那么ArrayList提供了以下方法供我们使用。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
此方法可以将list占用内存大小压缩到list中元素个数。同样也是通过Arrays.copyof
方法将元素拷贝到一片新的内寸空间,老的空间由gc回收。
此处专门设章节写这两个方法主要原因是它们的实现较为巧妙,值得一说。
如果是我们自己实现removeAll,最简单的方法就是循环调用remove方法,将需要删除的元素逐个删除;而retailAll则逐个查找需要保留的元素,并拷贝到一片新的空间上。这也许是最朴素,最简单粗暴的方法了。
JDK如何巧妙实现呢,先看看源码:
public boolean removeAll(Collection> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
public boolean retainAll(Collection> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
private boolean batchRemove(Collection> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
从源码可以看出,这无论是removeAll还是retainAll,它们都通过调用batchRemove这一个方法实现。它们的实现方式类似于jvm中垃圾回收机制中的整理-清除方法,即先将需要保留的元素紧凑地移到一块儿,剩下的即为可以删除的元素。整个删除和保留过程如下图所示:
这种方法非常快速并节约地删除或保留元素,较为巧妙。
toArray也没有什么神奇之处,无非就是返回ArrayList中自己保存的数组对象,这里单独描述,只是为了说明这个方法是安全的,我们无需担心对得到的数组进行操作会对原本的arraylist对象产生影响,它用于桥接基于数组和基于容器的两种API模式。源码如下:
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
源码非常简洁,可以看出返回的数组对象是通过Arrays.copyOf拷贝出来的一份对象,和原本的对象不属于同一内存空间。因此,得到的数组对象是独立的,可以放心使用。
java 8 中,ArrayList新增方法如下表所示:
方法 | 参数 | 描述 |
---|---|---|
forEach | Consumer < ? super E> action | 遍历每个元素做指定操作 |
spliterator | 返回一个Spliterator | |
removeIf | Predicate< ? super E> filter | 判断条件是否满足,满足则删除 |
replaceAll | UnaryOperator< E > operator | 根据operator进行替换 |
sort | Comparator< ? super E> c | 对list中元素根据Comparator指定规则进行排序 |
对于新增方法,我们将从如何使用它们的角度出发,进行描述。
首先我们定义一个员工类(Employee):
public class Employee {
private String name;
private String Dept;
private Integer age;
private Double salary;
//getter and setter
......
}
for (Employee emp:employees) {
System.out.println(emp.getName());
}
如今,我们可以这么做:
employees.forEach(e -> System.out.println(e.getName()));
也可以这么做:
employees.forEach(new Consumer() {
@Override
public void accept(Employee t) {
System.out.println(t.getName());
}
});
ArrayListSpliterator
,此处不做具体介绍;如需了解可以参考:这儿 employees.removeIf(e -> e.getDept().equals("IT") && e.getSalary() > 30000d);
employees.replaceAll(new UnaryOperator() {
@Override
public Employee apply(Employee t) {
if (t.getDept().equals("IT")) {
t.setDept("RD");
}
return t;
}
});
Collections.sort(employees, new Comparator() {
@Override
public int compare(Employee o1, Employee o2) {
return (int) (o2.getSalary() - o1.getSalary());
}
});
现在可以直接进行排序:
employees.sort((a, b) -> a.getSalary().compareTo(b.getSalary()));
本文主要分析了ArrayList几个常常容易忽略方法的源码,总结了java8中ArrayList新增的方法。仅当学习,记录。