今天回头研究一下 java 中的列表 ArrayList
,它表示用数组来存储一系列的对象,并且支持 �List
的特性.
我们今天研究一下它的基础属性, 添加方法, 以及与 lambda 相关的部分.
属性
默认容量
表示新建一个 ArrayList
的初始化空间.
private static final int DEFAULT_CAPACITY = 10;
空列表
提供两个空的数组列表,供代码中使用以便于给参数赋值.
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
存储数组
在数组泛型中存储的数据,用数组来实现.
transient Object[] elementData; // non-private to simplify nested class access
列表大小
size
表示列表所包含的元素个数,默认是0
private int size;
扩容
其实如果提到了 ArrayList
我们很多时候都会最先提到 ArrayList
的扩容方法,那么我们这边就先研究一下有关扩容方法的一串方法链
ArrayList
有一个名为 grow
的匿名方法,顾名思义,就是扩容,让我们来一起看一下(核心):
private void grow(int minCapacity) {
//我们取到了原有的容量
int oldCapacity = elementData.length;
//新的容量暂时等于原有的容量的 大约 一点五 倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//取 新容量 和 传入的最小容量 中的较小者(为了更小的内存占用)
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//MAX_ARRAY_SIZE的值是最大的 Integer - 8 (2147483639)
if (newCapacity - MAX_ARRAY_SIZE > 0)
/*
综合起来我们可以知道 minCapacity 是大于 2147483639 的
下面方法会返回一个 合适 的较大数
*/
newCapacity = hugeCapacity(minCapacity);
//方法获取了一个新的数组,长度为新的长度,前面的元素不变, 延长的部分使用默认值
elementData = Arrays.copyOf(elementData, newCapacity);
}
在这其中我们调用了 hugeCapacity
方法:
private static int hugeCapacity(int minCapacity) {
//负数抛出内存溢出异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//返回 MAX_VALUE 或者 MAX_VALUE-8
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
�所以我们得出了,在列表长度没有变的特别巨大的情况下,新的数组长度一般情况下会变成原有长度的1.5倍
所以当我们在调用 add
方法的时候,会发生如下事情:
public boolean add(E e) {
//调用了增加长度的方法,具体增加到多少其实不重要
ensureCapacityInternal(size + 1);
//给 size 的下一个标记位赋值
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) {
//长度不对的时候有用,但是在 add 环境下只是传值给 grow
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
所以我们会发现其实核心代码还是 grow
方法,其它的都是处理一些特殊情况的.
查找
在 ArrayList
中有几个类似的方法,他们都有一个特性,就是能够从列表中找到一个特定的元素并且执行某些操作, 例如:
public boolean contains(Object o);
public int indexOf(Object o);
public int lastIndexOf(Object o);
public boolean remove(Object o);
这些方法都非常类似,所以我们现在研究一下 indexOf
方法:
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;
}
小结
很显然我们能够得到结论:
- 我们在寻找对象的时候会用 for 循环遍历数组中的每一个元素
- 寻找对象的时候我们使用的是
equals
方法
函数方法
在 ArrayList
中有几个方法,它以函数接口作为参数, 分别是如下方法:
public void forEach(Consumer<? super E> action);
public boolean removeIf(Predicate<? super E> filter);
public void replaceAll(UnaryOperator<E> operator);
public void sort(Comparator<? super E> c);
以下我们会对上面几个方法举例使用或者进行代码分析:
forEach
forEach
方法要求传入一个 consumer, 然后依据我们传入的 lambda 去对列表的每一个元素进行一次消费行为.
源码
public void forEach(Consumer<? super E> action) {
//函数的非空校验
Objects.requireNonNull(action);
//记录当前操作次数,获取当前的数据和大小
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
//对每一个元素进行循环,并且执行对应的消费函数
//modCount 是用来防止多线程的时候出现问题的, 这个是一个�确保相对线程安全的方案
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
//一旦在遍历结束之后发现中途列表发生了变化,那么抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
栗子
@Test
public void base05(){
List<Integer> arrayList = Arrays.asList(1,2,3,4,5);
arrayList.forEach(i -> {
System.out.println(i);
});
}
removeIf
removeIf
方法要求传入一个� predicate 并且将返回值为 true 的移除方法.和 stream 的 filter 有些类似.
栗子
@Test
public void base06(){
List<Integer> arrayList = Arrays.asList(1,2,3,4,5);
ArrayList<Integer> newList = new ArrayList<Integer>(arrayList);
newList.removeIf(i -> {
return i % 2 == 0;
});
newList.forEach(i -> {
System.out.println(i);
});
}
replaceAll
replaceAll
方法接受一个 UnaryOperator 函数,返回相同类型的数据来替换原有数据
源码
public void replaceAll(UnaryOperator<E> operator) {
//非空校验
Objects.requireNonNull(operator);
//记录当前操作次数,获取当前的数据和大小
final int expectedModCount = modCount;
final int size = this.size;
//对每一个元素进行循环,并且执行对应的函数
//modCount 是用来防止多线程的时候出现问题的, 这个是一个�确保相对线程安全的方案
for (int i=0; modCount == expectedModCount && i < size; i++) {
elementData[i] = operator.apply((E) elementData[i]);
}
//一旦在遍历结束之后发现中途列表发生了变化,那么抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
栗子
@Test
public void base07(){
List<Integer> arrayList = Arrays.asList(1,2,3,4,5);
ArrayList<Integer> newList = new ArrayList<Integer>(arrayList);
newList.replaceAll(i -> {
return 10 - i;
});
newList.forEach(i -> {
System.out.println(i);
});
}
sort
sort
方法需要传入一个 comparator 接口对象的函数,表示用来比较两个数的大小的函数
源码
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
栗子
@Test
public void base08(){
List<Integer> arrayList = Arrays.asList(934,2,13,43,5);
ArrayList<Integer> newList = new ArrayList<Integer>(arrayList);
newList.sort((a,b) -> {
if(a > b){ return 1;}
else {return -1;}
});
newList.forEach(i -> {
System.out.println(i);
});
有关 ArrayList
的学习就到这里.
之后我们会继续学习 LinkedList
的相关内容.
欢迎关注我的博客: 既然来了就坐坐吧
小站刚开始起步,欢迎您的驾到.