ArrayList 全面详解

举报
mikechen的互联网架构 发表于 2024/10/31 22:13:40 2024/10/31
【摘要】 本文详细解析了Java集合框架中的ArrayList,包括其定义、特点、成员变量、构造函数、API、主要方法和扩容机制等。欢迎留言交流。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。

关注△mikechen的互联网架构△,10年+BAT架构经验倾囊相授

image.png

大家好,我是 [mikechen | 陈睿]((https://mikechen.cc/about-me) 。

image.png

ArrayList的定义

ArrayList 是 java 集合框架中比较常用的数据结构了,实现了 List 接口。

image.png

ArrayList相当于动态数组,与Java中的数组相比,它的容量能动态增长。

ArrayList的特点

  • 允许插入的元素重复
  • 插入的元素是有序的
  • 动态扩容
  • 非线程安全
  • 基于动态数组的数据结构
  • 擅长随机访问(get set)

ArrayList的成员变量

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
 

// 实际元素个数

private int size; 
 

// 存放的元素集合

transient Object[] elementData; 


// 默认初始大小10

private  static  final  int DEFAULT_CAPACITY = 10; 

 

 

//记录对List操作的次数,主要使用在Iterator中,防止迭代过程中集合被修改

protected  transient  int modCount = 0; 


// 下面两个变量用在构造函数中,判断第一次添加元素时知道从空的构造函数还是有参构造函数被初始化的

private static final Object[] EMPTY_ELEMENTDATA = {};

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // ...

}

ArrayList的构造函数

image.png

1. 无参数构造

//无参数的构造方法

    public ArrayList() {

        //此时 elementData为{}

        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

    }

构造一个初始容量为10的空的list集合,但构造函数只是给elementData赋值了一个空的数组,其实是在第一次添加元素时扩大至10。

2.有参构造函数

/**

* Constructs an empty list with the specified initial capacity.

*

* @param  initialCapacity  the initial capacity of the list

* @throws IllegalArgumentException if the specified initial capacity

*         is negative

*/

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

    }

}

只要保证传递参数值 不 < o,即可,小于 0,会抛异常。

3.集合参数构造函数

 public ArrayList(Collection<? extends E> c) {

        elementData = c.toArray(); //转为数组

        if ((size = elementData.length) != 0) { //集合大小不等于0

            // c.toArray might (incorrectly) not return Object[] (see 6260652)

            if (elementData.getClass() != Object[].class)//集合类型不是Object类型

                elementData = Arrays.copyOf(elementData, size, Object[].class);//复制

        } else {//集合大小为0,指定一个空数组

            // replace with empty array.

            this.elementData = EMPTY_ELEMENTDATA;

        }

    }

ArrayList的API

// Collection中定义的API

boolean             add(E object)

boolean             addAll(Collection<? extends E> collection)

void                clear()

boolean             contains(Object object)

boolean             containsAll(Collection<?> collection)

boolean             equals(Object object)

int                 hashCode()

boolean             isEmpty()

Iterator<E>         iterator()

boolean             remove(Object object)

boolean             removeAll(Collection<?> collection)

boolean             retainAll(Collection<?> collection)

int                 size()

<T> T[]             toArray(T[] array)

Object[]            toArray()

// AbstractCollection中定义的API

void                add(int location, E object)

boolean             addAll(int location, Collection<? extends E> collection)

E                   get(int location)

int                 indexOf(Object object)

int                 lastIndexOf(Object object)

ListIterator<E>     listIterator(int location)

ListIterator<E>     listIterator()

E                   remove(int location)

E                   set(int location, E object)

List<E>             subList(int start, int end)

// ArrayList新增的API

Object               clone()

void                 ensureCapacity(int minimumCapacity)

void                 trimToSize()

void                 removeRange(int fromIndex, int toIndex)

ArrayList主要方法解析

image.png

**1.add增加
**

 public boolean add(E e) {//添加一个元素

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        elementData[size++] = e; //长度+1

        return true; //返回boolean 类型

    }

    

    public void add(int index, E element) {//指定索引位置添加一个元素

        rangeCheckForAdd(index);//检查集合长度级索引大小

 

 

 

 

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        System.arraycopy(elementData, index, elementData, index + 1,

                         size - index);//复制

        elementData[index] = element; //元素放到集合指定索引的位置

        size++;//长度+1

    }

 

 

 

 

    private void ensureCapacityInternal(int minCapacity) {

        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//默认长度10

    }

 

 

 

    private void ensureExplicitCapacity(int minCapacity) {

        modCount++;//记录修改次数

 

 

           //

        if (minCapacity - elementData.length > 0)//传入长度大于当前长度,扩容

            grow(minCapacity);

    }

    private void grow(int minCapacity) {

        // overflow-conscious code

        int oldCapacity = elementData.length;//老容量

        int newCapacity = oldCapacity + (oldCapacity >> 1);//老容量+ 老容量/2

        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);//扩容拷贝

    }

 

 

 

    private static int hugeCapacity(int minCapacity) {

        if (minCapacity < 0) // overflow 传入容量 < 0 抛异常

            throw new OutOfMemoryError();

        return (minCapacity > MAX_ARRAY_SIZE) ?

            Integer.MAX_VALUE :

            MAX_ARRAY_SIZE;

    }

 

添加元素时,会指定默认为10的容量,当添加元素导致集合容量大于10,触发扩容机制,扩容为原来的1.5倍。

2.remove移除

 public E remove(int index) {

        //校验删除位置是否合理

        rangeCheck(index);

      // 同add理由一样

        modCount++;

       // 保存待删除元素

        E oldValue = elementData(index);

        // 判断删除位置:如果>0不在尾部需要调用System.arraycopy,否则在尾部直接删除

        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;

    }

当我们调用 remove(int index) 时,首先会检查 index 是否合法,然后再判断要删除的元素是否位于数组的最后一个位置。

如果 index 不是最后一个,就再次调用 System.arraycopy() 方法拷贝数组。

如果 index 是最后一个元素那么就直接将数组的最后一个位置空,size – 1即可。

3.get获取

 public E get(int index) { //获取指定索引的值

        rangeCheck(index);//是否越界

 

        return elementData(index);//返回指定下标的值

    }

    private void rangeCheck(int index) {

        if (index >= size) //索引大于 集合长度,抛异常

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    }

由于ArrayList底层是基于数组实现的,所以获取元素就相当简单了,直接调用数组随机访问即可。

4.set修改

public class ArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    public E set(int index, E element) {

        rangeCheck(index);

 

        E oldValue = elementData(index);

        elementData[index] = element;

        return oldValue;

    }}

修改指定位置的元素为新元素,首先需要校验给定index的值,index必须大于等于0,小于size,然后将新元素保存到index位置,并将旧元素返回。

5.扩容操作

public void ensureCapacity(int minCapacity) {

        //修改计时器

        modCount++;

        //ArrayList容量大小

        int oldCapacity = elementData.length;

        /*

         * 若当前需要的长度大于当前数组的长度时,进行扩容操作

         */

        if (minCapacity > oldCapacity) {

            Object oldData[] = elementData;

            //计算新的容量大小,为当前容量的1.5倍

            int newCapacity = (oldCapacity * 3) / 2 + 1;

            if (newCapacity < minCapacity)

                newCapacity = minCapacity;

            //数组拷贝,生成新的数组

            elementData = Arrays.copyOf(elementData, newCapacity);

        }

    }

ensureCapacityInternal方法的目的是确保给定的参数指定的容量值。

真正的扩容逻辑位于grow方法中:

public class ArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    private void grow(int minCapacity) {

        // overflow-conscious code

        int oldCapacity = elementData.length;

        int newCapacity = oldCapacity + (oldCapacity >> 1);// 扩容为原容量的1.5倍

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

    }

    // 处理超限问题

    // 如果给定的minCapacity为负数(首位为1)则抛出异常错误OutOfMemoryError

    // 如果给定容量大于数组最大容量,则取整数的最大值为容量,否则使用数组的最大容量作为扩容容量

    private static int hugeCapacity(int minCapacity) {

        if (minCapacity < 0) // overflow

            throw new OutOfMemoryError();

        return (minCapacity > MAX_ARRAY_SIZE) ?

            Integer.MAX_VALUE :

            MAX_ARRAY_SIZE;

    }}

ensureCapacity(),该方法就是ArrayList的扩容方法,每次扩容处理会是1.5倍。

1.5倍的扩容是最好的倍数,因为一次性扩容太大(例如2.5倍)可能会浪费更多的内存(1.5倍最多浪费33%,而2.5被最多会浪费60%,3.5倍则会浪费71%……)。

但是一次性扩容太小,需要多次对数组重新分配内存,对性能消耗比较严重。

所以1.5倍刚刚好,既能满足性能需求,也不会造成很大的内存消耗。

Fail-Fast机制

在我们每次操作集合的时候,都会记录一个修改次数。

modCount++ 其实他就是fail-fast机制,它是Java集合的一种错误检测机制。

当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。

例如:

假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容).

那么,这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

ArrayList总结

最后做一下总结,知识点归纳:

ArrayList底层采用数组实现,拥有快速随机访问能力,但是非线程安全的集合。

ArrayList默认容量为10,扩容规则为当要保存的新元素所需的容量不足时触发。

扩容机制为首先扩容为原始容量的 1.5 倍。

扩容之后是通过数组的拷贝来确保元素的准确性的,所以尽可能减少扩容操作。

如果在遍历的时候发生结构性变化,会触发ConcurrentModificationException异常。

结构性变化包括:添加新元素,删除元素。

ArrayList支持序列化功能,支持克隆(浅拷贝)功能,排序功能等。

以上,是 ArrayList 详细解析,欢迎评论区留言交流或拓展。

我是 mikechen | 陈睿 ,关注【mikechen的互联网架构】,10年+BAT架构技术倾囊相授。

本文已同步我的技术博客 www.mikechen.cc,更新至我原创的《30W+字大厂架构技术合集》中。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。