Java数据结构之ArrayList 源码解析

ArrayList是 List类型的集合类中最常使用的,本文基于Java1.8,对于ArrayList的实现原理做一下详细讲解。

一、ArrayList实现原理总结

ArrayList的实现原理总结如下:

①数据存储是基于数组实现的,默认初始容量为10;

②添加数据时,首先需要检查元素个数是否超过数组容量,如果超过了则需要对数组进行扩容;插入数据时,需要将插入点k开始到数组末尾的数据全部向后移动一位。

③数组的扩容是新建一个大容量(原始数组大小+扩充容量)的数组,然后将原始数组数据拷贝到新数组,然后将新数组作为扩容之后的数组。数组扩容的操作代价很高,我们应该尽量减少这种操作。

④删除数据时,需要将删除点+1位置开始到数组末尾的数据全部向前移动一位。

⑤获取数据很快,根据数组下表可以直接获取。

 

二、ArrayList的实现原理详解

1. ArrayList概述:

   ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
   每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
   注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。

 

2. ArrayList的实现:

   对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。下面我们来分析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;

三个构造函数:

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


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

类内private 方法 也是一些辅助方法:

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

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

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

常用方法:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
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++;
}
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;
}
//排序底层使用Arrays.sort排序。
public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}
//底层使用Arrays.copyOf 方法生成指定大小的新数组
public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}
//底层使用Arrays.copyOf 方法生成指定大小的新数组
public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}
//底层使用Arrays.copyOf 方法生成指定大小类型的新数组
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Arrays.copyOf 方法实现:

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;
}
remove 和fastremove 的区别:

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

/*****************************************************************************************************************************/

ArrayList是List里面使用率最高的

  package collection.lession7;

 

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Collection;

import java.util.Iterator;

import java.util.List;

  

public class Lession7 {

  

 public static void main(String[] args) {

  testNormal();

  testSpecial();

  // 一个最常见的错误

  testForProblem();

 }

  

 public static void testNormal() {

  // -------------------------------------------------------

  // 声明一个列表

  // 允许放入任何数据

  // -------------------------------------------------------

  ArrayList list = new ArrayList();

  // 放入整数

  // 当然你用 new Integer(1)也可以

  list.add(1);

  // 放入字符串

  list.add("abc");

  // 放入浮点数

  list.add(new Float(1.11));

  // add会将数据保存到列表的尾部

  showList(list); // 1, abc, 1.11]

  

  // 下面我们在列表的头部增加数据

  list.add(02);

  list.add(0"bcd");

  list.add(0new Double(2.34));

  // 列表可以指定插入的位置

  // 0 是头部第一个位置,所以数据都逐个放到最前面了

  showList(list); // [2.34, bcd, 2, 1, abc, 1.11]

  

  // 下面我们插入到我们希望的任何位置

  // 当然不能越界,(0 到 list.size()-1)范围内才可以

  list.add(13);

  list.add(4"xyz");

  // 数据被放到了正确的位置

  showList(list); // [2.34, 3, bcd, 2, xyz, 1, abc, 1.11]

  

  // -------------------------------------------------------

  // 我们有了数据,我们来测试读取数据

  // -------------------------------------------------------

  // 我们可以通过指定索引的位置,来拿到我们希望的数据

  System.out.println(list.get(0)); // 2.34

  System.out.println(list.get(4)); // xyz

  

  // -------------------------------------------------------

  // 测试是否存在某个数据

  // -------------------------------------------------------

  System.out.println(list.contains("xyz")); // true

  

  // 测试是否包含一组数据

  Collection c = new ArrayList();

  c.add(1);

  c.add(2);

  System.out.println(list.containsAll(c)); // true

  c.add(3);

  c.add(4);

  // containsAll_1234=false

  System.out.println(list.containsAll(c)); // false

  

  // -------------------------------------------------------

  // 查找某个数据所在的索引位置

  // 如果不存在,返回-1

  // -------------------------------------------------------

  System.out.println(list.indexOf(3)); // 1

  System.out.println(list.indexOf("xyz")); // 4

  System.out.println(list.indexOf("abcd")); // -1

  

  // -------------------------------------------------------

  // 测试删除数据

  // 请注意,

  // 如果你使用整数(int)数字,则默认调用的是remove(int index);

  // 如果你用 long,则会调用 remove(Object obj);

  // 所以如果你要删除整数,请使用 remove(new Integer(int));

  // -------------------------------------------------------

  // 删除索引为1的数据

  list.remove(1);

  // 索引为1的数据被干掉了

  showList(list); // [2.34, bcd, 2, xyz, 1, abc, 1.11]

  

  // 删除数字1 和字符串 abc

  list.remove(new Integer(1));

  list.remove("xyz");

  showList(list); // [2.34, bcd, 2, abc, 1.11]

  

  // -------------------------------------------------------

  // 迭代器的使用

  // -------------------------------------------------------

  Iterator it = list.iterator();

  while (it.hasNext()) {

   System.out.print(it.next() + " "); // 2.34 bcd 2 abc 1.11

  }

  System.out.println();

  

  // -------------------------------------------------------

  // 转化为数组

  // -------------------------------------------------------

  Object[] objs = list.toArray();

  for (Object obj : objs) {

   System.out.print(obj + " "); // 2.34 bcd 2 abc 1.11

  }

  System.out.println();

 }

  

 public static void testSpecial() {

  // -------------------------------------------------------

  // 测试重复和null

  // -------------------------------------------------------

  //

  List<Integer> list = new ArrayList<Integer>();

  list.add(123);

  list.add(456);

  list.add(123);

  list.add(456);

  // 数据允许重复

  showList(list); // [123, 456, 123, 456]

  

  list.add(null);

  list.add(789);

  list.add(null);

  list.add(999);

  // 允许放入多个null

  showList(list); // [123, 456, 123, 456, null, 789, null, 999]

  

  // -------------------------------------------------------

  // 测试一下查找最后一次出现的位置

  // -------------------------------------------------------

  System.out.println(list.indexOf(123)); // 0

  System.out.println(list.lastIndexOf(123)); // 2

  

  // -------------------------------------------------------

  // 转化为数组

  // 记得要转化为Inerger.

  // -------------------------------------------------------

  Integer[] nums = (Integer[]) list.toArray(new Integer[0]);

  // 注意数据里面有null,所以循环变量不要用int 要用Integer

  for (Integer num : nums) {

   System.out.print(num + " "); // 123 456 123 456 null 789 null 999

  }

  System.out.println();

  

 }

  

 public static void testForProblem() {

  // 一些朋友在向循环里向列表增加对象的时候

  // 经常忘记初始化,造成最终加入的都是同一个对象

  List<MyObject> list = new ArrayList<MyObject>();

  MyObject obj = new MyObject();

  for (int i = 1; i <= 5; i++) {

   obj.setName("Name" + i);

   list.add(obj);

  }

  // 里面的数据都是最后一个

  showList(list); // [Name5, Name5, Name5, Name5, Name5]

  

  // 正确的做法

  List<MyObject> list2 = new ArrayList<MyObject>();

  MyObject obj2 = null;

  for (int i = 1; i <= 5; i++) {

   obj2 = new MyObject();

   obj2.setName("Name" + i);

   list2.add(obj2);

  }

  // 里面的数据都是最后一个

  showList(list2); // [Name1, Name2, Name3, Name4, Name5]

 }

  

 /**

  * 显示List里面的数据。

  * 

  * @param list

  */

 private static void showList(List list) {

  System.out.println(Arrays.toString(list.toArray()));

 }

}

  

class MyObject {

 private String name;

  

 public String getName() {

  return name;

 }

  

 public void setName(String name) {

  this.name = name;

 }

  

 /**

  * 重写toString方法,输出name

  */

 public String toString() {

  return name;

 }

}

  输出结果

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

[1, abc, 1.11]

[2.34, bcd, 21, abc, 1.11]

[2.343, bcd, 2, xyz, 1, abc, 1.11]

2.34

xyz

true

true

false

1

4

-1

[2.34, bcd, 2, xyz, 1, abc, 1.11]

[2.34, bcd, 2, abc, 1.11]

2.34 bcd 2 abc 1.11

2.34 bcd 2 abc 1.11

[123456123456]

[123456123456null789null999]

0

2

123 456 123 456 null 789 null 999

[Name5, Name5, Name5, Name5, Name5]

[Name1, Name2, Name3, Name4, Name5]

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值