java ArrayList 源码浅析

学习的东西越多就会发现自己越无知,最近看各种大牛的博客之类,深觉自己的无知啊,瀑布汗...摆正心态,慢慢学习,希望勤能补拙了。

ArrayList算是Java集合框架中相对简单的一个了,学习数据结构的时候很多人也会选择去自己实现一个类似功能的数组的线性存储,其实ArrayList也是如此,只是其开发人员写的更加正规一些,下面就看下源码去看下他们的思路。

1. 定义

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

从其定义看出继承自AbstractList<E>,支持泛型,

实现了List接口,List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括null 在内的所有元素。除了实现
List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于Vector 类,除了此类是不同步的。)

RandomAccess是一个标记接口,没有任何内容,

Serializable表示其可以进行序列化,没有实现此接口的则不可以序列化,此接口也是一个标记接口,没有任何实现。

★补充:Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。

用途: a)当你想把的内存中的对象保存到一个文件中或者数据库中时候;

b)当你想用套接字在网络上传送对象的时候;

c)当你想通过RMI传输对象的时候;

2. 字段摘要

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

此处elementData定义为transient,这个关键词的解释见:http://blog.csdn.net/luotuomianyang/article/details/51915759

此外还从AbstractList中继承了一个modCount

protected transient int modCount

从结构上修改 此列表的次数。从结构上修改是指更改列表的大小,或者打乱列表,从而使正在进行的迭代产生错误的结果。

3. 构造函数

ArrayList()

构造一个初始容量为 10 的空列表。

ArrayList(Collection<?
extends E> c)

构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。

ArrayList(int initialCapacity)

构造一个具有指定初始容量的空列表。

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    /**
     * 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 a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
         *构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的
     *
     * @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;
        }
    }

4. 方法摘要

4.1 添加

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

添加新元素前判断容量是否足够,不够的话则进行扩容,扩容的方法如下:

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

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

这个方法首先计算出一个容量,大小为oldCapacity + (oldCapacity >> 1)。即elementData数组长度的1.5倍。再从minCapacity 和这个容量中取较大的值作为扩容后的新的数组的大小。

这时,会出现两种情况:

一. 新的容量小于数组的最大值MAX_ARRAY_SIZE ,即

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

最大的容量之所以设为Integer.MAX_VALUE - 8,在定义上方的注释中已经说了,大概是一些JVM实现时,会在数组的前面放一些额外的数据,再加上数组中的数据大小,有可能超过一次能申请的整块内存的大小上限,出现OutOfMemoryError。

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

首先判断指定位置index是否超出elementData的界限,之后调用ensureCapacity调整容量(若容量足够则不会拓展),调用System.arraycopy将elementData从index开始的size-index个元素复制到index+1至size+1的位置(即index开始的元素都向后移动一个位置),然后将index位置的值指向element。

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

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

可以看到添加的方法大同小异,判断索引是否超出界限,扩容,复制。

4.2 索引

 public boolean contains(Object o) {
    return indexOf(o) >= 0;
 }

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

 public int lastIndexOf(Object o) {
     if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
              return i;
      } else {
         for (int i = size-1; i >= 0; i--)
             if (o.equals(elementData[i]))
              return i;
      }
      return -1;
  }

通过遍历elementData数组来判断对象是否在list中,若存在,返回index,若不存在则返回-1。所以contains方法可以通过indexOf(Object)方法的返回值来判断对象是否被包含在list中。lastIndexOf和前面类似,只是for循环变成后续循环,即从最后一个元素开始检索。

4.3 get 和set

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    public E set(int index, E element) {
        rangeCheck(index);

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

其中rangeCheck是一个索引范围的检查

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

整体实现炒鸡简单有木有!

4.4 移除

移除此列表中指定位置上的元素。

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

移除此列表中首次出现的指定元素(如果存在)。

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

    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 方便垃圾回收
    }

移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素。

    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

移除列表中索引在
fromIndex
(包括)和 toIndex(不包括)之间的所有元素。向左移动所有后续元素(减小其索引)。此调用将列表缩短了
(toIndex - fromIndex)
个元素。(如果 toIndex==fromIndex,则此操作无效。)

4.5  trimToSize()

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

由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length很size相同,节省空间。

三目运算符:

x? y:z
x是一个boolean类型,若x为true,结果显示y,若x为false,则结果显示z.
时间: 2024-10-26 14:06:51

java ArrayList 源码浅析的相关文章

Java ArrayList源码剖析

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

Java - ArrayList源码分析

java提高篇(二一)-----ArrayList 一.ArrayList概述 ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小. 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小.默认初始容量为10.随着ArrayList中元素的增加,它的容量也会不断的自动增长.在每次添加新的元素时,Array

Java ArrayList源码分析(有助于理解数据结构)

arraylist源码分析 1.数组介绍 数组是数据结构中很基本的结构,很多编程语言都内置数组,类似于数据结构中的线性表 在java中当创建数组时会在内存中划分出一块连续的内存,然后当有数据进入的时候会将数据按顺序的存储在这块连续的内存中.当需要读取数组中的数据时,需要提供数组中的索引,然后数组根据索引将内 存中的数据取出来,返回给读取程序.在Java中并不是所有的数据都能存储到数组中,只有相同类型的数据才可以一起存储到数组中.    因为数组在存储数据时是按顺序存储的,存储数据的内存也是连续的

ArrayList源码浅析(jdk1.8)

ArrayList的实质就是动态数组.所以可以通过下标准确的找到目标元素,因此查找的效率高.但是添加或删除元素会涉及到大量元素的位置移动,所以效率低. 一.构造方法 ArrayList提供了3个构造方法 1.无参的,就是把表示集合的数组赋值为空. public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 2.带初始化大小的构造方法,就是new一个指定大小的数组赋值给表示集合的数组. public Ar

Java String 源码浅析

引言 从一段代码说起: public void stringTest(){     String a = "a"+"b"+1;     String b = "ab1";     System.out.println(a == b); } 大家猜一猜结果如何?如果你的结论是true.好吧,再来一段代码: public void stringTest(){     String a = new String("ab1");   

ArrayList源码浅析

这里只理解主要的常用方法: 1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable 3 { 4 private static final long serialVersionUID = 8683452581122892189L; 5 6 /** 7 * 默认的初始化数组容量为10 8 */

java arraylist源码记录

1. ArrayList 实现了RandomAccess接口, RandomAccess接口用于标记是否可以随机访问 2. 继承了AbstractList类, 因此获取了modcount , modcount用于实现快速失败机制, 如果list有修改, 那么modcount自增 3. ArrayList不支持并发, 是非线程安全的 4. 支持存放null元素 5. 扩容, 每次都是原来大小的1.5倍 public void ensureCapacity(int minCapacity) { //

jdk1.7 ArrayList源码浅析

参考:http://www.cnblogs.com/xrq730/p/4989451.html(借鉴的有点多,哈哈) 首先介绍ArrayList的特性: 1.允许元素为空.允许重复元素 2.有序,即插入顺序和访问顺序一致 3.非同步 ArrayList实现原理为动态数组 首先看构造方法: public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentExcept

JAVA HashMap源码浅析

引言 HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢? 一 Entry Entry是Map接口中的一个内部接口,它是实现键值对存储关键.在HashMap中,有Entry的实现类,叫做Entry.Entry类很简单,里面包含key,value,由外部引入的hash,还有指向下一个Entry对象的引用,和数据结构中学的链表中的note节点很类似. Entry类的属性和构造函数: final K key; V value; Entry<K,V> next; int hash