zoukankan      html  css  js  c++  java
  • java集合系列(3)ArrayList

    此文章转载https://www.jianshu.com/p/53cf2a3bf3da

    一、ArrayList的数据结构:
    ArrayList的数据结构如下:

     

    说明:通过查看源码可以知道ArrayList底层的数据结构是数组,数组元素的类型是Object类型,即可以存放所有类型的数据,所有对ArrayList类的实例的操作底层都是基于数组实现。

    二、ArrayList源码分析:
    1、ArrayList的继承关系:
    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    说明:ArrayList继承了AbstractList抽象父类,实现了List接口(规定List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)

    2、ArrayList类的属性:

    说明:类的属性中核心属性为elementData,类型是Object[],用于存放实际元素,并且标记为transient,意味着在序列化的时候,此此段不会被序列化。

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        // 版本号
        private static final long serialVersionUID = 8683452581122892189L;
        // 缺省容量
        private static final int DEFAULT_CAPACITY = 10;
        // 空对象数组
        private static final Object[] EMPTY_ELEMENTDATA = {};
        // 缺省空对象数组
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        // 元素数组
        transient Object[] elementData;
        // 实际元素大小,默认为0
        private int size;
        // 最大数组容量
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    }

    3、ArrayList类构造函数:

    //指定elementData数组的大小,不允许初始化大小<0,否则抛出异常
    public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) { // 初始容量大于0
                this.elementData = new Object[initialCapacity]; // 初始化元素数组
            } else if (initialCapacity == 0) { // 初始容量为0
                this.elementData = EMPTY_ELEMENTDATA; // 为空对象数组
            } else { // 初始容量小于0,抛出异常
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
    //ArrayList()构造函数:
    //当为指定初始大小时,会给elementData赋值为空集合
    public ArrayList() { 
            // 无参构造函数,设置元素数组为空 
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
    //ArrayList(Collection<?extends E>)构造函数
    //当传递的参数为集合类型时,会把集合类型转化为数组类型,并赋值给elementData
    public ArrayList(Collection<? extends E> c) { // 集合参数构造函数
            elementData = c.toArray(); // 转化为数组
            if ((size = elementData.length) != 0) { // 参数为非空集合
                if (elementData.getClass() != Object[].class) // 是否成功转化为Object类型数组
                    elementData = Arrays.copyOf(elementData, size, Object[].class); // 不为Object数组的话就进行复制
            } else { // 集合大小为空,则设置元素数组为空
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }

    4、核心方法分析:
    4.1、add()方法:

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

    可以看到,add方法里面调用了ensureCapacityInternal()方法,该方法可以理解为为确保elementData数组有合适的大小,其具体实现如下:

    private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//判断该数组是否是空数组
                return Math.max(DEFAULT_CAPACITY, minCapacity);//取较大值
            }
            return minCapacity;
        }

    在ensureCapacityInternal()方法中,调用了 ensureExplicitCapacity()方法,该方法也是为了确保数组有合适的大小,具体实现如下:

    private void ensureExplicitCapacity(int minCapacity) {
            // 结构性修改加1
            modCount++;
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }

    而ensureExplicitCapacity()方法中,又调用了grow()方法,具体实现如下:

    private void grow(int minCapacity) {
            int oldCapacity = elementData.length; // 旧容量
            int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍,此处用了位运算提高效率
            if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
                newCapacity = hugeCapacity(minCapacity); // 指定新容量
            // 拷贝扩容
            elementData = Arrays.copyOf(elementData, newCapacity);
        }

    总结:通过以上方法跟踪,我们可以发现add()方法最终调用了grow()方法,其他均为过程方法,grow()方法里面定义了数组元素的存储以及扩容方式。

    4.2、set()方法:需要设置下标

     public E set(int index, E element) {
            // 检验索引是否合法
            rangeCheck(index);
            // 旧值
            E oldValue = elementData(index);
            // 赋新值
            elementData[index] = element;
            // 返回旧值
            return oldValue;
        }

    4.3、indexOf()方法:

    // 从首开始查找数组里面是否存在指定元素
        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;
        }

    说明:从头开始查找与指定元素相等的元素,可以查找null元素,即ArrayList中可以存null元素,与该方法对应的lastIndexOf()方法,表示从尾部元素开始查找。

    4.4、get()方法:

    E elementData(int index) {
            return (E) elementData[index];
        }

    说明:get方法会坚持索引是否合法(只检查是否大于size,而没有检查是否小雨0),非法则抛出数组越界异常,值得注意的是,在get方法中通过调用elementData方法返回具体元素,

    elementData方法实现如下:

    E elementData(int index) {
            return (E) elementData[index];
        }

    注意:返回的值都经过了向下转型(Object->E),这是应用程序设计中屏蔽的小细节

    4.5、remove()方法:

    public E remove(int index) {
            // 检查索引是否合法
            rangeCheck(index);
            
            modCount++;
            E oldValue = elementData(index);
            // 需要移动的元素的个数=数组总长度-(该元素所在的位置索引+1)
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            // 赋值为空,有利于进行GC
            elementData[--size] = null; 
            // 返回旧值
            return oldValue;
        }

    说明:remove方法用于移除指定下标的元素,此时会把指定下标到数组末尾的元素向前移动一个单位,并且会把数组最后一个元素设置为null,这样做是为了方便之后整个数组不使用时,会被GC,这是个小技巧。

    三、总结:
    ArrayList和LinkedList的区别:
    1、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
    2、对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
    3、对于新增和删除操作add和remove(不是在尾部添加删除),LinkedList比较占优势,因为ArrayList要移动数据。

    ArrayList和Vector的区别:
    1、Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。
    2、 Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。
    3、Vector还有一个子类Stack.

    总结

    ArrayList总体来说比较简单,不过ArrayList还有以下一些特点:

    • ArrayList基于数组方式实现,无容量的限制(会扩容)

    • 添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间

    • 线程不安全,会出现fall-fail。下一篇文章会详细讲,

    • add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位

    • get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))

    • remove(Object o)需要遍历数组

    • 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;
          }
    • remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高

    • contains(E)需要遍历数组

    •  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;
          }
    • 使用iterator遍历可能会引发多线程异常

    谢谢大家,如有问题还请批评指正。

  • 相关阅读:
    安卓学习第一课——电话拨号器
    CodeForces 644B【模拟】
    hdu5861【线段树】
    CodeForces 41A+43A【课上无聊刷水题系列】
    hdoj5493【树状数组+二分】
    HDU5894【组合数学】
    Codeforces643A【一种暴力】
    CodeForces 689C【二分】
    CodeForces 665B 【水-暴力】
    CodeForces 653A【水】
  • 原文地址:https://www.cnblogs.com/lusaisai/p/12386503.html
Copyright © 2011-2022 走看看