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之后的空间。

原文地址:https://www.cnblogs.com/skylv/p/10102786.html