美文网首页
Java集合-ArrayList

Java集合-ArrayList

作者: 面向星辰大海的程序员 | 来源:发表于2024-02-27 01:19 被阅读0次

先上图:


ArrayList.png

ArrayList应该是经常用到的一个集合容器了,可以说java集合的其他容器都没用过也一定用过ArrayList。ArrayList内部是维护一个数组来存储元素。

ArrayList_grow.png

1.5倍扩容,默认10个元素,在添加第一个元素时才创建一个大小为10的数组。

源码不多做介绍了。下面说一个重点:

遍历问题

先抛出4个遍历,如代码所示:

      ArrayList<String> arrayList = new ArrayList<>();
        resetArrayList(arrayList);
        //1.正序删除
        for (int i = 0; i < arrayList.size(); i++) {
            String re = arrayList.remove(i);
            System.out.println("re=" + re);
        }
        System.out.println("1.arrayList.size=" + arrayList.size());
        resetArrayList(arrayList);
        //2.后序删除
        for (int i = arrayList.size() - 1; i >= 0; i--) {
            arrayList.remove(i);
        }
        System.out.println("2.arrayList.size=" + arrayList.size());
        resetArrayList(arrayList);
        //3.增强for循环删除
//        for (String s : arrayList) {
//            arrayList.remove(s);
//        }
        System.out.println("3.arrayList.size=" + arrayList.size());
        //4.使用迭代器
        resetArrayList(arrayList);
        Iterator<String> it = arrayList.iterator();
        while (it.hasNext()) {
            it.next();
            it.remove();
        }
        System.out.println("4.arrayList.size=" + arrayList.size());
//      Iterator<String> listIt = arrayList.listIterator(1);

ArrayList元素重置

 public static void resetArrayList(ArrayList<String> arrayList) {
        arrayList.clear();
        for (int i = 0; i < 3; i++) {
            arrayList.add("(" + i);
        }
    }

打印结果:

re=(0
re=(2
1.arrayList.size=1
2.arrayList.size=0
3.arrayList.size=3
4.arrayList.size=0

分析1:
现象:循环跑完后arrayList中还有一个元素,“ (1 ”元素没有被删除
原因:删除第一个元素,就是arrayList内部的数组的第0下标的元素,后续的元素整体向前移动,也就是“(1”、“(2”,移动到0、1的位置,当第二次循环到来时,i已经是1了,此时对应的值就是“(2”,所以“(2”被移除,移除元素的同时,arrayList.size值也会跟着变化,因此不会发生越界异常,循环终止。

显然,实际上很少有这种需求。

分析2:
现象: 打印正常
原因: 从后向前删除,不会使数组发生整体位移,arrayList内部移除元素时,如果是最后一位元素直接置空,不需要移动元素。i--也能对得上最后一位元素。

分析3:
现象: 直接抛出异常ConcurrentModificationException
原因: 看下方源码,曾强for循环就是迭代器循环,当取得第一个元素并移除,去下一个元素就会检查出有改动,因此抛出异常。

 public Iterator<E> iterator() {
// 使用增强for循环就是获得一个迭代器
  // 这里直接new一个返回
        return new Itr();
    }
    /**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
 //这记录操作数,modCount是记录操作数,ArrayList添加、移除元素都++
        int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        public E next() {
   //s就是从这个方法返回的,这里检查如果操作数不等于自己的记录,说明元素有添加或移除,
            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];
        }

        final void checkForComodification() {
  //操作数不一致,抛出ConcurrentModificationException
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

分析4:
现象: 打印正常
原因: 看下方代码分析,
1>hasNext方法判断是否有下一个元素
2>next取当前元素
这里没有出ConcurrentModificationException是remove方法中重新记录了modCount
3>remove移除

 public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.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;
          //lastRet记录当前元素的下标
            return (E) elementData[lastRet = i];
        }

        public void remove() {
        //这里没有走next方法会直接抛出状态不合法的异常
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
              //移除当前元素
                ArrayList.this.remove(lastRet);
              //因为当前元素被移除,后续的元素会占用这个位置,游标要移回来
                cursor = lastRet;
            //lastRet重新赋值-1,假如这里没有赋值-1,remove这里调用多次的话就会删除多个元素
                lastRet = -1;
          //移除元素后modCount有变,重新记录
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

如果想从指定下标开始做移除操作可以使用:

//从下标1开始
Iterator<String> listIt = arrayList.listIterator(1);

ConcurrentModificationException,这个异常叫并发修改异常,既然叫并发,那和多线程有关系么?

个人认为是无直接关系

从代码设计角度看,是为了数组的唯一,防止数组正在使用时被修改从而取到的元素不正确,如果不能保证数组的正确,直接抛个异常。看着办吧。

间接关系分析:抛出异常的判断是迭代器记录与ArrayList记录的不一致,多线程大概率是多个迭代器在操作,比如其中一个迭代器移除,那modCount改变,那另一个迭代器操作时就会发现自己记录的不一致,直接抛异常。

还有,ArrayList本就线程不安全的。

总结:ArrayList就是维护一个数组的容器,包括如何扩容、如何迭代遍历以及增删改查。

相关文章

网友评论

      本文标题:Java集合-ArrayList

      本文链接:https://www.haomeiwen.com/subject/zuxtzdtx.html