Ultra-detailed! ArrayList source graphic resolve

Yuan is not poetic female program is not a good cook -
please indicate the source, From Chi rain - [https://blog.csdn.net/cjm2484836553/article/details/104329665]

Arraycopy yesterday played a long time, today, let's look at the source code it ArrayList.

Yes, I went to play a bad painting techniques of the time -

Preview the outline of this article:

Here Insert Picture Description

Here we begin illustration of the source ArrayList of journey, starting with CRUD talk, then repeat ArrayListIterator source, of course we will be interspersed between the pit and talk about a few precautions ~

1. increase

ArrayList element addition operation involving two methods add(E object)and add(int index, E object).

1.1 add (E object) adds an element directly in the tail

add(E object)This method is a direct add an element, is inserted in the tail.

If the size of the original array is not enough will carry out the expansion.

The final data is added to the tail, while the size plus 1.

Source as follows:
Here Insert Picture Description

1.2 add (int index, E object) adds an element at the specified position

add(int index, E object)This function is a position in the index and insert a new element.

  • We look at the insert core steps of:

    All data starting from position to be inserted, has to be to move one; then put data to be inserted into them.

    He drew a diagram to facilitate understanding:
    Here Insert Picture Description

  • Learn the core after the insertion step, we take a look at add(int index, E object)the source code, insert an element to a specified location specifically how to achieve it.

    一、要先检测要插入的位置index是否合法。

    二、判断数组是否够用:

    a. 如果数组够用,即 s<a.length时,直接调用arraycopy(),将从index开始的所有数据都往后挪一位。(关于arraycopy的使用我上一篇已经详细讲过了,所以这里我们应该知道他是从后往前依次往后挪一位过去的)。

    b. 如果数组满了,即s>=a.length时,这里就要

    ​ (1)先将原数组进行扩容,生成新的数组;

    ​ (2)将原数组中index之前的数据复制到新数组对应的位置中去。

    ​ (3)将原数组中index之后的数据往后挪一位的移动到新的数组中去。

    ​ (4)将新数组赋给array。

    三、将要添加的元素放在index的位置,同时有效数据个数size+1。

    源代码见下图:
    Here Insert Picture Description

补充:扩容规则

我们都知道,数组的大小是不可变的,而ArrayList的大小是可变的。而ArrayList底层也是用到了数组,那ArrayList是如何做到大小可以动态变化的呢?

答案就是通过扩容的方式。

也就是Object[] newArray = new Object[newCapacity(s)];这句代码。

现在我们来具体看看newCapacity(s)的实现:

Here Insert Picture Description
Here Insert Picture Description

这里我们做一下解释:

当目前的容量currentCapacity<6 时,increment=12;

否则的话 increment等于currentCapacity的2倍。

最终返回的大小是 currentCapacity+increment

也就是说,扩容之后要么是在原有的基础上 +12,要么就是扩大为原来的三倍。

2.删

ArrayList的删除操作,我们也来看两个 remove(Object object)remove(int index)

2.1 remove(int index) 删除指定位置的元素

  • 同样,我们先来看看删除的核心操作:

    对应的代码就是System.arraycopy(a, index + 1, a, index, --s - index);这句代码,

    即 :如果要删除【index】位置的元素,那就要把【index】之后的所有元素都往前挪一位,覆盖掉index原本的位置.

    同样来画个图来帮助大家理解:
    Here Insert Picture Description

  • 了解了核心的操作之后,我们就来看看 remove(int index) 的源代码吧:
    Here Insert Picture Description

2.2 remove(Object object) 删除某个已知元素

remove(Object object) 删除某个已知元素。

上面我们已经知道了删除指定位置元素的操作,那如果要删除某个已知元素的话,我们是不是也应该先找到它对应的位置,然后再进行删除呢。

  • 那问题来了,怎么找到元素对应的位置呢?

    对,通过循环遍历,并进行比较 找到对应的index.
    Here Insert Picture Description

▲有个坑!

▲【注意】:

调用remove方法, 会, 且只会 删除第一个与传入对象通过equals方法判断相等的元素。

如果传入null, 则删除掉第一个null元素。

所以, 如果自定义类想要使用remove方法从列表删除某个指定值对象, 还需要实现该类型自己的equals方法才行!

▲还有个坑!

ArrayList是可以顺序删除节点的,但是!如果使用普通for循环,必须是从后往前删。不能从前往后删。

我们先来看一下【错误示范】:

ArrayList list=new ArrayList();
list.add("a");
list.add("b");
list.add("c");

System.out.println("删除前:"+list.toString());

//顺序删除节点错误示范:从前往后删----会删不干净
for (int i=0;i<list.size();i++){
    list.remove(i);
}
System.out.println("删除后:"+list.toString());

【错误结果展示】:
Here Insert Picture Description

【出错原因分析】:

要顺序删除ArrayList的全部节点,如果我们从前往后的顺序删除,先删除【0】位置的数据,但是由于删除的时候是从后往前挪一位进行删除的,所以【0】的位置又会被下一个位置的数据覆盖上,实际上【0】还是有数据的。再画一张图来方便大家理解:
Here Insert Picture Description

【正确的做法】:

要想顺序删除ArrayList的所有节点,且采用普通的for循环,那只能从后往前删,这样就不会出问题。
Here Insert Picture Description

3.改、查

ArrayList修改数据很简单,调用的是set(int index, E object)方法,直接修改对应位置的数据即可。
Here Insert Picture Description

ArrayList获取数据就跟简单了,由于是顺序表有下标,直接取出对应下标数据就可以了。
Here Insert Picture Description

4.ArrayList的3种遍历方式

ArrayList的遍历我们有三种方式:for循环增强for循环迭代器三种方式。

(当然,增强for循环其实还是用迭代器实现的,这一点我们可以通过反编译来进行验证。)

ArrayList arrayList = new ArrayList();
arrayList.add("情人节");
arrayList.add("快乐");
arrayList.add("我");
arrayList.add("对");
arrayList.add("自己说~");

System.out.println("for循环的方式遍历:");
for (int i = 0; i < arrayList.size(); i++) {
    System.out.print(arrayList.get(i));
}

System.out.println();
System.out.println("---------------------------------");
System.out.println("增强for循环的方式遍历:");
for (Object s : arrayList) {
    System.out.print((String) s);
}

System.out.println();
System.out.println("---------------------------------");
System.out.println("迭代器的方式遍历:");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
    System.out.print(iterator.next());
}

我们来看一下打印结果:
Here Insert Picture Description

5.迭代器 ArrayListIterator源码解读

好的,上面我们知道了ArrayList可以使用迭代器进行遍历。

  • 那为什么它可以使用迭代器这种方式呢?

    这个我们又要去看源码了,跟进arrayList.iterator()的 iterator()方法,我们会发现ArrayList有一个内部类ArrayListIterator。而 ArrayListIterator 实现了 Iterator 接口,所以才可以使用迭代器这种方式进行迭代。

Here Insert Picture Description

现在我们就来具体看看ArrayListIterator的相关源代码和注意事项吧。

首先,我们可以看到ArrayListIterator实现了Iterator这个接口。

5.1 提一下什么是 Iterator (迭代器)?

我们都知道在Java中,有很多的数据容器,这些的操作又有很多的共性。而迭代器就是给各种容器提供了公共的操作接口。这样就使得对容器的操作有了规范性。

在Iterator接口中定义了三个方法:

  • hasNext(): 如果仍有元素可以迭代,就返回true.

  • next(): 返回迭代的下一个元素。

  • remove(): 从集合中移除返回的最后一个对象。(可选操作)

源码如下:
Here Insert Picture Description

5.2 ArrayListIterator中的坑▲▲▲

ArrayListIterator 的源码其实并不难理解,就是实现了 Iterator 中的三个方法。

但是这里有一个▲坑▲大家需要注意,那就是:

每当我们使用迭代器遍历元素时,如果使用迭代器以外的方法修改了元素内容(如删除元素),那就会抛出ConcurrentModificationException的异常。

让我先看一下现象,然后再从源码角度找原因。

错误代码示例:

		ArrayList arrayList = new ArrayList();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");

        System.out.println("移除前:" + arrayList);
        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            if ("c".equals(iterator.next())) {
                arrayList.remove("c");
            }
        }
        System.out.println("移除后:" + arrayList);

        //注意增强for使用的也是迭代器
        //所以一下这种操作也会报ConcurrentModificationException
        //for (Object o : arrayList) {
        //    arrayList.remove(o);
        //}
        //System.out.println("移除后2:" + arrayList);

报错显示:
Here Insert Picture Description

好的,现象我们已经看到了,那现在我们就来看看错误的原因吧。

我们要先来了解一下这几个变量的含义:
Here Insert Picture Description

然后我们来看一下何种情况下会报错:
Here Insert Picture Description

先分析一下报错原因:

在我们使用 ArrayLis 的 iterator() 方法获取到迭代器进行遍历时,会把 ArrayList 当前状态下的 modCount 赋值给 ArrayListIterator类的 expectedModCount 属性。

如果我们在迭代过程中,使用了 ArrayList 的 remove()方法,这时 modCount 就会加 1 ,但是迭代器中的expectedModCount 并没有变化,当我们再使用迭代器的next()方法时,它就会报ConcurrentModificationException的错。

最后我们再来比较一下 ArrayListIterator中的 remove()方法和ArrayList自己的remove()方法的不同之处,验证一下错误发生的原因:
Here Insert Picture Description
Here Insert Picture Description
Here Insert Picture Description

✍所以我们得到的启示是:

每当我们使用迭代器遍历元素时,要使用迭代器自己的删除方法,而不能使用迭代器以外的方法修改了元素内容,否则会造成expectedModCount和modCount的值不一致,从而抛出ConcurrentModificationException的异常。

此外,我们还要注意一下,增强for循环其实也是使用的迭代器,所以也要注意同样的问题。

积累点滴,做好自己~

Published 85 original articles · won praise 152 · views 280 000 +

Guess you like

Origin blog.csdn.net/cjm2484836553/article/details/104329665