List接口实现类ArrayList源码及详解

一.ArrayList简介

ArrayList是实现List接口的动态数组。因为它是基于数组实现的,主要有如下特点:

1、插入、删除比较慢,因为插入、删除需要移动数据位置。

2、可以重复插入数据、可以插入null。

3、查找比较快,可以直接使用下标。

ArrayList的底层实现是不同步,多线程操作会出现问题,这一点要注意。

二.ArrayList常用方法

1.add(E e):向集合中添加一个元素。

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");

2.add(int index, E element):向集合index位置处添加一个元素。

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add(2, "张三");

3.addAll(Collection<? extends E> c):将某集合全部添加到另外一集合。

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");

ArrayList<String> allArrayList = new ArrayList<String>();
allArrayList.addAll(arrayList);

4.addAll(int index, Collection<? extends E> c):将某集合全部添加到另外一集合的index位置处。

ArrayList<String> arrayList=new ArrayList<String>();
arrayList.add("张三");

ArrayList<String> allarrayList=new ArrayList<String>();
allarrayList.addAll(2,arrayList);

5.set(int index, E element):修改index位置对应的Object。

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.set(0, "李四");

6.remove(int index):移除列表中指定位置上的元素。

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");
arrayList.remove(0);

7.remove(Object o):移除列表中指定元素。

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");
arrayList.remove("张三");

8.removeAll():清空集合

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");
arrayList.removeAll(arrayList);

9.get(int index)方法:获取index位置对应的Object

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");
String s = arrayList.get(0);

10.subList(int fromIndex,int toIndex)方法:截取集合[fromIndex,toIndex)

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("刘六");
arrayList.add("王二麻子");

List<String> list = arrayList.subList(1, 3);
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
     Log.d("TAG", iterator.next() + "");
}
D/TAG: 李四

D/TAG: 王五

11.size()方法:集合长度

ArrayList<String> arrayList = new ArrayList<String>();
int num = arrayList.size();

12.isEmpty():集合是否为空

ArrayList<String> arrayList = new ArrayList<String>();
boolean b = arrayList.isEmpty();

13.contains(Object o):集合是否包含o

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("刘六");
arrayList.add("王二麻子");

boolean b = arrayList.contains("王五");
boolean c = arrayList.contains("222");
Log.d("TAG", "b----:" + b);
Log.d("TAG", "c----:" + c);
D/TAG: b----:true

D/TAG: c----:false

14.indexOf(Object o):集合中第一次出现o的位置

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("张三");
arrayList.add("刘六");
arrayList.add("王二麻子");
arrayList.add("张三");

int a = arrayList.indexOf("张三");
int b = arrayList.indexOf("刘六");
int c = arrayList.indexOf("222");
Log.d("TAG", "a----:" + a);
Log.d("TAG", "b----:" + b);
Log.d("TAG", "c----:" + c);
D/TAG: a----:0

D/TAG: b----:4

D/TAG: c----:-1

15.lastIndexOf(Object o):集合中最后一次出现o的位置

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("张三");
arrayList.add("刘六");
arrayList.add("王二麻子");
arrayList.add("张三");

int d = arrayList.lastIndexOf("张三");
int e = arrayList.lastIndexOf("刘六");
int f = arrayList.lastIndexOf("222");
Log.d("TAG", "d----:" + d);
Log.d("TAG", "e----:" + e);
Log.d("TAG", "f----:" + f);
D/TAG: d----:6

D/TAG: e----:4

D/TAG: f----:-1

三.源码分析

我们从ArrayList的构造方法开始我们的源码之旅。

源码分析之构造方法

1.无参构造方法

使用

ArrayList<String> arrayList = new ArrayList<String>();

源码 

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2.指定大小的构造方法

使用

ArrayList<String> arrayList = new ArrayList<String>(10);

源码 

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

ArrayList最常用的两个构造方法就是上述的两个构造方法。

小结1

<1> 如果创建ArrayList时,构造方法中不指定ArrayList的大小。系统会默认创建一个默认的Object类型的数组(空的数组)。

transient Object[] elementData;


private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

也就是将elementData数组变成 默认空的数组。

<2> 如果创建ArrayList时,构造方法中指定了ArrayList的大小。则判断大小做不同的处理。

(1) 指定大小大于0

this.elementData = new Object[initialCapacity];

也就是将elementData数组 变成 指定大小长度的数组。

(2) 指定大小等于0

this.elementData = EMPTY_ELEMENTDATA;


private static final Object[] EMPTY_ELEMENTDATA = {};

也就是将elementData数组 变成 空的数组 即此时和无参构造方法创建ArrayList一样。

(3) 指定大小小于0

throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);

抛出异常。

源码分析之直接添加元素

举例

ArrayList<String> arrayList = new ArrayList<String>();

//直接添加元素 该元素会被添加到ArrayList的最后一个
arrayList.add("张三");

源码

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

第一行代码执行ensureCapacityInternal()方法。

ensureCapacityInternal方法源码

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

该方法传参size+1

/**
 * The size of the ArrayList (the number of elements it contains).
 *
 * @serial
 */
private int size;

size:看注释可知,他是ArrayList的实际大小 即ArrayList中有多少元素。

如果,elementData是默认的Object类型的数组。则重新计算大小。那么elementData什么时候是默认的Object类型的数组呢?由构造方法源码可知,就是创建ArrayList时,使用无参构造方法创建ArrayList的时候赋值的。也就是说,如果使用ArrayList的无参构造方法创建ArrayList时。第一次add元素。会计算一下数组大小。

minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

这,也可以看出ArrayList默认创建的大小是10

取传参的数值(size+1)(第一次添加时size+1=1) 和ArrayList的默认大小10。取两者最大的值。第一次肯定是10。然后执行ensureExplicitCapacity(minCapacity);方法。

ensureExplicitCapacity(minCapacity);方法源码

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

     // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

该方法中,传进来minCapacity是10。因为第一次添加元素,所以elementData是空的。所以显然if语句是成立的。所以会执行grow()方法。

grow()方法源码

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    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:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

该方法中。

老的数组长度定义为elementData.length; 第一次即为0。

新的数组长度初始化为 0+(0>>1) 还是0。

0-minCapacity(传进来minCapacity是10)=-10 小于0 将新的数组长度赋值传进来的minCapacity(传进来minCapacity是10)。

而 MAX_ARRAY_SIZE 

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

所以,此分支一般不会走到。

然后将elementData数组使用Arrays类的copyOf方法拷贝一份新的数组。这就是ArrayList的扩容。

最后执行

elementData[size++] = e;

即,将传进来的元素插到最后一个位置。

然后再次add方法添加元素时。

ensureCapacityInternal方法中

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

这个条件不成立。

ensureExplicitCapacity方法中

if (minCapacity - elementData.length > 0)
      grow(minCapacity);

条件也不成立,所以就不会执行grow()方法 扩容了。

小结2

<1> 使用ArrayList的无参构造方法创建ArrayList对象。由于没有指定数组大小。所以默认创建了一个空的数组。

<2> 第一次add方法添加元素时,会最后执行到grow()方法来扩容。ArrayList默认数组大小是10。

<3> 当 minCapacity - elementData.length > 0 条件成立时,会执行扩容方法。

源码分析之向指定位置添加元素方法

举例

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add(1, "李四");

源码

public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
}

同理,如果传进来的位置比当前数组长度还大,或者传进来的位置小于0。则抛出异常。

否者执行ensureCapacityInternal(size + 1);方法。和add()方法一样。这里就不再赘述了。

然后执行

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

即。向index位置插入一个元素,需要将该元素后面的所有元素都copy一遍。

最后将传进来的元素放到指定index的位置。

小结3

<1> add方法向指定位置插入元素,会使用System类的arraycopy方法。将index位置后面的元素全部copy一遍。

<2> 这样也能侧面证实ArrayList的插入元素。相对比较耗时。因为他需要操作该位置后面全部的元素。

源码分析之删除指定位置的元素

举例

ArrayList<String> arrayList = new ArrayList<String>();

//删除下标2对应的元素
arrayList.remove(2);

源码

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 *
 * @param index the index of the element to be removed
 * @return the element that was removed from the list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    modCount++;
    
    E oldValue = (E) 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位置对应的元素。

计算需要移动的元素个数

int numMoved = size - index - 1;

也就是说。如果数组长度为20。删除的位置是10。那么需要移动的元素个数是20-10-1=9。其实也好理解。就是删除了index=10的元素。那么index=10后面的元素要全部前移一位。

如果计算的需要移动的元素大于0。也就是说有需要移动的元素。那么需要调用System.arraycopy方法。将index后面的元素全部拷贝一份。

最后执行

elementData[--size] = null; // clear to let GC do its work

因为已经重新拷贝,所以需要把原数组中的元素置空。

最后返回删除的元素

return oldValue;

小结4

<1> 删除指定位置的元素时,需要校验index位置是否合法,以免造成空指针。

<2> 删除指定位置的元素时,该位置后的全部元素都需要前移一位。这也侧面证实了ArrayList删除数据时比较耗时。因为要移动index后面全部的数据。

<3> 删除指定位置的元素时,会返回指定位置的元素值。

源码分析之删除元素

举例

ArrayList<String> arrayList = new ArrayList<String>();


//删除数组中“张三”
arrayList.remove("张三");

源码

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If the list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
 * (if such an element exists).  Returns <tt>true</tt> if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *
 * @param o element to be removed from this list, if present
 * @return <tt>true</tt> if this list contained the specified element
 */
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;
}

首先 判断需要删除的元素是否是null。

如果是空,则系统认为你要删除的元素是空元素。For循环中找出第一个空的元素的位置。执行fastRemove方法。

如果非空,则系统认为你要删除的元素是传参的元素。For循环中找出第一个传参的元素的位置。执行fastRemove方法。

fastRemove方法源码

/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
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
}

该方法和删除指定位置的源码类似。这里就不再赘述了。

小结5

<1> 删除指定元素时,系统会找到数组中第一个出现指定元素的位置。然后按照删除指定位置的元素方法操作。

<2> 删除指定元素时,是删除的第一个元素。如果数组中有多个指定的元素。则其他的没有影响,只会删除第一个。因为代码中For循环找到一个指定元素位置后,就直接return了。

<3> 删除指定元素时,方法会有布尔类型的返回值。返回true表示删除成功,返回false表示删除失败。

补充

ArrayList的删除除了上述讲解的remove(int index)和remove(Object o)外。还有一个迭代器的删除方法Iterator.remove()。

四.ArrayList删除数据事项

上述已经讲过,ArrayList删除方法有三个。remove(int index) 和remove(Object o)都是ArrayList的方法。源码分析可知,两个方法删除时,后面的元素都会重新操作前移一位。

代码

package com.example.test;

import android.os.Bundle;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initArrayList();
    }

    public void initArrayList() {

        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("张三");
        arrayList.add("李四");
        arrayList.add("王五");
        arrayList.add("刘六");
        arrayList.add("王二麻子");

        /**
         * 删除“王五”后立刻取出“刘六”
         * */

        //方法1.remove(int index)
        arrayList.remove(2);

        String result = arrayList.get(3);
        Log.i("TAG", "3位置上对应的结果----:" + result);

        Log.i("TAG", "删除“王五”后立刻取出“刘六”遍历集合");
        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            Log.i("TAG", iterator.next() + "");
        }
    }

}

结果

I/TAG: 3位置上对应的结果----:王二麻子

I/TAG: 删除“王五”后立刻取出“刘六”遍历集合
I/TAG: 张三
I/TAG: 李四
I/TAG: 刘六
I/TAG: 王二麻子

说明

remove(int inedex)方法删除集合元素,被删除的元素后面的元素全部前移。

代码

package com.example.test;

import android.os.Bundle;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initArrayList();
    }

    public void initArrayList() {

        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("张三");
        arrayList.add("李四");
        arrayList.add("王五");
        arrayList.add("刘六");
        arrayList.add("王二麻子");

        /**
         * 删除“王五”后立刻取出“刘六”
         * */

        //方法2.remove(Object o)
        arrayList.remove("王五");

        String result = arrayList.get(3);
        Log.i("TAG", "3位置上对应的结果----:" + result);

        Log.i("TAG", "删除“王五”后立刻取出“刘六”遍历集合");
        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            Log.i("TAG", iterator.next() + "");
        }
    }

}

结果

I/TAG: 3位置上对应的结果----:王二麻子

I/TAG: 删除“王五”后立刻取出“刘六”遍历集合
I/TAG: 张三
I/TAG: 李四
I/TAG: 刘六
I/TAG: 王二麻子

说明

remove(Object o)方法删除集合元素,被删除的元素后面的元素全部前移。

代码

package com.example.test;

import android.os.Bundle;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initArrayList();
    }

    public void initArrayList() {

        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("张三");
        arrayList.add("李四");
        arrayList.add("王五");
        arrayList.add("刘六");
        arrayList.add("王二麻子");

        /**
         * 删除“王五”后立刻取出“刘六”
         * */

        //方法3.Iterator.remove()
        Iterator iter = arrayList.iterator();
        while (iter.hasNext()) {
            if (iter.next().equals("王五")) {
                iter.remove();
            }
        }

        String result = arrayList.get(3);
        Log.i("TAG", "3位置上对应的结果----:" + result);

        Log.i("TAG", "删除“王五”后立刻取出“刘六”遍历集合");
        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            Log.i("TAG", iterator.next() + "");
        }
    }

}

结果

I/TAG: 3位置上对应的结果----:王二麻子

I/TAG: 删除“王五”后立刻取出“刘六”遍历集合
I/TAG: 张三
I/TAG: 李四
I/TAG: 刘六
I/TAG: 王二麻子

说明

集合的删除 一般情况下使用remove(int inedex),remove(Object o)或是Iterator.remove()方法都可以。(比如上述删除某个已知的元素(要删除的元素位置和值都确定且只删除一个))。

然而要遍历集合删除循环删除时建议使用迭代器的删除即Iterator.remove()方法删除。

举例

代码

public class CollectionActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_collection);
        initArrayList();
    }

    public void initArrayList() {

        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("张三");
        arrayList.add("李四");
        arrayList.add("王五");
        arrayList.add("刘六");
        arrayList.add("王二麻子");

        int num = arrayList.size();
        for (int i = 0; i < num; i++) {
            String s = arrayList.get(i);
            if ("李四".equals(s)) {
                arrayList.remove("李四");
            }
        }
    }
}

结果报错(数组下标越界)

因为集合中删除了一个元素,集合长度变小一个。而For循环时,数组的长度已经计算好。

解决方法

<1> For循环条件时,数组长度随时计算

for (int i = 0; i < arrayList.size(); i++)

<2>使用迭代器的删除方法

修改代码

public class CollectionActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_collection);
        initArrayList();
    }

    public void initArrayList() {

        ArrayList<String> arrayList = new ArrayList<String>();
        arrayList.add("张三");
        arrayList.add("李四");
        arrayList.add("王五");
        arrayList.add("刘六");
        arrayList.add("王二麻子");

        Iterator iter = arrayList.iterator();
        while (iter.hasNext()) {
            if ("李四".equals(iter.next())) {
                iter.remove();
            }
        }

        Log.i("TAG", "*********************遍历集合***************************");
        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            Log.i("TAG", iterator.next() + "");
        }
    }
}

结果

I/TAG: *********************遍历集合***************************
I/TAG: 张三

I/TAG: 王五

I/TAG: 刘六

I/TAG: 王二麻子

正确

五.总结

由源码可知,ArrayList是基于数组实现的集合。

添加和删除元素时,都需要将添加或者删除元素位置后面的元素全部使用System.arraycopy方法copy移动一遍。比较耗时。

但是由于基于数组实现,所以获取指定位置的元素时,比较快。因为可以直接使用下标找到指定位置上的元素。

所以。

如果使用集合时,更多的是通过下标找指定元素。那么建议使用ArrayList。

如果使用集合时,需要频繁的删除或者插入元素。那么建议使用LinkedList。

LinkedList详解:https://blog.csdn.net/weixin_37730482/article/details/73810685

附:ArrayList官方文档

https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值