ArrayList 源码浅析

Java ArrayList

ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。

ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现。除该类未实现同步外,其余跟Vector大致相同。每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量。当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。前面已经提过,Java泛型只是编译器提供的语法糖,所以这里的数组是一个Object数组,以便能够容纳任何类型的对象。
ArrayList 继承了 AbstractList ,并实现了 List 接口。

在这里插入图片描述

ArrayList 类位于 java.util 包中,使用前需要引入它,语法格式如下:

import java.util.ArrayList;  // 引入 ArrayList 类  
ArrayList<E> objectName =new  ArrayList<>(); // 初始化
  • E: 泛型数据类型,用于设置 objectName 的数据类型,只能为引用数据类型
  • objectName: 对象名。

ArrayList 是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
在这里插入图片描述

size(), isEmpty(), get(), set()方法均能在常数时间内完成,add()方法的时间开销跟插入位置有关,addAll()方法的时间开销跟添加元素的个数成正比。
其余方法大都是线性时间。 为追求效率,ArrayList没有实现同步(synchronized),如果需要多个线程并发访问,用户可以手动同步,也可使用Vector替代。

添加元素

ArrayList 类提供了很多有用的方法,添加元素到 ArrayList 可以使用 add() 方法:

  • add()
/**
 * 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;
}

在这里插入图片描述

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

add(int index, E e)需要先对元素进行移动,然后完成插入操作,也就意味着该方法有着线性的时间复杂度。
在这里插入图片描述

使用示例

import java.util.ArrayList;  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<String> sites = new ArrayList<String>();  
        sites.add("Google");  
        sites.add("Bing");  
        sites.add("Taobao");  
        sites.add("Weibo");  
        System.out.println(sites);  
    }  
} 

以上实例,执行输出结果为:

[Google,  Bing,  Taobao,  Weibo]
  • addAll()

addAll()方法可以一次添加多个元素,根add()一样它也有两个版本,一个是直接在末尾添加,一个时从指定的位置插入,插入之前也需要对空间进行检查
如果需要扩容则自动扩容,从指定位置插入的话也会存在元素移动。addAll()的时间复杂度和插入元素的数量以及插入的位置有关。

/**
 * Appends all of the elements in the specified collection to the end of
 * this list, in the order that they are returned by the
 * specified collection's Iterator.  The behavior of this operation is
 * undefined if the specified collection is modified while the operation
 * is in progress.  (This implies that the behavior of this call is
 * undefined if the specified collection is this list, and this
 * list is nonempty.)
 *
 * @param c collection containing elements to be added to this list
 * @return <tt>true</tt> if this list changed as a result of the call
 * @throws NullPointerException if the specified collection is null
 */
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

/**
 * Inserts all of the elements in the specified collection into this
 * list, starting at the specified position.  Shifts the element
 * currently at that position (if any) and any subsequent elements to
 * the right (increases their indices).  The new elements will appear
 * in the list in the order that they are returned by the
 * specified collection's iterator.
 *
 * @param index index at which to insert the first element from the
 *              specified collection
 * @param c collection containing elements to be added to this list
 * @return <tt>true</tt> if this list changed as a result of the call
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws NullPointerException if the specified collection is null
 */
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, index, numNew);
    size += numNew;
    return numNew != 0;
}

访问元素

访问 ArrayList 中的元素可以使用 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);
    }

使用示例

import java.util.ArrayList;  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<String> sites = new ArrayList<String>();  
        sites.add("Google");  
        sites.add("Bing");  
        sites.add("Taobao");  
        sites.add("Weibo");  
        System.out.println(sites.get(1));  // 访问第二个元素  
    }  
}  

注意:数组的索引值从 0 开始。

以上实例,执行输出结果为:

Bing

修改元素

如果要修改 ArrayList 中的元素可以使用 set() 方法:

/**
 * Replaces the element at the specified position in this list with
 * the specified element.
 *
 * @param index index of the element to replace
 * @param element element to be stored at the specified position
 * @return the element previously at the specified position
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

使用示例

import java.util.ArrayList;  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<String> sites = new ArrayList<String>();  
        sites.add("Google");  
        sites.add("Bing");  
        sites.add("Taobao");  
        sites.add("Weibo");  
        sites.set(2, "Wiki"); // 第一个参数为索引位置,第二个为要修改的值  
        System.out.println(sites);  
    }  
}  

以上实例,执行输出结果为:

[Google,  Bing,  Wiki,  Weibo]

删除元素

如果要删除 ArrayList 中的元素可以使用 remove() 方法:
remove()方法有两种实现,一种时指定位置删除元素remove(int index),另一种是第一个满足elementData[index]的元素remove(Object o),删除操作后需要将删除节点之后的
元素向前移动。同时为了让GC生效,需要将最后一个位置显式赋值为null。虽然有了垃圾收集器但是并不代表一定不会出现内存泄漏,GC的依据是该对象是否还有引用指向,如果不手动赋值null或者是该位置有新的元素覆盖,那么原来的对象将不会被回收。

/**
 * 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);
    // clear to let GC do its work
    elementData[--size] = null; 
    return oldValue;
}

使用示例

import java.util.ArrayList;  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<String> sites = new ArrayList<String>();  
        sites.add("Google");  
        sites.add("Bing");  
        sites.add("Taobao");  
        sites.add("Weibo");  
        sites.remove(3); // 删除第四个元素  
        System.out.println(sites);  
    }  
}  

以上实例,执行输出结果为:

[Google,  Bing,  Taobao]

计算大小

如果要计算 ArrayList 中的元素数量可以使用 size() 方法:

import java.util.ArrayList;  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<String> sites = new ArrayList<String>();  
        sites.add("Google");  
        sites.add("Bing");  
        sites.add("Taobao");  
        sites.add("Weibo");  
        System.out.println(sites.size());  
    }  
}  

以上实例,执行输出结果为:

4

迭代数组列表

我们可以使用 for 来迭代数组列表中的元素:

import java.util.ArrayList;  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<String> sites = new ArrayList<String>();  
        sites.add("Google");  
        sites.add("Bing");  
        sites.add("Taobao");  
        sites.add("Weibo");  
        for (int i = 0; i < sites.size(); i++) {  
            System.out.println(sites.get(i));  
        }  
    }  
}  

以上实例,执行输出结果为:

Google  
Bing  
Taobao  
Weibo

也可以使用 for-each 来迭代元素:

import java.util.ArrayList;  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<String> sites = new ArrayList<String>();  
        sites.add("Google");  
        sites.add("Bing");  
        sites.add("Taobao");  
        sites.add("Weibo");  
        for (String i : sites) {  
            System.out.println(i);  
        }  
    }  
}  

以上实例,执行输出结果为:

Google  
Bing  
Taobao  
Weibo

其他的引用类型

ArrayList 中的元素实际上是对象,在以上实例中,数组列表元素都是字符串 String 类型。

如果我们要存储其他类型,而 <E> 只能为引用数据类型,这时我们就需要使用到基本类型的包装类。

基本类型对应的包装类表如下:

基本类型引用类型
booleanBoolean
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter

此外,BigInteger、BigDecimal 用于高精度的运算,BigInteger 支持任意精度的整数,也是引用类型,但它们没有相对应的基本类型。

ArrayList<Integer> li=new  ArrayList<>();  // 存放整数元素  
ArrayList<Character> li=new  Arraylist<>();  // 存放字符元素

以下实例使用 ArrayList 存储数字(使用 Integer 类型):

import java.util.ArrayList;  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<Integer> myNumbers = new ArrayList<Integer>();  
        myNumbers.add(10);  
        myNumbers.add(15);  
        myNumbers.add(20);  
        myNumbers.add(25);  
        for (int i : myNumbers) {  
            System.out.println(i);  
        }  
    }  
}  

以上实例,执行输出结果为:

10  
15  
20  
25

ArrayList 排序

Collections 类也是一个非常有用的类,位于 java.util 包中,提供的 sort() 方法可以对字符或数字列表进行排序。

以下实例对字母进行排序:

import java.util.ArrayList;  
import java.util.Collections;  // 引入 Collections 类  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<String> sites = new ArrayList<String>();  
        sites.add("Taobao");  
        sites.add("Wiki");  
        sites.add("Bing");  
        sites.add("Weibo");  
        sites.add("Google");  
        Collections.sort(sites);  // 字母排序  
        for (String i : sites) {  
            System.out.println(i);  
        }  
    }  
}  

以上实例,执行输出结果为:

Google  
Bing  
Taobao  
Weibo  
Wiki

以下实例对数字进行排序:

import java.util.ArrayList;  
import java.util.Collections;  // 引入 Collections 类  
  
public class BingTest {  
    public static void main(String[] args) {  
        ArrayList<Integer> myNumbers = new ArrayList<Integer>();  
        myNumbers.add(33);  
        myNumbers.add(15);  
        myNumbers.add(20);  
        myNumbers.add(34);  
        myNumbers.add(8);  
        myNumbers.add(12);  
  
        Collections.sort(myNumbers);  // 数字排序  
  
        for (int i : myNumbers) {  
            System.out.println(i);  
        }  
    }  
}  

以上实例,执行输出结果为:

8 
12  
15  
20  
33  
34

ArrayList 方法

Java ArrayList 常用方法列表如下:

方法描述
add()将元素插入到指定位置的 arraylist 中
addAll()添加集合中的所有元素到 arraylist 中
clear()删除 arraylist 中的所有元素
clone()复制一份 arraylist
contains()判断元素是否在 arraylist
get()通过索引值获取 arraylist 中的元素
indexOf()返回 arraylist 中元素的索引值
removeAll()删除存在于指定集合中的 arraylist 里的所有元素
remove()删除 arraylist 里的单个元素
size()返回 arraylist 里元素数量
isEmpty()判断 arraylist 是否为空
subList()截取部分 arraylist 的元素
set()替换 arraylist 中指定索引的元素
sort()对 arraylist 元素进行排序
toArray()将 arraylist 转换为数组
toString()将 arraylist 转换为字符串
ensureCapacity()设置指定容量大小的 arraylist
lastIndexOf()返回指定元素在 arraylist 中最后一次出现的位置
retainAll()保留 arraylist 中在指定集合中也存在的那些元素
containsAll()查看 arraylist 是否包含指定集合中的所有元素
trimToSize()将 arraylist 中的容量调整为数组中的元素个数
removeRange()删除 arraylist 中指定索引之间存在的元素
replaceAll()将给定的操作内容替换掉数组中每一个元素
removeIf()删除所有满足特定条件的 arraylist 元素
forEach()遍历 arraylist 中每一个元素并执行特定操作

底层实现

数据结构

/**
 * 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;

构造

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

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

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

自动扩容机制

每次向数组中添加元素时,都会检查添加后元素的个数是否会超出当前数组的长度,如果超出当前数组长度,
数组见会进行扩容,以此来满足数据的需求。数组扩容通过一个public的方法ensureCapacity(int minCapacity)实现。
实际使用中在添加大量元素前,也可以使用ensureCapacity方法来手动扩容,用这种方法减少地震时再分配的数量。
当数组进行扩容的时候,会将老数组中的元素copy一份到新的数组中,每次容量增长的值约是原来容量的1.5倍。复制需要花费比较大的性能代价,
因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。
或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。

/**
 * Increases the capacity of this <tt>ArrayList</tt> instance, if
 * necessary, to ensure that it can hold at least the number of elements
 * specified by the minimum capacity argument.
 *
 * @param   minCapacity   the desired minimum capacity
 */
public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

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

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

/**
 * The maximum size of array to allocate.
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨若凌风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值