Java 集合框架03---ArrayList的源码分析

简介: 上篇我们学习了Collection的相关源码,下面我们将继续学习List 家族中最常用的一个集合ArrayList。

上篇我们学习了Collection的相关源码,下面我们将继续学习List 家族中最常用的一个集合ArrayList。

我们将从以下几个方面剖析ArrayList。


1.ArrayList的简介

2.ArrayList的数据结构

3.ArrayList的扩容机制

4.ArrayList的遍历

注意事项

全文对ArrayList的源码解析均是基于JDK1.8


ArrayList的简介


ArrayList的定义

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}

ArrayList的类图如下:

e58949486048842966eec2e858a2152c_SouthEast.jpg

从上述定义和类图中我们可以得到以下几点信息:


1.ArrayList是一个集合,其继承了AbstractList,实现了List 接口,有集合的基本操作,如添加,查找,删除。

2.ArrayList实现了Serializable接口,说明其可以被序列化,可以序列化之后进行传输。

3.ArrayList实现了RandomAccess接口,说明其可以被随机访问。

4.ArrayList实现了Cloneable接口,说明其可以被克隆

5.ArrayList支持泛型。

ArrayList的数据结构


看到本结我们心中不免有几个疑问:例如,

1. ArrayList到底是一个怎样的数据结构呢?

2. ArrayList是如何实现自动扩容的呢?

带着疑问我们来从源码中寻找答案:

//初始大小
        private static final int DEFAULT_CAPACITY = 10;
        //初始数组
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     * 简译:没有指定初始大小的`ArrayList`再添加第一个元素之后初始大小变成10。
     */
    transient Object[] elementData; // non-private to simplify nested class access
    //构造指定初始容量的集合
    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);
        }
    }
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    //将一个继承自`Collection`的集合转化成`ArrayList`
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }


小结:从上述源码中可以看出,ArrayList是一个数组结构,所以其拥有数组的所有特性。

默认构造成Object[],如果传入泛型则构造成对应泛型的数组。


ArrayList的扩容机制


接着我们在看看ArrayList是如何实现自动扩容的。

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!(确保当前ArrayList维护的数组具有存储新元素的能力,经过处理之后将元素存储在数组elementData的尾部)
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
        private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    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);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }


本部分源码的调用结构是

add--调用->ensureCapacityInternal--调用->ensureExplicitCapacity--调用-->grow--调用-->hugeCapacity


从中我们可知

1. ArrayList的极限大小是2*32 -1。(存储元素的个数为整型的范围。)

2. 当ArrayList扩容时首先会设计新的存储能力为 原来的1.5倍。

int newCapacity = oldCapacity + (oldCapacity >> 1);

确定ArrayList扩容之后最新的可存储元素之后,调用

elementData = Arrays.copyOf(elementData, newCapacity);

进行元素的元素的复制。

扩展:


public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
  public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }


arraycopy标识为native意味JDK的本地库,不可避免的会进行IO操作,如果频繁的对ArrayList进行扩容,毫不疑问会降低ArrayList的使用性能,因此当我们确定添加元素的个数的时候,我们可以事先知道并指定ArrayList的可存储元素的个数,这样当我们向ArrayList中加入元素的时候,就可以避免ArrayList的自动扩容,从而提高ArrayList的性能。


ArrayList的遍历


ArrayList的遍历的遍历主要有三种方式:

package com.jay.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
 * Created by xiang.wei on 2018/3/4
 *
 * @author xiang.wei
 */
public class ArrayListIterTest {
    public static void main(String[] args) {
        //待遍历的ArrayList
        List<Integer> testList = new ArrayList<>();
        for (int i = 0; i < 500000; i++) {
            testList.add(i);
        }
        testFori(testList);
        testFor(testList);
        testIter(testList);
    }
    private static void testFori(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            list.get(i);
        }
        System.out.println("RandomAccess用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
    private static void testFor(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        for (Integer integer : list) {
                ;
        }
        System.out.println("for迭代用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
    private static void testIter(List<Integer> list) {
        long startTime = System.currentTimeMillis();
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            iterator.next();
        }
//        for(Iterator<Integer> it = list.iterator(); it.hasNext(); ) {
//            it.next();
//        }
        System.out.println("iterator迭代器用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
}


我运行了5次,结果如下:

实验次数 RandomAccess(ms) For(ms) Iterator(ms)
第一次 10 21 9
第二次 10 16 17
第三次 10 19 11
第四次 11 18 18
第五次 13 21 12
平均数 10.8 19 13.4

从上述表格,我们可以看出RandomAccess(随机索引)访问的效率最高,For迭代的效率最低。

如果疑问,欢迎讨论。

相关的引用:

Java ArrayList的自动扩容机制

http://blog.csdn.net/yang1464657625/article/details/59109133


相关文章
|
2月前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
7天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
30 3
|
2月前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
24天前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
41 5
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
46 4
|
2月前
|
开发框架 Java 关系型数据库
Java哪个框架适合开发API接口?
在快速发展的软件开发领域,API接口连接了不同的系统和服务。Java作为成熟的编程语言,其生态系统中出现了许多API开发框架。Magic-API因其独特优势和强大功能,成为Java开发者优选的API开发框架。本文将从核心优势、实际应用价值及未来展望等方面,深入探讨Magic-API为何值得选择。
71 2
|
2月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
55 0
|
5月前
|
存储 Java 索引
【Java集合类面试二十四】、ArrayList和LinkedList有什么区别?
ArrayList基于动态数组实现,支持快速随机访问;LinkedList基于双向链表实现,插入和删除操作更高效,但占用更多内存。
|
5月前
|
存储 Java 索引
Java 中 ArrayList 和 LinkedList 之间的区别
【8月更文挑战第22天】
146 1
|
8月前
|
存储 安全 Java
java面试基础 -- ArrayList 和 LinkedList有什么区别, ArrayList和Vector呢?
java面试基础 -- ArrayList 和 LinkedList有什么区别, ArrayList和Vector呢?
69 0