数据结构-ArrayList源码解析

一、ArrayList简介

1.1、ArrayList概述

  1)ArrayList是个动态数组,它是基于数组实现的List类。

  2)该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加。

    如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能。

  3)ArrayList的用法和Vector向类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用。

    另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。

  4)ArrayList的关系:

      

1.2、ArrayList的数据结构

  分析一个类的时候,数据结构往往是它的灵魂所在,理解底层的数据结构其实就理解了该类的实现思路,具体的实现细节再具体分析。

  ArrayList的数据结构,底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作底层都是基于数组的。

二、ArrayList源码分析

  

 

  分析:

    1)ArrayList就继承这个AbstractList类,拿到一些通用的方法,然后自己在实现一些自己特有的方法,这样一来,让代码更简洁,就继承结构最底层的类中通用的方法都抽取出来,先一起实现了,减少重复代码。所以一般看到一个类上面还有一个抽象类,应该就是这个作用。

    2)RandomAccess接口:这个是一个标记性接口,通过查看api文档,它的作用就是用来快速随机存取,有关效率的问题,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高,例如arrayList。

                而没有实现该接口的话,使用Iterator来迭代,这样性能更高,例如linkedList。所以这个标记性只是为了让我们知道我们用什么样的方式去获取数据性能更好。

      Cloneable接口:实现了该接口,就可以使用Object.Clone()方法了。

      Serializable接口:实现该序列化接口,表明该类可以被序列化,什么是序列化?简单的说,就是能够从类变成字节流传输,然后还能从字节流变成原来的类。

2.2、类中的属性

private static final long serialVersionUID = 8683452581122892189L;

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * Shared empty array instance used for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * Shared empty array instance used for default sized empty instances. We
 * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
 * first element is added.
 */
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.
 */
transient Object[] elementData; // non-private to simplify nested class access

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

DEFAULT_CAPACITY:默认的容量,值为10

EMPTY_ELEMENTDATA:空数组,初始化用

DEFAULTCAPACITY_EMPTY_ELEMENTDATA :第一添加时,用

elementData:实际的数据存储数组

Size集合的大小。

2.3、构造方法

  ArrayList有三个构造方法:

    

  1)无参构造方法  

/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2)有参构造函数一

/**
 * Constructs a list containing the elements of the specified
 * collection, in the order they are returned by the collection‘s
 * iterator.
 *
 * @param c the collection whose elements are to be placed into this list
 * @throws NullPointerException if the specified collection is null
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

总结:arrayList的构造方法就做一件事情,就是初始化一下储存数据的容器,其实本质上就是一个数组,在其中就叫elementData。

2.4、核心方法

  2.4.1、add()方法(有四个)

    

    1)boolean add(E);//默认直接在末尾添加元素

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了。

      ensureCapacityInternal(xxx); 确定内部容量的方法

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

ensureExplicitCapacity(minCapacity);
}

   

判断初始化的elementData是不是空的数组,如果是空的话,minCapacity=size+1;其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小。

ensureExplicitCapacity方法就是真正的判断elementData是否够用

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

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

这里先给修改数量加加,然后判断是否需要扩容,需要就调用grow方法。

Grow方法是最核心的方法

/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
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);
}

这里用位的右移来扩充容量,这里会进行数组COPY,对于arrayList来说,数组COPY很占用资源。所以在实际应用中,如果一开始能预估大小,最好给一个适合的长度,这样可以防止数组进行COPY带来的开销。

    2)void add(int,E);在特定位置添加元素,也就是插入元素

 /**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
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++;
}

这个和上一个方法不同的地方是,用rangeCheckForAdd判断是否越界,而且如果把数据元素放在数组中,需要数组COPY,这个开销就比较大。 

分析:

     

  2.4.2、删除方法

    其实这几个删除方法都是类似的。我们选择几个讲,其中fastRemove(int)方法是private的,是提供给remove(Object)这个方法用的。

    

1)remove(int):通过删除指定位置上的元素

/**
 * 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) {
    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;
}

  1. 检查index是否合法
  2. 增加修改次数
  3. 取出原素
  4. 根据index计算数组COPY移动的位数,然后进行数组COPY,这个要说的是如果从数组最后一位删除的话,是不需要移动数组的。

 2)remove(Object):这个方法可以看出来,arrayList是可以存放null值得。

/**
 * 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 ? get(i)==null : 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;
}

这里和上面不同的是,这里需要循环查找元素,fastRmove和第一个删除方法类似。所以这里要更慢一些。

1)clear():将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉

/**
 * Removes all of the elements from this list.  The list will
 * be empty after this call returns.
 */
public void clear() {
    modCount++;

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

size = 0;
}

   

  

  2.4.4、indexOf()方法

/**
 * Returns the index of the first occurrence of the specified element
 * in this list, or -1 if this list does not contain the element.
 * More formally, returns the lowest index <tt>i</tt> such that
 * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
 * or -1 if there is no such index.
 */
public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

  从头开始查找与指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存放null元素的。与此函数对应的lastIndexOf,表示从尾部开始查找。

  2.4.5、get()方法

/**
 * Returns the element at the specified position in this list.
 *
 * @param  index index of the element to return
 * @return the element at the specified position in this list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    rangeCheck(index);

return elementData(index);
}

从这里get是最快的,速度是n(1)

三、总结 

1)arrayList可以存放null。
2)arrayList本质上就是一个elementData数组。
3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
4)arrayList中获取快,直接添加快,插入、删除慢。
5)如果确定数据量,最好是给集合默认值,防止数组COPY.
6)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。

原文地址:https://www.cnblogs.com/vkbwxdx/p/10166323.html

时间: 2024-10-14 15:58:40

数据结构-ArrayList源码解析的相关文章

Java集合干货系列-(一)ArrayList源码解析

前言 今天来介绍下ArrayList,在集合框架整体框架一章中,我们介绍了List接口,ArrayList继承了AbstractList,实现了List.ArrayList在工作中经常用到,所以要弄懂这个类是极其重要的.构造图如下:蓝色线条:继承绿色线条:接口实现 正文 ArrayList简介 ArrayList定义 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomA

面试必备:ArrayList源码解析(JDK8)

面试必备:ArrayList源码解析(JDK8) https://blog.csdn.net/zxt0601/article/details/77281231 概述很久没有写博客了,准确的说17年以来写博客的频率降低到一个不忍直视的水平.这个真不怪我,给大家解释一下. 一是自从做了leader,整天各种事,开会,过需求,无限循环.心很累,时间也被无线压榨 二 我本身也在学习一些其他的技术,比如ReactNative,也看了半天的kotlin,撸了几个groovy脚本.gradle插件. 三 是打

ArrayList源码解析

ArrayList源码解析 ArrayList简介: ArrayList 是list接口的一个常用实现类.它的对象可以认为是一维数组的"类版本".我们很快就可以看到,ArrayList 对象可以看做是一维数组的改良版本.类似于数组,ArrayList 对象支持元素的随机访问:也就是说,只要给出元素的索引,任何元素的访问时间都是常数.但是同数组不同的是,ArrayList 对象的大小在程序执行的过程中可以自动进行调整,并且ArrayList对象具有在任何索引位置插入和删除对象的方法,而数

给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析

原文出自吞噬天地,链接整理自ImportNew 给jdk写注释系列之jdk1.6容器(2):LinkedList源码解析 给jdk写注释系列之jdk1.6容器(3):Iterator设计模式 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析 给jdk写注释系列之jdk1.6容器(5)-LinkedHashMap源码解析 给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&Map迭代器 给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析 工作中

源码解读:ArrayList源码解析(JDK8)

ArrayList源码解析(JDK8) 更详细的讲解可以参考这篇博文,本文只讲解在阅读源码中个人遇到的问题. 面试必备:ArrayList源码解析(JDK8) 构造函数 /** * ArrayList容器默认初始容量 */ private static final int DEFAULT_CAPACITY = 10; /** * 用于有参构造的空数组 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 用于无参构造的空数组

ArrayList源码解析(一)

目录 1.位置 2.变量和常量 3.构造函数 4.trimToSize()方法 正文 源码解析系列主要对Java的源码进行详细的说明,由于水平有限,难免出现错误或描述不准确的地方,还请大家指出. 回到顶部 1.位置 ArrayList位于java.util包中. 1 package java.util; 2 3 import java.util.function.Consumer; 4 import java.util.function.Predicate; 5 import java.util

Java 1.8 ArrayList源码解析

1 // 非线程安全 2 // 继承了AbstractList类 3 // 实现了List.RandomAccess.Cloneable.java.io.Serializable接口 4 // 后面3个接口是标记接口,没有抽象方法. 5 // 表示ArrayList可以随机访问.浅复制.序列化和反序列化. 6 public class ArrayList<E> extends AbstractList<E> 7 implements List<E>, RandomAcc

JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)

文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!) 一.写在前面 这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList.HashMap及其对应的线程安全实现,此文章作为自己相关学习的一个小结,记录学习成果的同时,也希望对有缘的朋友提供些许帮助. 当然,能力所限,难免有纰漏,希望发现的朋友能够予以指出,不胜感激,以免误导了大家! 二.稳扎稳打过源码 首先,是源码内部的成员变量定义以及构造方法: 1 /** 2 * Default in

第二章 ArrayList源码解析

一.对于ArrayList需要掌握的七点内容 ArrayList的创建:即构造器 往ArrayList中添加对象:即add(E)方法 获取ArrayList中的单个对象:即get(int index)方法 删除ArrayList中的对象:即remove(E)方法 遍历ArrayList中的对象:即iterator,在实际中更常用的是增强型的for循环去做遍历 判断对象是否存在于ArrayList中:contain(E) ArrayList中对象的排序:主要取决于所采取的排序算法(以后讲) 二.源