码迷,mamicode.com
首页 > 其他好文 > 详细

ArrayList源码

时间:2020-05-22 16:56:55      阅读:58      评论:0      收藏:0      [点我收藏+]

标签:sequence   rop   uri   city   ons   成功   object   null   提高   

继承层级关系

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

RandomAccess

/**
* List实现使用的标记接口,标识它们支持快速(通常为恒定时间)随机访问。该接口的主要目的是允许通过算法更改其
* 以便在应用于随机访问列表或顺序访问列表是提供良好的性能
**/
public interface RandomAccess {
}

以下内容参考自 JavaGuide-randomaccess接口

  • 实际上RandowAccess接口中什么都没有定义。所以,在我看来RandomAccess接口不过是一个标识。标识什么?标识实现这个接口的类具有随机访问功能。
  • binarySearch()方法中,它要判断传入的list是否RandomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法。
    public static <T>
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }
  • ArrayList实现了RandomAccess接口,而LinkedList没有实现。ArrayList底层是数组,而LinkedList底层是链表。数组天然支持随机访问,时间复杂度O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为O(n),所以不支持快速随机访问。RandomAccess接口只是标识,并不是说ArrayList实现了RandomAccess接口才具有快速随机访问功能,而是因为它本身支持快速随机访问才实现了该接口。

Cloneable


Serializable

  • 序列化接口

    • 互联网的产生带来了机器间通讯的需求,而互联网通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。
    • 序列化:将数据结构或对象转换成二进制串的过程
    • 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
  • 可以参考我的另一篇文章 Java中的序列化和反序列

  • 还有一篇美团技术团队的文章,更加详细介绍了序列化和反序列化,侧重服务框架层面。序列化和反序列化

成员变量

    /**
     * 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.
     *
     * 实际存储元素的对象数组
     * 当第一个元素被添加时任何一个空的(DEFAULTCAPACITY_EMPTY_ELEMENTDATA=0)ArrayList
     * 将被扩容为10(DEFAULT_CAPACITY)
     */
    transient Object[] elementData; // non-private to simplify nested class access

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

sizeelementData.length

  • 这里需要注意sizeelement.legth是两个不同的概念
    • sizeArrayList的元素数量
    • elementData.lengthArrayList内置数组的长度
    • 详细的不同在trimToSize()方法额介绍中

transient关键字

  • 可以看到elementDatatransient关键字修饰,要理解这个行为,首先要理解transient关键字

定义

以下内容参考自 Java修饰符 transient,volatile等

  • 为了使对象可以在互联网之间传输,Java中提供了Serilizable接口。我们可以不必关心具体序列化的过程,只要这个类实现了Serializable接口,这个类就可以序列化。
    • Java中的Serializable接口
  • 然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其它属性不需要序列化,例如,如果用户的一些敏感信息(如密码,银行卡等),为了安全起见,不希望在网络操作中被传输,这些信息对应的成员变量就可以加上transient关键字。
transient使用小结
  1. 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
  2. transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
  3. 被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
ArrayList中的transient

以下内容参考自 图解集合1:ArrayList

private transient Object[] elementData;

  • 为什么elementData是使用transient修饰的呢?

    • ArrayList实现了Serializable,这意味着ArrayList是可以被序列化的,用transient修饰elementData意味着不希望elementData数组被序列化。这是为什么?
    • 我认为主要还是上面说的sizeelement.length并不是一回事,以空参构造形式(List<Integer> list=new ArrayList())来说,当list.add(1)执行之后,elementData.length=10size=1
    • 那么是否有必要序列化整个elementData呢?显然没有这个必要,因此ArrayList重写了writeObject()方法:
        private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException{
            // Write out element count, and any hidden stuff
            int expectedModCount = modCount;
            s.defaultWriteObject();
    
            // Write out size as capacity for behavioural compatibility with clone()
            s.writeInt(size);
    
            // Write out all elements in the proper order.
            for (int i=0; i<size; i++) {
                s.writeObject(elementData[i]);
            }
    
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }
    
    • 每次序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然后遍历elementData,只序列化size=element.length部分的元素
      1. 加快了序列化速度
      2. 减小了序列化之后的文件大小

成员函数

构造器

ArrayList()

  /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • 将实际存储元素的数组引用指向一个空数组

ArrayList(int initialCapacity)

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

ArrayList(Collection<? extends E> c)

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

扩容核心方法

add(E e)

    /**
     * Appends the specified element to the end of this list.
     * 在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;
    }
  • ensureCapacityInternal(int minCapacity):
    • 确保容量:以size+1作为minCapacity(注意这个minCapacity,它代表本次扩容所需最小容量,是个很重要的参数)
  • elementData[size++] = e
    • 将要add元素指向它在数组中对应位置,同时将size累加

ensureCapacityInternal(int minCapacity)

    /**
     * 确保容量的内部方法,命名上为了区别于对外提供的确保容量方法
     *
     * @param minCapacity
     */
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
  • 传递minCapacity

calculateCapacity()

    /**
     * 计算容量
     *
     * @param elementData
     * @param minCapacity
     * @return
     */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 若list为空
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
  • 逻辑比较简单:
    • 若list维护的数组当前为空时,最小容量置为10;
    • 否则返回传入的minCapacity

ensureExplicitCapacity(int minCapacity)

    /**
     * 确保准确的容量
     *
     * @param minCapacity
     */
    private void ensureExplicitCapacity(int minCapacity) {
        // AbstractList中维护的
        modCount++;

        // overflow-conscious code 可能溢出的代码
        if (minCapacity - elementData.length > 0)
            // 所需最小容量大于当前数组长度,开始扩容
            grow(minCapacity);
    }
  • modCount

    • 它被定义在AbstractList
     /**
         * The number of times this list has been <i>structurally modified</i>.
         * Structural modifications are those that change the size of the
         * list, or otherwise perturb it in such a fashion that iterations in
         * progress may yield incorrect results.
         
         * 该列表被结构修改的次数。结构修改是指更改列表大小或以其它方式干扰列表的方式,即正在进行的
         * 迭代可能会产生错误的结果。
         *
         * <p>This field is used by the iterator and list iterator implementation
         * returned by the {@code iterator} and {@code listIterator} methods.
         * If the value of this field changes unexpectedly, the iterator (or list
         * iterator) will throw a {@code ConcurrentModificationException} in
         * response to the {@code next}, {@code remove}, {@code previous},
         * {@code set} or {@code add} operations.  This provides
         * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
         * the face of concurrent modification during iteration.
         *
         * <p><b>Use of this field by subclasses is optional.</b> If a subclass
         * wishes to provide fail-fast iterators (and list iterators), then it
         * merely has to increment this field in its {@code add(int, E)} and
         * {@code remove(int)} methods (and any other methods that it overrides
         * that result in structural modifications to the list).  A single call to
         * {@code add(int, E)} or {@code remove(int)} must add no more than
         * one to this field, or the iterators (and list iterators) will throw
         * bogus {@code ConcurrentModificationExceptions}.  If an implementation
         * does not wish to provide fail-fast iterators, this field may be
         * ignored.
         */
        protected transient int modCount = 0;
    

grow(int minCapacity)

  • 容量不足,进入扩容核心逻辑
    private void grow(int minCapacity) {
        // overflow-conscious code 可能溢出的代码
        int oldCapacity = elementData.length;
        // 扩容核心操作,也就是常说的每次1.5倍扩容
        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)
    • 位运算(//TODO)

hugeCapacity(int minCapacity)

    private static int hugeCapacity(int minCapacity) {
        // 整型越界,OOM
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // MAX_ARRAY_SIZE= 2147483639
        	// 若最小所需容量>有效数组上限,直接返回Integer.MAX_VALUE。有的jvm可以成功,有的就会失					// 败,但下次扩容一定OOM
        	// 若最小所需容量<有效数组上限,返回有效数组上限值。也就是说将1.5扩容后的新容量值收缩到
        		// 有限数组上限值大小。()
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }
  • OOM,整型越界

  • 关于这里的hugeCapacity(int minCapacity)

    • 以下内容参考自 stackoverflow回答

      • 最大数组大小限制为一定数量,该大小因不同的JVM而异,通常小于Integer.MAX_VALUE。 因此,即使您有足够的内存来分配Integer.MAX_VALUE元素数组,您在大多数JVM上也会出现OutOfMemoryError。 MAX_ARRAY_SIZE假定在大多数现有JVM上都是有效的数组大小。
    • 该函数的核心作用只有一个,限制上限容量

      1. 当最小所需容量大于数组容量限制时,那么新扩容容量必然大于Integer.Max,为避免OOM,将容量设置Integer.Max,有的虚拟机可以成功,有的也会产生OOM。
      2. 当最小所需容量小于数组容量限制时,但新扩容容量大于数组容量限制,就将新扩容容量压缩成数组容量限制大小。

其它主要方法

trimToSize()

    /**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list‘s current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     *
     * 将此ArrayList实例的容量调整为列表的当前大小。应用程序可以使用此操作来最小化
     * ArrayList实例的存储。
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
                    ? EMPTY_ELEMENTDATA
                    : Arrays.copyOf(elementData, size);
        }
    }
  • 这里其实有两个问题要明确:即sizeelementData.length两个概念引出的问题

    • 它们有什么区别?

      以下内容参考自关于java ArrayList.trimToSize()方法的疑问

      • size是ArrayList中的元素数量
      • elementData.lengthArrayList内置数组的长度
        • 我们可以看下这两个变量分别在哪里维护的:
          • size:在add(E e)时,elementData[size++] = e;
          • elementData:在扩容方法grow(int minCapacity)elementData = Arrays.copyOf(elementData, newCapacity);
    • 它们为什么会不相等?

      • 产生的原因比较多,比如以空参构建一个list并add一个元素时,扩容为10,则size=1,而elementData.length=10
      • 其实核心原因是因为ArrayList的扩容机制:每次扩容都是在minCapacity的基础上给较大容量,这样可以减少扩容次数。而trimToSize()方法相当于对扩容后遗症的一种补偿
    • 外部什么时候需要使用这个函数?

      • 这是我在网上找的应用场景:
        • 最近项目中就类似场景:系统启动,初始化一些数据到 ArrayList 中缓存起来,这些数据比较多(几千个元素)但是根据业务场景是不会变的,这种情况可以使用该方法收缩一下

ensureCapacity(int minCapacity)

    /**
     * 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.
     *
     * 如有必要,增加此ArrayList实例的容量,以确保它至少可以容纳最小容量参数
     * 指定的元素数
     *
     * @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);
        }
    }
  • ArrayList的扩容机制提高了性能,如果每次只扩充一个,那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
  • 其实就是在节省空间和避免频繁拷贝之间做了一个取舍。

size()

    /**
     * Returns the number of elements in this list.
     *
     * 返回list中动态数组的元素数
     *
     * @return the number of elements in this list
     */
    public int size() {
        return size;
    }

isEmpty()

    /**
     * Returns <tt>true</tt> if this list contains no elements.
     *
     * 判断list是否为空
     *
     * @return <tt>true</tt> if this list contains no elements
     */
    public boolean isEmpty() {
        return size == 0;
    }

contains(Object o)

    /**
     * Returns <tt>true</tt> if this list contains the specified element.
     * More formally, returns <tt>true</tt> if and only if this list contains
     * at least one element <tt>e</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
     *
     * 判断集合是否包含元素o
     *
     * @param o element whose presence in this list is to be tested
     * @return <tt>true</tt> if this list contains the specified element
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
  • 核心功能主要在indexOf()

indexOf(Object o)

    /**
     * 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&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;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;
    }
  • 返回从第0个元素开始的第一个指定元素,传入null也一样

        @Test
        public void testIndexOf() {
            List<String> list = new ArrayList<>();
            list.add("卡卡西");
            list.add(null);
            System.out.println("null index is:" + list.indexOf(null));
        }
    
    null index is:1
    

lastIndexOf(Object o)

    /**
     * Returns the index of the last occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the highest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    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;
    }
  • indexOf(Object o)方法的倒序

clone()

    /**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     * 
     * 返回此ArrayList的浅表副本(元素本身不会被复制)(浅复制)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    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);
        }
    }
  • ArrayList<?> v = (ArrayList<?>) super.clone();
    • 执行该行代码拷贝完成后,两个list其实共用一个数组,然后下一行代码又拷贝了一个新的数组给新的list

toArray()

    /**
     * Returns an array containing all of the elements in this list
     * in proper sequence (from first to last element).
     *
     * 以正确顺组返回一个包含此列表中所有元素的数组(从第一个元素到最后一个元素)
     *
     * <p>The returned array will be "safe" in that no references to it are
     * maintained by this list.  (In other words, this method must allocate
     * a new array).  The caller is thus free to modify the returned array.
     *
     * 返回的数组将是“安全的”,因为没有对其的引用由该列表维护。(换句话说,此方法必须分配一个新数组。)
     * 因此,调用者可以自由修改返回的数组。
     *
     * <p>This method acts as bridge between array-based and collection-based
     * APIs.
     * 
     * 此方法充当基于数组的API和基于集合的API之间的桥梁
     *
     * @return an array containing all of the elements in this list in
     *         proper sequence
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
  • 浅拷贝

toArray(T[] a)

    /**
     * Returns an array containing all of the elements in this list in proper
     * sequence (from first to last element); the runtime type of the returned
     * array is that of the specified array.  If the list fits in the
     * specified array, it is returned therein.  Otherwise, a new array is
     * allocated with the runtime type of the specified array and the size of
     * this list.
     *
     * 返回一个数组,该数组按正确的顺序包含此列表中的所有元素(从第一个元素到最后一个元素)
     * 返回数组的运行时类型是按照指定数组的运行时类型。如果列表合适指定的数组,则将其返回。
     * 否则,将使用指定数组的运行时类型和此列表的大小分配一个新数组。
     *
     * <p>If the list fits in the specified array with room to spare
     * (i.e., the array has more elements than the list), the element in
     * the array immediately following the end of the collection is set to
     * <tt>null</tt>.  (This is useful in determining the length of the
     * list <i>only</i> if the caller knows that the list does not contain
     * any null elements.)
     *
     * @param a the array into which the elements of the list are to
     *          be stored, if it is big enough; otherwise, a new array of the
     *          same runtime type is allocated for this purpose.
     * @return an array containing the elements of the list
     * @throws ArrayStoreException if the runtime type of the specified array
     *         is not a supertype of the runtime type of every element in
     *         this list
     * @throws NullPointerException if the specified array is null
     */
    @SuppressWarnings("unchecked")
    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;
    }
  • ArrayList提供了一个将List转为数组的方法。toArray()有两个重载的方法:

    • list.toArray();

    • list.toArray(T[] a);

    • 第一个重载方法,是将list直接转换成Object[]数组;第二种重载方法是将list转换为你所需要的类型的数组。

      • 第二种方法用法如下:
      String[] array =new String[list.size()];
      list.toArray(array);
      
  • 详情可参考该链接 集合转数组的toArray()toArray(T[] a)方法

ArrayList源码

标签:sequence   rop   uri   city   ons   成功   object   null   提高   

原文地址:https://www.cnblogs.com/riders/p/12938033.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!