创建ArrayList
创建 ArrayList 时,一般是直接创建,或者是直接将实现了 Collection 接口的集合类传入进行初始化创建
// 1 直接new
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
System.out.println(Arrays.toString(list.toArray()));
// 2 创建时直接添加元素 实现了collection接口的类
ArrayList<String> list1 = new ArrayList<>(list);
System.out.println(Arrays.toString(list1.toArray()));
HashSet<String> hashSet = new HashSet<>();
hashSet.add("C");
ArrayList<String> list2 = new ArrayList<>(hashSet);
System.out.println(Arrays.toString(list2.toArray()));
添加元素 -> ArrayList 的扩容机制
首先看 ArrayList 的 add 方法,当添加元素时,首先是通过 ensureCapacityInternal 方法进行
增量判断,判断后进行赋值以及返回添加元素成功。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!这里是增量机制
elementData[size++] = e;
return true;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final int DEFAULT_CAPACITY = 10;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
而对于 ensureCapacityInternal 方法来说,首先通过 calculateCapacity 方法来计算容量。
如果 elementData(存储数据的数组) 为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 数组,
则说明 ArrayList 是无参刚创建的,所以使用默认的容量(10)与实际数量 minCapacity(size + 1)
中最大值。通过 ensureExplicitCapacity 方法验证容量与 elementData 实际长度的大小情况,
通过 grow 方法进行扩容。同理 addAll 方法也是同样的机制。只不过多了复制数组的一步。
成员变量 modCount 则是后面再说,下面先说 grow 方法是怎么增容的。
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);
}
grow 方法增容详解。首先根据 ArrayList 中元素的数量 + (数量 >> 1)也就大约是容量的1.5倍,
所以 newCapacity - minCapacity < 0 这个条件只有空数据第一次添加的时候能成立,
让 elementData 初始化为长度10的数组,之后都不成立。在上面的 ensureExplicitCapacity
方法中 minCapacity 是 size + 1,所以只有当 size == elementData.length 时,才会
调用 grow 方法进行扩容,将容量 增扩为 oldCapacity + (oldCapacity >> 1),约是1.5倍。
删除元素 -> ArrayList 删除元素时所需考虑问题
删除方法一般使用 remove 方法,其他较为特殊的方法暂不讨论。
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;
}
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 void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
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
}
通过 index 删除时先进行下标范围检查,然后获取到删除元素,移动原数组。
为什么是 size - 1 - index,是因为 index 从0开始。numMoved 不判断=0的情况
则是因为=0时,删除元素是末尾元素,直接置 null 就好。elementData[--size] = null
就是末尾元素置 null。其他情况则移动元素通过 object 删除时则涉及到 fastRemove 方法,
原理和删除 index 的情况一致。这里判断了 o == null是因为 o 为 null 时调用
equals 方法时报 NPE,所以要判断。而 equals 方法判断相等时,为了实现现实中的
‘相等概念’需要重写 equals 和 hashCode 方法。否则 equals 只是判断是否为同一对象。
ArrayList<String> list = new ArrayList<>();
list.add("张三");
list.add("张三");
list.add("赵四");
list.add("王五");
for (int i = 0; i < list.size(); i++) {
if ("张三".equals(list.get(i))) {
list.remove(list.get(i));
System.out.println("张三已被删除");
}
}
for (String str : list) {
if ("张三".equals(str)) {
list.remove(str);
System.out.println("张三已被删除1");
}
}
/*
张三已被删除
张三已被删除1
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at com.wuhaitao.base.collection.List.ArrayListInfo.main(ArrayListInfo.java:62)
*/
一般情况下,集合中删除元素时,我们都是通过遍历去删除元素。所以这里存在一些问题。
由于有fail-fast 机制,这个机制意思是:一旦检测到可能会发生错误,就立马抛出异常,
程序将不再往下执行。这个机制并不是集合中特有的,只是在集合中很容易实现,在这里
介绍很方便。比如上面的例子,看着没有任何问题,但是一旦运行起来就会报错,抛出了
ConcurrentModificationException 异常。并且还调用了911行的checkForComodification
方法。明明我们只调用了 remove 方法,remove 方法中也没有调用
checkForComodification方法,那么这些情况是为什么呢?另外,为什么 fori 没有
问题的删除了第一个‘张三’,但是为什么后面不再删除了?在 fori 的情况下,
当第一个元素被删除时,集合本身元素位置发生变化,删除完第一个‘张三’后:i = 1,但是
第二个‘张三’被移动到了 index = 0 的位置,所以下一次 get(i) 时,获取到的是被移动到
index = 1 的‘赵四’第二个‘张三’,因此被巧妙地跳过了。所以,当使用 fori 进行删除时,
虽然可以进行删除操作,但存在数据问题。我们接着来看增强 for。
String[] arr = new String[]{"张三", "张三", "赵四", "王五"};
String[] var2 = arr;
int i = arr.length;
for(int var4 = 0; var4 < i; ++var4) {
String s = var2[var4];
System.out.println(s);
}
List<Integer> list = new ArrayList();
for(i = 0; i < 5; ++i) {
list.add(i);
}
Iterator var7 = list.iterator();
while(var7.hasNext()) {
Integer integer = (Integer)var7.next();
if ("张三".equals(str)) {
list.remove(str);
}
}
增强for是一个语法糖。所以在编译代码时,它会被解析成基础代码。如果是数组使用增强for
则解析过后就是 fori 的形式。如果是实现了 Iterable 的集合,则会解析成 iterator
迭代器的形式遍历集合。所以,在之前 ArrayList 的增强 for 中,会被解析成 iterator
迭代器形式遍历。所以会调用 iterator,hasNext 方法以及 next 方法。但是呢!
这里有一个我们非常需要注意的地方,删除方法我们是调用的 ArrayList 的 remove 方法。
我们接下来看下 Itr 源码。(注意:泛型也是语法糖,也会被解析成基础代码,但这里不做讲解)
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在 ArrayList 实现的 iterator 方法中,返回了内部类 Itr 实例对象。Itr 则实现了
Iterator 接口,所以会重写 hasNext,next 方法。在 hasNext 中判断了指针是否还指向元素。
而在 next 方法中掉了用我们前面提到的报错方法 checkForComodification,这个方法抛出了
ConcurrentModificationException 异常。而这个异常是根据 modCount != expectedModCount
这个条件达成的。好,现在我们就要来聊聊这个一开始就提到的 modCount 了。
protected transient int modCount = 0;
// add 方法调用
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// remove 方法调用
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
}
首先 modCount 并不在 ArrayList 中定义的,而是在 AbstractList 中定义的,其记录的是
集合被修改的次数。所以,在 add,remove 等方法中会见到 modCount++ 这个操作。在‘张三’
的例子增强for中,创建 Itr 内部类时 int expectedModCount=modCount,expectedModCount
这个内部类成员变量已被赋值,因为做了四次 add、一次 remove 操作, 所以
expectedModCount = modCount = 5。当增强 for第一次遍历时,checkForComodification
方法判断成功,删除第二个‘张三’(第一个‘张三’是 fori 中删除)上面说过,增强 for 中调用的是
ArrayList的删除方法。所以这时modCount++变成了6,所以在下一次删除时,
checkForComodification 方法判断失败,所以报了 ConcurrentModificationException 异常。
那么我们来看正常的 iterator 迭代器遍历。
List<String> list = new ArrayList<>();
list.add("张三");
list.add("张三");
list.add("赵四");
list.add("王五");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if ("张三".equals(next)) {
iterator.remove();
System.out.println("张三已被删除1");
}
}
// itr 的删除源码
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
在迭代器遍历删除时,我们调用的时 Itr 内部类的删除方法,我们明显看到了
expectedModCount = modCount,这也就解释了为什么迭代器遍历不会报错而增强 for 会报错
最后,阿里守则中,也明确提到了,不要在 foreach 循环里删除元素。若需删除元素,
请使用 Iterator 方式。在并发操作时,需对 Iterator 对象加锁。
查找元素 -> 如果有序,可以二分查找提高效率
如果要正序查找一个元素,可以使用 indexOf 方法;如果要倒序查找一个元素,可以使用
lastIndexOf 方法contains方法可以判断 ArrayList 中是否包含某个元素,内部调用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;
}
public 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;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
如果 ArrayList 中的元素是经过排序的,就可以使用二分查找法,效率更快
ArrayList<String> copy = new ArrayList<>(alist);
copy.add("1");
copy.add("2");
copy.add("2");
copy.add("4");
Collections.sort(copy);
System.out.println(copy);
int index = Collections.binarySearch(copy, "b");
System.out.println(index);
更新元素
来看一下 set() 方法的源码
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
更新元素时,先对指定的下标进行检查,看是否越界,然后替换新值并返回旧值
(。・∀・)ノ゙嗨!如果这篇文章小小帮助到你,可以帮小弟我点个赞呀。点赞是对我最大的支持与认可,感谢!