编程语言
首页 > 编程语言> > Java集合类复习之ArrayList(JDK10)

Java集合类复习之ArrayList(JDK10)

作者:互联网

1、集合与数组的区别

2、ArrayList

①、ArrayList是一个动态数组,实现了List< E>, RandomAccess, Cloneable, java.io.Serializable接口;

②、ArrayList默认长度是10,扩容函数newCapacity,将长度扩展到原来的1.5倍关键操作为:int newCapacity = oldCapacity + (oldCapacity >> 1);然后将元素组拷贝到新数组里面,将新数组作为原来的数组,所以ArrayList的扩容开销很大的,应尽量避免这种操作,实现最好确定需要的长度。

③、add(E e)方法

public boolean add(E e) {
  modCount++;
  将元素和数组长度、数组大小(元素的个数)传过去
  add(e, elementData, size);
  return true;
}
private void add(E e, Object[] elementData, int s) {
    如果元素的个数和数组的长度相等,那么就需要对数组进行扩容
    if (s == elementData.length)
          elementData = grow();
     如果不是,则将元素添加到最后(元素的最后)
     elementData[s] = e;
     并且元素个数+1
     size = s + 1;
}
private Object[] grow() {
将可允许的数组的最小容量传过去(元素个数(size+1)—因为要添加一个元素)
return grow(size + 1);
}

private Object[] grow(int minCapacity) {
调用复制的方法,在原来元素基础上增加容量
return elementData = Arrays.copyOf(elementData,
                                   newCapacity(minCapacity));
}

private int newCapacity(int minCapacity) {
// overflow-conscious code
获取数组的长度(默认的或是自己定义的)
int oldCapacity = elementData.length;
新的容量是原容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
新容量比可允许的最小容量小,那么新的容量就是可允许的最小的容量
之所以会有这个判断,是因为我们可以自己定义初始容量,而不一定是默认的容量
if (newCapacity - minCapacity <=  0) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return minCapacity;
}
如果新容量大于数值的最大值,传入huge方法,
return (newCapacity - MAX_ARRAY_SIZE <= 0)
    ? newCapacity
    : hugeCapacity(minCapacity);

④、add(int index, E element)
在指定位置处插入元素,先检查插入后新的长度是否需要扩容,然后再将index后面的元素复制到原数组中,关键操作为

System.arraycopy(elementData, index,elementData, index + 1,s - index);

⑤、modCount
modCount是AbstractList的一个成员变量,表示list结构上被修改的次数,结构修改是指更改列表大小或以其他方式干扰列表的方式,即正在进行的迭代可能会产生错误的结果。他的字段供iterator和listIterator方法返回的迭代器和列表迭代器实现使用。如果此字段的值意外更改,则iterator(或listIterator)将抛出ConcurrentModificationException以响应next,remove,previous,set,add等操,这是jdk在面对迭代遍历的时候为了避免不确定性而采取的快速失败原则。子类对此字段的使用是可选的,如果子类希望支持快速失败,只需要覆盖该字段相关的所有方法即可。单线程调用不能添加删除Iterator正在遍历的对象,否则将可能抛出ConcurrentModificationException异常,如果子类不希望支持快速失败,该字段可以直接忽略。

⑥、fail-fast和fail-safe

在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出ConcurrentModificationException。

原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount
变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount
这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModification Exception。

缺点:基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

引用自:面试题思考:java中快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

3、Vector

Vector与ArrayList功能相同,只是Vector是线程安全的,扩容时默认是扩充为原来的两倍,也可在初始化Vector的时候使用public Vector(int initialCapacity, int capacityIncrement)指定扩容长度。

标签:遍历,Java,迭代,int,ArrayList,elementData,JDK10,集合
来源: https://blog.csdn.net/qq_43060759/article/details/104769533