ArrayList源码解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: ArrayList是基于List 接口,大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。

ArrayList是基于List 接口,大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。下面我们来分析ArrayList的源代码,ArrayList继承体系结构如下:

public class ArrayList<E> extends AbstractList<E>

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

ArrayList类继承AbstractList类并实现了List、Cloneable和Serializable接口,因而ArrayList具有克隆和序列化的功能。

属性

//默认初始容量

private static final int DEFAULT_CAPACITY = 10;

//用于空实例的共享空数组实例

private static final Object[] EMPTY_ELEMENTDATA = {};

//用于默认大小的空实例的共享空数组实例。与EMPTY_ELEMENTDATA数组的区别在于当第一个元素被加入进来的时候,它知道扩大多少

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//elementData数组用来存储ArrayList中的元素

transient Object[] elementData;

//ArrayList中存储的元素的个数

private int size;

构造函数

ArrayList提供了三种方式的构造器:

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

源码的注释是构造一个初始容量为 10 的空列表。即当我们不提供参数而new一个对象时,底层的数组就是直接用长度为10的空实例数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA进行实例化。难点就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA的长度我们还不知道,关于如何使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA构造了一个初始容量为10的空列表在下面的add(E e)函数源码的介绍中就会知道答案了。

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

传入参数来实例化底层的数组对象,其中对参数的3中情况进行了处理。当参数小于0时抛异常;当参数等于0时;用空实例数组对象EMPTY_ELEMENTDATA来初始化底层数组elementData。

public ArrayList(Collection<? extends E> c) {  
    elementData = c.toArray();  
    size = elementData.length;  
    // c.toArray might (incorrectly) not return Object[] (see 6260652)  
    if (elementData.getClass() != Object[].class)  
        elementData = Arrays.copyOf(elementData, size, Object[].class);  
} 

将容器Collection转化为数组赋给数组elementData,还对Collection转化是否转化为了Object[]进行了检查判断。如果Collection为空,则就将空的实例数组对象EMPTY_ELEMENTDATA赋给了elementData。

常用方法

修改

// 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。  
public E set(int index, E element) {  
    RangeCheck(index);  
  
    E oldValue = (E) elementData[index];  
    elementData[index] = element;  
    return oldValue;  
} 

添加

// 将指定的元素添加到此列表的尾部。  
public boolean add(E e) {  
    ensureCapacityInternal(size + 1);   
    elementData[size++] = e;  
    return true;  
} 

// 将指定的元素插入此列表中的指定位置。  
// 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。  
public void add(int index, E element) {  
    //位置有效性检查
    rangeCheckForAdd(index);
    //添加修改次数以及判断是否需要扩张数组长度
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //完成数组自身从index开始的所有元素拷贝到index+1开始且长度为size-index的位置上。
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
} 

// 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。  
public boolean addAll(Collection<? extends E> c) {  
    Object[] a = c.toArray();  
    int numNew = a.length;  
    ensureCapacityInternal(size + numNew);  // Increments modCount 
    //将a中的所有元素拷贝到数组elementData最后一个元素的后面 
    System.arraycopy(a, 0, elementData, size, numNew);  
    size += numNew;  
    return numNew != 0;  
} 

// 从指定的位置开始,将指定collection中的所有元素插入到此列表中。  
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);  
    Object[] a = c.toArray();  
    int numNew = a.length;  
    ensureCapacityInternal(size + numNew);  // Increments modCount  

    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
    System.arraycopy(a, 0, elementData, size, numNew);  
    size += numNew;  
    return numNew != 0;  
}

读取

public E get(int index) {
        rangeCheck(index);//有效性检查
        return elementData(index);
    }

删除

ArrayList提供了根据下标或者指定对象两种删除方法,如下:

// 删除此列表中指定位置上的元素。
public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = 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;
    }
// 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。
public boolean remove(Object o) {
// 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
        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;
    }

从源码中可以看到,无论指定对象o是否为null,都是在ArrayList中找到与此第一个相等的元素的位置,然后调用fastRemove(index)来进行移除;如果没有找到指定对象o的位置,则返回false,表示没有移除成功。

需要注意的是从数组中移除元素的操作,也会导致被移除的元素以后的所有元素的向前移动一个位置。remove(int index)从代码就可以看到,而 remove(Object o)则是调用了fastRemove(index),我们看下代码:

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
}

可以看到,我们需要左移的功能是在这里实现的

清除

//直接将数组中的所有元素设置为null即可,这样便于垃圾回收
public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

扩容

扩容需要要重点说明的一个,在添加方法中涉及到ensureCapacityInternal()方法,源码如下:

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

        ensureExplicitCapacity(minCapacity);
}

如果elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则就用默认容量10来进行开辟空间。这里的源码就解释了DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组和EMPTY_ELEMENTDATA数组的区别之所在。也说明了当我们用无参构造函数来实例化一个对象时,确实是构造的一个长度为10的数组对象。

接下来再看下ensureExplicitCapacity()方法

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

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

接着看grow()方法

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        /*
        下面两个if的作用为处理两种情况:
        1)第一种情况为:如果newCapacity扩展的过小。则应该至少扩张到所需的空间大小minCapacity
        2)第二种情况为:newCapacity扩张的过大,如果过大,则用Integer.MAX_VALUE来代替。
         */
        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);
    }

从源码中可以看到,此函数的功能就是一个数组的扩容。一般情况下是扩展到原来数组长度的1.5倍。 

但是,由于扩张1.5倍可能和我们的需要不一致,即可能太小,也可能太大。因此,就有了源码中的两个if条件的处理。即如果扩张1.5倍太小,则就用我们需要的空间大小minCapacity来进行扩容。如果扩张1.5倍太大或者是我们所需的空间大小minCapacity太大,则进行Integer.MAX_VALUE来进行扩容。

 

参考:

http://zhangshixi.iteye.com/blog/674856

https://blog.csdn.net/u010412719/article/details/51108357

https://blog.csdn.net/ns_code/article/details/35568011

相关文章
|
23天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
60 2
|
23天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
1月前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
45 3
|
2月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
59 5
|
2月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
135 5
|
2月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
2月前
|
算法 Java 程序员
Map - TreeSet & TreeMap 源码解析
Map - TreeSet & TreeMap 源码解析
38 0
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
72 0
|
2月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
57 0
|
2月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
64 0

推荐镜像

更多