• Java集合框架之一:ArrayList源码分析


    ArrayList底层维护的是一个动态数组,每个ArrayList实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。

          ArrayList不是同步的(也就是说不是线程安全的),如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步,在多线程环境下,可以使用Collections.synchronizedList方法声明一个线程安全的ArrayList,例如:

          List arraylist = Collections.synchronizedList(new ArrayList());

          下面通过ArrayList的源码来分析其原理。

          1、ArrayList的构造方法:ArrayList提供了三种不同的构造方法

          1) ArrayList(),构造一个初始容量为 10 的空列表。

          2) ArrayList(int initialCapacity),构造一个具有指定初始容量的空列表。

          3) ArrayList(Collection<? extends E> c),构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。

          源码如下:

      

    1
    private transient Object[] elementData;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public ArrayList(int initialCapacity) {
       super();
       if (initialCapacity < 0)
           throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
       this.elementData = new Object[initialCapacity]; //生成一个长度为10的Object类型的数组
      }
      
      public ArrayList() {
       this(10); //调用ArrayList(int i)
      }<br><br>
      public ArrayList(Collection<? extends E> c) {
           elementData = c.toArray();   //返回包含此 collection 中所有元素的数组
       size = elementData.length;
       // c.toArray might (incorrectly) not return Object[] (see 6260652)
       if (elementData.getClass() != Object[].class)
          elementData = Arrays.copyOf(elementData, size, Object[].class); //复制指定的数组,返回包含相同元素和长度的Object类型的数组
      }

      当采用不带参数的构造方法ArrayList()生成一个集合对象时,其实是在底层调用ArrayList(int initialCapacity)这一构造方法生产一个长度为10的Object类型的数组。当采用带有集合类型参数的构造方法时,在底层生成一个包含相同的元素和长度的Object类型的数组。

         2、add方法:ArrayList提供了两种添加元素的add方法

         1) add(E e),将指定的元素添加到此列表的尾部。

         2) add(int index, E e),将指定的元素插入此列表中的指定位置。向右移动当前位于该位置的元素(如果有)以及所有后续元素(将其索引加 1)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public boolean add(E e) {
        ensureCapacity(size + 1);  // 扩大数组容量
        elementData[size++] = e;   //将元素e添加到下标为size的Object数组中,并且执行size++
        return true;
        }<br>
    public void add(int index, E element) {
        if (index > size || index < 0//如果指定要插入的数组下标超过数组容量或者指定的下标小于0,抛异常
            throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
     
        ensureCapacity(size+1);  // 扩大数组容量
        System.arraycopy(elementData, index, elementData, index + 1,size - index);  //从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。<br>                                                                                    // elementData --- 源数组   index --- 源数组中的起始位置   <br>                                                                                    // elementData --- 目标数组  index+1 ---  目标数组中的起始位置<br>                                                                                    // size - index --- 要复制的数组元素的数量
        elementData[index] = element; //将要添加的元素放到指定的数组下标处
        size++;
        }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void ensureCapacity(int minCapacity) {
       modCount++;
       int oldCapacity = elementData.length;  //原数组的容量
       if (minCapacity > oldCapacity) {
           Object oldData[] = elementData;
           int newCapacity = (oldCapacity * 3)/2 + 1//定义新数组的容量,为原数组容量的1.5倍+1
               if (newCapacity < minCapacity)
           newCapacity = minCapacity;
               // minCapacity is usually close to size, so this is a win:
               elementData = Arrays.copyOf(elementData, newCapacity);  //复制指定的数组,返回新数组的容量为newCapacity
       }
       }

      如果集合中添加的元素超过了10个,那么ArrayList底层会新生成一个数组,长度为原数组的1.5倍+1,并将原数组中的元素copy到新数组中,并且后续添加的元素都会放在新数组中,当新数组的长度无法容纳新添加的元素时,重复该过程。这就是集合添加元素的实现原理。

         3、get方法:

         1) get(int index),返回此列表中指定位置上的元素。

         

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public E get(int index) {
        RangeCheck(index); //检查传入的指定下标是否合法
     
        return (E) elementData[index];  //返回数组下标为index的数组元素
        }
     
    private void RangeCheck(int index) {
        if (index >= size)  //如果传入的下标大于或等于集合的容量,抛异常
            throw new IndexOutOfBoundsException(
            "Index: "+index+", Size: "+size);
        }

      

         4、remove方法:

         1) E remove(int index),移除此列表中指定位置上的元素。向左移动所有后续元素(将其索引减 1)。

         2) boolean remove(Object o),移除此列表中首次出现的指定元素(如果存在)。如果列表不包含此元素,则列表不做改动,返回boolean值。

         

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public E remove(int index) {
        RangeCheck(index);  //检查指定的下标是否合法
     
        modCount++;
        E oldValue = (E) elementData[index];  //获取指定下标的数组元素
     
        int numMoved = size - index - 1//要移动的元素个数
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index, numMoved);  //移动数组元素
        elementData[--size] = null; // Let gc do its work
     
        return oldValue;
        }
     
     public boolean remove(Object o) {
        if (o == null) { //如果传入的参数为null
                for (int index = 0; index < size; index++)
            if (elementData[index] == null) {  //移除首次出现的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) {  //移除指定位置的元素,实现方法类似remove(int i)
            modCount++;
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // Let gc do its work
        }

     

        5、clone方法:

        1) Object clone(),返回此ArrayList实例的浅表副本(不复制这些元素本身) 。

        

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public Object clone() {
       try {
           ArrayList<E> v = (ArrayList<E>) super.clone();  //调用Object类的clone方法返回一个ArrayList对象
           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();
       }
       }

      

        以上通过对ArrayList部分关键源码的分析,知道了ArrayList底层的实现原理,关于ArrayList源码有以下几点几点总结:

        1) ArrayList 底层是基于数组来实现的,可以通过下标准确的找到目标元素,因此查找的效率高;但是添加或删除元素会涉及到大量元素的位置移动,效率低。

        2) ArrayList提供了三种不同的构造方法,无参数的构造方法默认在底层生成一个长度为10的Object类型的数组,当集合中添加的元素个数大于10,数组会自动进行扩容,即生成一个新的数组,并将原数组的元素放到新数组中。

        3) ensureCapacity方法对数组进行扩容,它会生成一个新数组,长度是原数组的1.5倍+1,随着向ArrayList中不断添加元素,当数组长度无法满足需要时,重复该过程。

  • 相关阅读:
    死锁篇
    java线程池
    sql server 多行数据指定字符串拼接
    动态提交 表格
    ABP
    DDD学习
    sql 语句插入数据返回id
    Post方式提交,通过上下文HttpContext,Request[""]获取
    JQ的过滤隐藏
    sql 查询有多少行
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/6477561.html
Copyright © 2020-2023  润新知