• Java容器学习之ArrayList


    一、概述

    ArrayList是java中十分常用的集合类,继承于AbstractList,并实现了List、RandomAccess、Cloneable和Serializable接口。ArrayList底层是使用数组来实现的,是一个动态的数组队列,它具有以下特点。

    • 可以动态扩容、缩容
    • ArrayList的操作不是线程安全的
    • 允许元素重复,允许元素为空

    ArrayList初始默认大小是10,每次扩容时是原大小的1.5倍,如果一开始就知道需要的Lsit长度,可以指定ArrayList的长度,减少扩容操作。

    二、源码分析(JDK 1.8)

     2.1 ArrayList属性

    // 默认的容量
    private static final int DEFAULT_CAPACITY = 10;
    // 保存ArrayList中数据使用的数组
    transient Object[] elementData
    // ArrayList的元素个数
    private int size;

    此外还有一个成员变量modCount,这个变量用来记录修改次数,增删改的都会改变modCount的值。ArrayList是线程不安全的,如果在使用迭代器的过程中有其他线程修改了List,那么将抛出ConcurrentModificationException,是Fail-Fast策略的应用。

    2.2 构造函数

     1  // 构造一个初始容量为10的空列表
     2 public ArrayList() {
     3         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
     4     }
     5 // 构造具有指定容量的空列表
     6 public ArrayList(int initialCapacity) {
     7         if (initialCapacity > 0) {
     8             this.elementData = new Object[initialCapacity];
     9         } else if (initialCapacity == 0) {
    10             this.elementData = EMPTY_ELEMENTDATA;
    11         } else {
    12             throw new IllegalArgumentException("Illegal Capacity: "+
    13                                                initialCapacity);
    14         }
    15     }
    16 
    17 // 构造一个包含指定容器元素的列表,按照容器迭代器返回的顺序排列
    18 public ArrayList(Collection<? extends E> c) {
    19         elementData = c.toArray();
    20         if ((size = elementData.length) != 0) {
    21             // c.toArray might (incorrectly) not return Object[] (see 6260652)
    22             if (elementData.getClass() != Object[].class)
    23                 elementData = Arrays.copyOf(elementData, size, Object[].class);
    24         } else {
    25             // replace with empty array.
    26             this.elementData = EMPTY_ELEMENTDATA;
    27         }
    28     }

    ArrayList有三个构造函数,如上代码所示。

    2.3  trimToSize()

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

      trimToSize()方法可以将ArrayList的底层数组缩容到当前ArrayList中元素的个数(ArrayList的size值),使用这个方法可以最小化ArrayList使用的存储空间。

    2.4 添加

     1       // 在列表尾部添加元素
     2      public boolean add(E e) {
     3         ensureCapacityInternal(size + 1);  // Increments modCount!!
     4         elementData[size++] = e;
     5         return true;
     6     }
     7     // 在指定位置添加指定元素
     8     public void add(int index, E element) {
     9         rangeCheckForAdd(index);
    10 
    11         ensureCapacityInternal(size + 1);  // Increments modCount!!
    12         System.arraycopy(elementData, index, elementData, index + 1,
    13                          size - index);
    14         elementData[index] = element;
    15         size++;
    16     }
    17     // 将集合c中元素添加到列表尾部
    18     public boolean addAll(Collection<? extends E> c) {
    19         Object[] a = c.toArray();
    20         int numNew = a.length;
    21         ensureCapacityInternal(size + numNew);  // Increments modCount
    22         System.arraycopy(a, 0, elementData, size, numNew);
    23         size += numNew;
    24         return numNew != 0;
    25     }
    View Code

      add方法中都用到了ensureCapacityInternal方法,这个方法用于对ArrayList进行扩容。

    2.5 扩容

     1     // 扩容操作
     2     private void ensureCapacityInternal(int minCapacity) {
     3         if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
     4             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
     5         }
     6 
     7         ensureExplicitCapacity(minCapacity);
     8     }
     9 
    10     private void ensureExplicitCapacity(int minCapacity) {
    11         modCount++;
    12 
    13         // overflow-conscious code
    14         if (minCapacity - elementData.length > 0)
    15             grow(minCapacity);
    16     }
    17     // 增加容量,使列表可以的容量至少满足minCapacity个元素
    18     private void grow(int minCapacity) {
    19         // overflow-conscious code
    20         int oldCapacity = elementData.length;
    21         int newCapacity = oldCapacity + (oldCapacity >> 1);
    22         if (newCapacity - minCapacity < 0)
    23             newCapacity = minCapacity;
    24         if (newCapacity - MAX_ARRAY_SIZE > 0)
    25             newCapacity = hugeCapacity(minCapacity);
    26         // minCapacity is usually close to size, so this is a win:
    27         elementData = Arrays.copyOf(elementData, newCapacity);
    28     }

     ensureExplicitCapacity()方法先判断当前数组的容量是否可以满足需求,如果不满足就使用grow进行扩容操作,每次扩容为原来的1.5倍。

    2.6 删除

     1     // 删除指定位置的元素,并返回被删除的元素
     2     public E remove(int index) {
     3         // 范围检查
     4         rangeCheck(index);
     5 
     6         modCount++;
     7         E oldValue = elementData(index);
     8         // 需要移动的元素数
     9         int numMoved = size - index - 1;
    10         if (numMoved > 0)
    11             // 等于0表示删除的是末尾的元素,不需要移动
    12             System.arraycopy(elementData, index+1, elementData, index,
    13                              numMoved);
    14         elementData[--size] = null; // clear to let GC do its work
    15 
    16         return oldValue;
    17     }    

    E remove(int index) 删除指定位置的元素

     

        // 范围检查
        private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    删除前进行边界检查

     

     1     // 删除列表中首次出现的指定元素,如果有多个只删除第一个
     2     public boolean remove(Object o) {
     3         if (o == null) {
     4             for (int index = 0; index < size; index++)
     5                 if (elementData[index] == null) {
     6                     fastRemove(index);
     7                     return true;
     8                 }
     9         } else {
    10             for (int index = 0; index < size; index++)
    11                 if (o.equals(elementData[index])) {
    12                     fastRemove(index);
    13                     return true;
    14                 }
    15         }
    16         return false;
    17     }
    18     // 私有的跳过边界检查快速删除指定位置元素的方法
    19     private void fastRemove(int index) {
    20         modCount++;
    21         int numMoved = size - index - 1;
    22         if (numMoved > 0)
    23             System.arraycopy(elementData, index+1, elementData, index,
    24                              numMoved);
    25         elementData[--size] = null; // clear to let GC do its work
    26     }

    public boolean remove(Object o) 删除首次出现的指定元素,若有多个只删除第一个

     

     1      // 删除指定范围的元素
     2     protected void removeRange(int fromIndex, int toIndex) {
     3         modCount++;
     4         int numMoved = size - toIndex;
     5         System.arraycopy(elementData, toIndex, elementData, fromIndex,
     6                          numMoved);
     7 
     8         // clear to let GC do its work
     9         int newSize = size - (toIndex-fromIndex);
    10         for (int i = newSize; i < size; i++) {
    11             elementData[i] = null;
    12         }
    13         size = newSize;
    14     }

    void removeRange(int fromIndex, int toIndex) 删除[fromIndex, toIndex)范围的元素

     1     // 从列表中删除指定集合c中的元素
     2     public boolean removeAll(Collection<?> c) {
     3         Objects.requireNonNull(c);
     4         return batchRemove(c, false);
     5     }
     6     // 列表中保留指定集合c中的元素
     7     public boolean retainAll(Collection<?> c) {
     8         Objects.requireNonNull(c);
     9         return batchRemove(c, true);
    10     }
    11     // 从List的底层数组中删除或者保留指定集合中元素的值,complement为true是保留指定集合元素,为false是删除指定集合元素
    12     private boolean batchRemove(Collection<?> c, boolean complement) {
    13         final Object[] elementData = this.elementData;
    14         int r = 0, w = 0;
    15         boolean modified = false;
    16         try {
    17  
    18             for (; r < size; r++)
    19                 if (c.contains(elementData[r]) == complement)
    20                     elementData[w++] = elementData[r];
    21         } finally {
    22             // Preserve behavioral compatibility with AbstractCollection,
    23             // even if c.contains() throws.
    24             if (r != size) {
    25                 System.arraycopy(elementData, r,
    26                                  elementData, w,
    27                                  size - r);
    28                 w += size - r;
    29             }
    30             // w==size表示全部元素保留,没有删除操作发生,否则返回true,并更改数组
    31             if (w != size) {
    32                 // clear to let GC do its work
    33                 for (int i = w; i < size; i++)
    34                     elementData[i] = null;
    35                 modCount += size - w;
    36                 size = w;    //新的大小为剩下元素的个数
    37                 modified = true;
    38             }
    39         }
    40         return modified;
    41     }    

    在try块中首先遍历数组,如果要保留集合c中的元素就把相同元素移到数组前段,如果要删除集合c中的数据就把不同元素移到数组前段,将剩下的数据保存在0到w之间,

    第一个if语句是为了处理c.contains()抛出异常时执行;第二个if语句是处理w之后的空间。

  • 相关阅读:
    常见面试测试要点
    怎样在 CentOS/RHEL 7/6 上安装和配置 Sendmail 服务器
    Cannot uninstall 'pyparsing'. It is a distutils installed project
    Linux下校验SHA1和MD5的方法
    Linux查看进程启动时间和运行多长时间
    sqlplus -S参数表示什么意思?
    dnspython模块报错 AttributeError: 'CNAME' object has no attribute 'address'
    CentOS7中安装pip的方法
    四则运算中遇到的一个问题
    动手动脑
  • 原文地址:https://www.cnblogs.com/skylv/p/10102786.html
Copyright © 2020-2023  润新知