ArrayList详解(基于1.8)

ArrayList是在Java开发中使用率很高、很常见的集合类,它继承自AbstractList,实现List接口,并实现了 RandomAccess, Cloneable, java.io.Serializable标记接口,底层基于可扩容的数组实现,允许Null存在,同时支持快速访问、序列化、复制。


继承结构

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList继承关系图

  1. ArrayList继承AbstractList,并实现了List接口,但AbstractList同样实现了List接口,为什么这样设计?

AbstractList为抽象类,而抽象类可以有抽象方法,也可以有实现方法,利用这一点,在AbstractList抽象类中实现一些通用的方法,子类可以继承到通用方法也可以重写各自特有的方法,代码更加简洁。
至于 ArrayList和AbstractList为什么都实现了List接口,在网上看到了一个有意思的回答Why does LinkedHashSet extend HashSet and implement Set:说是一个误会,开发时以为会有价值,所以就留着了,但实际并没有什么用途,不过也没有影响,就一直没有去维护…

  1. RandomAccess, Cloneable, java.io.Serializable这几个接口都是空接口,作用是什么?

这类没有属性和方法的接口是标记接口,Java中的一个标记接口表示的的是一种类的特性,实现了该标记接口的类则具有该特性。如实现了Serializable接口的类,表示这个类的对象是可以进行序列化和反序列化的。Java中常见的标记接口还有Cloneable接口、RandomAccess接口和Remote接口。可以用 if(对象名 instanceof 标记接口名)检测一个类是否实现某个标记接口。

  • RandomAccess接口:用来快速随机存取,有关效率的问题,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高;若未实现,则使用Iterator来迭代,这样性能更高;
  • Cloneable接口:该接口标记一个类的对象是否有安全的clone方法;
  • Serializable接口:实现该序列化接口,表明该类可以被序列化;

静态变量

// 序列化版本号
private static final long serialVersionUID = 8683452581122892189L;
// 缺省(默认)容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组对象
private static final Object[] EMPTY_ELEMENTDATA = {};
// 缺省(默认)空数组对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 元素数组
transient Object[] elementData; 
// 实际元素数量,默认0
private int size;
// 最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  1. EMPTY_ELEMENTDATA 和DEFAULTCAPACITY_EMPTY_ELEMENTDATA 两个空数组对象分别有什么用?

源码中在DEFAULTCAPACITY_EMPTY_ELEMENTDATA 定义处存在以下的注释:“We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.”,大意是我们将两个数组区分开,由此知道在添加第一个元素时应该扩容多少。

  1. 为什么最大数组容量是Integer.MAX_VALUE - 8 而不是 Integer.MAX_VALUE ?
  2. transient 修饰词的作用?

构造方法

ArrayList有三种构造方法,分别为无参、指定大小、指定集合三种形式。

  • 无参构造函数:elementData 指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA,是一个空数组,在第一次添加数据时才会将数组长度设置为10。
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  • 指定长度的构造函数
    若initialCapacity >0 ,则初始化为指定长度数组;
    若initialCapacity == 0,则初始化为EMPTY_ELEMENTDATA空数组;
    若initialCapacity < 0,则抛出异常。
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);
    }
}     
  • 指定Collection 的构造方法
    若传入的Collection为null,则抛出NullPointerException 异常;
    若c.toArray().length == 0,则指向EMPTY_ELEMENTDATA;
    若c.toArray().length != 0,则将elementData 指向Collection 转化后(可能需要转化两次)的数组。
public ArrayList(Collection<? extends E> c) {
	elementData = c.toArray();
	if ((size = elementData.length) != 0) {
		// c.toArray might (incorrectly) not return Object[] (see 6260652) 
        // toArray方法的实现不同,有些不会返回Object[]类型的数组,所以要增加一个判断
		if (elementData.getClass() != Object[].class)
			elementData = Arrays.copyOf(elementData, size, Object[].class);
	} else {
		// replace with empty array.
		this.elementData = EMPTY_ELEMENTDATA;
	}
}

主要方法

添加元素-add/addAll

ArrayList集合提供了四种添加元素的方法:

// 在最后加一个元素
public boolean add(E e){...}  
// 在指定位置添加一个元素,需满足:0 <= index <= size
public void add(int index, E element){...}  
// 利用System.arraycopy(...)函数将c中元素加到list末尾
public boolean addAll(Collection<? extends E> c){...}  
// 指定位置插入集合元素
public boolean addAll(int index, Collection<? extends E> c){...}  

四种方法的逻辑大致相同,此处以第二个方法为例,主要看一下调用到的方法。

public void add(int index, E element) {
    // 0 <= index <= size,否则IndexOutOfBoundsException
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 
    System.arraycopy(elementData, index, elementData, index + 1,
                    size - index);
    elementData[index] = element;
    size++;
}
// 用于判断index是否符合要求:0 <= index <= size
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 数组越界消息拼接
private String outOfBoundsMsg(int index) {
    return "Index: "+index+", Size: "+size;
}
// 计算最小容量
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
// 为了判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
    // 操作数++,modCount来自AbstractList,后续再研究
    modCount++;
    // overflow-conscious code
    // 如果最小容量>当前数组长度,则进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
// 真正的扩容方法
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // oldCapacity + (oldCapacity >> 1) ==> oldCapacity*1.5
    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:
    // 原理:建一个新数组,copy旧数组
    // 所以在初始化数组时,在数据长度已知或有范围时,尽量指定初始化大小,减少扩容次数,避免时间、空间浪费
    elementData = Arrays.copyOf(elementData, newCapacity);
}
// 超大数组...走到这一步的代码..不,支撑这段代码走到这一步的服务器真牛*
private static int hugeCapacity(int minCapacity) {
    // 讲道理...这个地方不会小于0吧
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    // 这种情况基本不会出现,这么大的数组一般都OOM了,如果没有OOM,一定是需求不合理或者代码不合理
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

指定位置插入时还需要用到System.arraycopy()方法,就是将elementData在插入位置后的所有元素往后面移。

/**
 * @param src     数据源
 * @param srcPos  数据源起始位置
 * @param dest    目标对象
 * @param destPos 目标对象起始位置
 * @param length  操作长度
 * Demo:System.arraycopy(elementData, index, elementData, index + 1, size - index);
 **/
public static native void arraycopy(Object src,int srcPos,Object dest, int destPos,int length);

移除元素-remove/removeAll/retainAll/clear/removeIf

ArrayList集合提供了五种移除元素的方法:

  • remove(int index):移除指定位置的元素,并返回remove的元素
public E remove(int index) {
    // 校验index范围
    rangeCheck(index);
    modCount++;
    // remove的元素
    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;
}
// 校验index范围(纳闷:这为啥不判断index<0?直接抛IndexOutOfBoundsException)
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  • remove(Object o):移除指定元素(以equals方法匹配,至多remove一个),true:成功删除,false:没匹配到
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;
}
// 跳过rangeCheck&&不返回删除的元素(remove(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
}
  • clear():移除全部元素(所有位置指向null)
public void clear() {
    modCount++;
    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    size = 0;
}
  • removeAll(Collection<?> c):移除指定集合的元素
public boolean removeAll(Collection<?> c) {
    // 若c为空,则抛出NullPointerException
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}
// 批量移除(complement==false表示去掉交集,complement==true表示只保留交集)
private boolean batchRemove(Collection<?> c, boolean complement) {
    // 疑问,这样做的意义是什么?
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    // 标记是否修改集合
    boolean modified = false;
    try {
        // 小机灵鬼,w<=r恒成立,所以把不包含的元素往前排
        // 最终[0,w)就是留下的元素,循环删除[w,siz)的元素即可
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        // 如果c.contains()抛出异常,则保留[r,size)的元素
        if (r != size) {
            System.arraycopy(elementData, r, elementData, w, size - r);
            w += size - r;
        }
        // w!=size,存在不符合条件的,指向null
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}
// 判断当前集合是否包含某个对象(此处以ArrayList的实现为例)
// true:包含,false:不包含
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
// 查找元素,返回第一个匹配的元素所在的索引值
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;
}
  • retainAll(Collection<?> c):与removeAll(Collection<?> c)相反,retainAll用来取交集,不再展开分析
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}
  • removeIf(Predicate<? super E> filter):删除符合条件的元素
@Override
public boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    // figure out which elements are to be removed
    // any exception thrown from the filter predicate at this stage
    // will leave the collection unmodified
    int removeCount = 0;
    final BitSet removeSet = new BitSet(size);
    final int expectedModCount = modCount;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked")
        final E element = (E) elementData[i];
        if (filter.test(element)) {
            // 保留需要删除的元素的索引
            removeSet.set(i);
            removeCount++;
        }
    }
    // 防止并发修改
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    // shift surviving elements left over the spaces left by removed elements
    final boolean anyToRemove = removeCount > 0;
    if (anyToRemove) {
        final int newSize = size - removeCount;
        for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
            // 实话说..没看懂,据网上介绍:方法返回第一个位出现或之后指定的起始索引被设置为false的索引
            // 写了个测试类,感觉像是找下一个值
            // eg:bitSet.set(1.2.3...10);
            // System.out.println(bitSet.nextClearBit(3));->10
            // System.out.println(bitSet.nextClearBit(10));->10
            // System.out.println(bitSet.nextClearBit(11));->11
            // System.out.println(bitSet.nextClearBit(13));->13
            i = removeSet.nextClearBit(i);
            // 此处将不符合条件的按顺序放到数组中
            elementData[j] = elementData[i];
        }
        // 末尾替换成null
        for (int k=newSize; k < size; k++) {
            elementData[k] = null;  // Let gc do its work
        }
        this.size = newSize;
        // 防止并发修改
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
    return anyToRemove;
}

截取集合-subList

  • subList(int fromIndex, int toIndex):截取List

“非结构性修改”:是指不涉及到list的大小改变的修改;
“结构性修改”,指改变了list大小的修改;

/**
 * 通过方法注释可以知道:
 * 1. 该方法返回的是父list的一个视图,[fromIndex,toIndex);
 * 2. 父子list做的非结构性修改(non-structural changes)都会影响到彼此;
 * 3. 对于结构性修改,子list的所有操作都会反映到父list上,但父list的修改将会导致返回的子list失效;
 **/
public List<E> subList(int fromIndex, int toIndex) {
    // 类方法,检查参数合规性,会抛出IndexOutOfBoundsException/IllegalArgumentException异常
    subListRangeCheck(fromIndex, toIndex, size);
    // 此处返回的是SubList
    return new SubList(this, 0, fromIndex, toIndex);
}
// SubList是arrayList的一个匿名内部类,所有的操作方法均是操作的原来的ArrayList,即“parent”变量
private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;
    SubList(AbstractList<E> parent,int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }
    ...
}

通过源码发现subList返回的是一个SubList内部类,且父集合做结构性修改时,会导致子集合失效(如果必须用subList,最好随用随取,防止父集合结构性修改导致的异常):

public static void main(String[] args) throws Exception {
    List<String> arrayList = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        arrayList.add("" + i);
    }
    List<String> subList = arrayList.subList(5, 7);
    System.out.println(JsonUtils.toJson(arrayList));
    System.out.println(JsonUtils.toJson(subList));
    System.out.println("父子集合做非结构性修改,正常输出");
    arrayList.set(5,11 + "");
    subList.set(1,12+"");
    System.out.println(JsonUtils.toJson(arrayList));
    System.out.println(JsonUtils.toJson(subList));
    System.out.println("子集合做结构性修改,影响父集合,正常输出(注意13插入的位置)");
    subList.add(13 + "");
    System.out.println(JsonUtils.toJson(arrayList));
    System.out.println(JsonUtils.toJson(subList));
    System.out.println("父集合做结构性修改,子集合失效,访问子集合时会出现异常");
    arrayList.add(12 + "");
    // subList = arrayList.subList(5, 7);
    System.out.println(JsonUtils.toJson(arrayList));
    System.out.println(JsonUtils.toJson(subList));
}
// 输出如下信息:
["0","1","2","3","4","5","6","7","8","9"]
["5","6"]
父子集合做非结构性修改,正常输出
["0","1","2","3","4","11","12","7","8","9"]
["11","12"]
子集合做结构性修改,影响父集合,正常输出(注意13插入的位置)
["0","1","2","3","4","11","12","13","7","8","9"]
["11","12","13"]
父集合做结构性修改,子集合失效,访问子集合时会出现异常
["0","1","2","3","4","11","12","13","7","8","9","12"]
Exception in thread "main" java.util.ConcurrentModificationException

循环-forEach

  • forEach(Consumer<? super E> action):循环(Consumer是函数式接口,可配合lamble使用)
@Override
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;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        // 调用函数式接口的实现
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

注意循环内不要对list进行修改(任何导致modCount改变的操作),会导致ConcurrentModificationException

// arrayList.add(1-10000);
arrayList.forEach(e-> {
    System.out.println(e);
    if (e.equals("5000")){
        arrayList.remove("5000");
    }
});

其他方法

  • 实例的容量调整为列表的当前大小,减少空间浪费
    public void trimToSize(){}
  • 判断是否需要扩容(添加元素时系统会自动扩容,没必要主动调用这个方法)
    public void ensureCapacity(int minCapacity){}
  • 返回列表的当前大小
    public int size(){}
  • 查找元素,返回最后匹配的元素所在的索引值(使用equals比较,如果没匹配到,返回-1)
    public int lastIndexOf(Object o){}
  • 复制集合(元素本身不会复制)
    public Object clone() {}
  • 转换为数组
    public Object[] toArray() {}
    public <T> T[] toArray(T[] a) {}
  • 获取指定索引的元素(底层为数组,所以效率比较高)
    public E get(int index) {}
  • 替换元素(返回被替换的元素)
    public E set(int index, E element){}
  • 可分割迭代器(用到了内部类ArrayListSpliterator)
    • 原理:对任务进行分割,每个线程执行一段,线程安全
      public Spliterator<E> spliterator() {}
  • 排序(函数式接口)
    public void sort(Comparator<? super E> c) {}
  • 迭代
    • (用到内部类Itr)
      public Iterator<E> iterator() {}
    • 用到了内部类ListItr
      public ListIterator<E> listIterator() {}
  • 判断是否为空(先判断!=null,再调用该方法)
    public boolean isEmpty(){}

ArrayList总结(基于1.8)

  • 原理
    • 基于数组,封装了一个动态、再分配的Object[]数组
  • 容量
    • 默认初始化长度:DEFAULT_CAPACITY = 10
    • 1.5倍扩容:oldCapacity + (oldCapacity >> 1)
    • 扩容原理是创建新数组,copy元素,所以在初始化数组时,在数据长度已知或有范围时,尽量指定初始化大小,减少扩容次数,避免时间、空间浪费
  • 线程安全性
    • 线程不安全,若需要线程安全的数组,可用Collicitons工具类
      List<String> testList = Collections.synchronizedList(new ArrayList<>());
    • 同样实现List类的Vector是线程安全的,但不推荐使用,其线程安全原理是使用synchronized同步块,效率低
  • arrayList允许存放null
  • arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除时性能会下降,需要移动数据位置
  • remove操作会将后续元素向前移位
  • remove某个对象时,使用equals方法来匹配目标值的
  • retainAll用来取两个集合的交集,removeAll是移除交集
  • 使用subList时,要注意父集合结构性修改导致的子集合时效问题,最好随用随取

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值