ArrayList源码和相关问题分析

1、底层原理

  ArrayList底层是用数组实现的存储。通过无参构造方法的方式ArrayList()初始化,则赋值底层数Object[] elementData为一个默认空数组Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}所以数组容量为0,只有真正对数据进行添加add时,才分配默认DEFAULT_CAPACITY = 10的初始容量。通过有参构造方法初始化,可以创建指定长度的数组。

  创建时指定初始容量和不指定的区别:如果能预估数据量大小,可以在定义ArrayList时指定数组初始大小,这样可以避免不必要的数组扩容,从而减小开销。

2、删除带来的问题

2.1、for循环删除

  (1)正向删除

public static void main(String[] args){
        List<String> list = new ArrayList<String>();

        list.add("111");
        list.add("222");
        list.add("222");
        list.add("333");
        list.add("444");
        list.add("333");
        //for循环正向循环删除
        for (int i = 0;i < list.size();i++){
            if (list.get(i).equals("333")){
                list.remove(i);
            }
        }
        System.out.println(Arrays.toString(list.toArray()));
    }

  运行结果:[111, 222, 333, 444, 333]

  分析:发现相邻的222没有被删除,原因是i=1位置的元素删除后,后面的元素都会向前移一位,下次循环会从i=2开始,而数据迁移后i=1位置的222就不会再被删除。所以for循环正向删除会以后相邻重复元素。

  (2)反向删除

public static void main(String[] args){
        List<String> list = new ArrayList<String>();

        list.add("111");
        list.add("222");
        list.add("222");
        list.add("333");
        list.add("444");
        list.add("333");
        //for循环反向循环删除
        for (int i = list.size() - 1;i >= 0;i--){
            if (list.get(i).equals("222")){
                list.remove(i);
            }
        }
        System.out.println(Arrays.toString(list.toArray()));
    }

  反向删除不会有问题。

2.2、Iterator循环删除

  (1)在iterator中使用ArrayList的remove( )方法

public static void main(String[] args){
        List<String> list = new ArrayList<String>();

        list.add("111");
        list.add("222");
        list.add("222");
        list.add("333");
        list.add("444");
        list.add("333");
        //foreach循环删除
        Iterator iterator = list.iterator();
       while (iterator.hasNext()){
           if (iterator.next().equals("222")){
               list.remove(iterator.next());
           }
       }
        System.out.println(Arrays.toString(list.toArray()));
    }

  运行结果:会报Exception in thread "main" java.util.ConcurrentModificationException

  分析:看下ArrayList源码——list.remove( )和list.iterator( )

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 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
    }
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];
        }

  list.iterator( )返回的是内部类Itr,它实现了Iterator接口,报错的原因就在于调用iterator.next( )时有一个checkForComodification( )方法会做一个检查

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

  list.remove( )在删除元素时,只做了modCount ++,所以检查时两个值会不相等。

  (2)使用iterator的remove( )方法

public static void main(String[] args){
        List<String> list = new ArrayList<String>();

        list.add("111");
        list.add("222");
        list.add("222");
        list.add("333");
        list.add("444");
        list.add("333");
        
        Iterator iterator = list.iterator();
       while (iterator.hasNext()){
           if (iterator.next().equals("222")){
               iterator.remove();
           }
       }
        System.out.println(Arrays.toString(list.toArray()));
    }

  这种方式不会报错,且能正确删除元素(推荐使用的方式)。分析下源码:——看iteraotr的remove( )

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();
            }
        }

  其实调用的就是ArrayList的remove(),不过有一步:expectedModCount = modCount,所以不会报错。

2.3、foreach中删除

  foreach原理是因为这些集合类都实现了Iterable接口,该接口中定义了Iterator迭代器的产生方法,并且foreach就是通过Iterable接口在序列中进行移动。也就是说:在编译的时候编译器会自动将对for这个关键字的使用转化为对目标的迭代器的使用。

 

  
posted @ 2020-06-24 19:54  jingyi_up  阅读(35)  评论(0编辑  收藏  举报