构造函数
有有参构造和无参构造两种。
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);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从上面可知,当initialCapacity大于0的时候,直接初始化一个大小为initialCapacity的Object数组。initialCapacity为0的时候,则将空的Object数组赋值给elementData。
在这里需要区别的是,数组的长度和list的长度,在面试的时候,问过一个这样的问题,就是在有参初始化list的时候,数组的长度有没有变化?list的长度有没有变化?
ArrayList<Integer> list = new ArrayList(10);
System.out.println(list.size());
list.set(2,1)
第一行返回的是0,第二行则抛出IndexOutOfBoundsException异常。所以在初始化ArrayList的时候,数组的长度会改变,但是list的长度返回的是size。只有当add()或者remove()的时候size才会变化。
add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
在add方法中,会先调用ensureCapacityInternal()。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
会与DEFAULT_CAPACITY先进行比较,取最大值,然后紧接着调用ensureExplicitCapacity更新modCount的值,并判断是否需要扩容。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
在ensureExplicitCapacity()主要是判断是否进行扩容处理,当传入的是数据大于数据的长度的时候,则需要扩容。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//更新为旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//检查新容量的大小是否小于最小需要容量,如果小于那旧将最小容量最为数组的新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
////如果新容量大于MAX_ARRAY_SIZE,使用hugeCapacity比较二者
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将原数组中的元素拷贝
elementData = Arrays.copyOf(elementData, newCapacity);
}
下面来看看hugeCapacity();
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//对minCapacity和MAX_ARRAY_SIZE进行比较
//若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
//若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
这里需要注意MAX_ARRAY_SIZE,有些虚拟机会在数组中保存 header words 头部字。当虚拟机大于 MAX_ARRAY_SIZE (Integer.MAX -8 )就容易OOM。
所有有这个判断的目的是尽量减小OOM。
进行一系列处理之后,然后将添加的值放入到对应的位置,并size++;
add(int index, E element)与add(E element)最大的区别是add(int index, E element)需要进行数组的拷贝。调用的是arraycopy()
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
ArrayList所谓的查询快增删慢就是体现在这里。
remove()
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
//如果这个值大于0 说明后续有元素需要左移(size=index+1)
//如果是0说明被移除的对象就是最后一位元素(不需要移动别的元素)
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
先进行size检测,并对modCount进行修改,然后也是进行数组的拷贝处理。
另外还有一个remove(Object o)方法,区别就是先根据值找到对应的index,然后和remove(int index)是一摸一样的。
但是在我们平常工作中,遍历删除元素对于新手可能会出现一些问题。下面我们进行细讲。
ArrayList遍历时删除元素总结有下面几种方式。
1、 普通for循环正序删除(结果:会漏掉元素判断)
2、 普通for循环倒序删除(结果:正确删除)
3、 for-each循环删除(结果:抛出异常)
4、 Iterator遍历,使用ArrayList.remove()删除元素(结果:抛出异常)
5、 Iterator遍历,使用Iterator的remove删除元素(结果:正确删除)
普通for循环正序删除
原ArrayList是[4, 5, 6, 7, 7, 5]
for (int i = 0; i < arrayList.size(); i++) {
if (arrayList.get(i) == 7) {
arrayList.remove(i);
//解决方案: 加一行代码i = i - 1; 删除元素后,下标减1
}
System.out.println("当前arrayList是"+arrayList.toString());
}
返回的结果为[4,5,6,7,5]
可以看到与我们预期的结果不一致,因为在最删除的时候,我们知道,当前数字后面的数的位置都会往前移,第二个7的位置会移到第一个7的位置。为当下一轮循环的时候,i++了,对应位置上是5了,所以会出想上面的情况。
解决办法:在删除之后,i--操作。
for-each循环删除
for (Integer value : arrayList) {
if (value.equals(7)) {
arrayList.remove(value);
}
System.out.println("当前arrayList是"+arrayList.toString());
}
结果 Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.test.ArrayListTest1.removeWayThree(ArrayListTest1.java:50)
at com.test.ArrayListTest1.main(ArrayListTest1.java:24)
会抛出ConcurrentModificationException异常,主要在于for-each的底层实现是使用ArrayList.iterator的hasNext()方法和next()方法实现的,
public E next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
调用next()的时候,会进行判断,modCount如果不等于expectedModCount,则会报上面的那个异常。expectedModCount是一开始酒精性赋值,相当于是一个固定值,不会改变。而我们进行remove操作的时候,modCount会增加,所以导致两个值不一致,所以报异常了。
Iterator遍历,使用ArrayList.remove()删除元素这种方式包异常也是跟上面一摸一样的原因。
Iterator遍历,使用Iterator的remove删除元素
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
if (value.equals(3)) {//3是需要删除的元素
iterator.remove();
}
}
当调用iterator.remove()时,调用的是Itr的remove
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
从上面可以看到,对expectedModCount进行的重新赋值,所以就不行报异常。
Arrays.asList
List<Integer> statusList = Arrays.asList(1, 2);
statusList.add(3);
System.out.println(statusList.contains(3));
预期的结果,应该是输出true,但是实际却是抛出了java.lang.UnsupportedOperationException异常:
因为asList 返回的ArrayList却是Arrays类的内部类,它也继承了AbstractList类,重写了很多方法,比如我们上面使用的contains方法,但是却没有重写add方法,所以我们在调用add方法时才会抛出java.lang.UnsupportedOperationException异常。
subList
1、修改原集合元素的值,会影响子集合
2、修改原集合的结构(指的是添加和删除元素操作),会引起ConcurrentModificationException异常
3、修改子集合元素的值,会影响原集合
4、修改子集合的结构,会影响原集合
网友评论