Java集合类详解(1) -- ArrayList详解

什么是ArrayList

ArrayList的底层是基于一个数组实现的,实际上ArrayList就是一个动态数组。

ArrayList的一些特性

  • ArrayList并不是线性安全的,在多线程环境下不能使用。
  • 每个ArrayList实例都有一个容量属性,该容量属性是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。

ArrayList是如何实现的

ArrayList实现了List接口,其实其操作就是对于数组的操作。下面我们来通过源代码进行讲解。

一 属性

//使用对象数组存储ArrayList中的元素
private transient Object[] elementData;  
   
//对象数组中包含元素的数量
private int size;

transient关键字是啥意思

对象的属性加上transient关键字后,该对象被序列化时,此属性不会被保存。

二 构造方法

ArrayList提供了三种构造方法,分别可以构造以下三种列表

  • 指定容量的空表
  • 容量为10的空表
  • 包含指定collection元素的列表
// ArrayList带容量大小的构造函数。    
    public ArrayList(int initialCapacity) {    
        super();    
        if (initialCapacity < 0)    
            throw new IllegalArgumentException("Illegal Capacity: "+    
                                               initialCapacity);    
        // 新建一个数组    
        this.elementData = new Object[initialCapacity];    
    }    
   
// ArrayList无参构造函数。默认容量是10。    
    public ArrayList() {    
        this(10);    
    }    
   
// 创建一个包含collection的ArrayList    
    public ArrayList(Collection<? extends E> c) {    
        elementData = c.toArray();    
        size = elementData.length;    
        if (elementData.getClass() != Object[].class)    
            elementData = Arrays.copyOf(elementData, size, Object[].class);    
    }

三 增加元素

用一个新元素来替代旧元素,并返回旧元素

public E set(int index, E element) {  
   RangeCheck(index);  
 
   E oldValue = (E) elementData[index];  
   elementData[index] = element;  
   return oldValue;  
}

添加一个元素至列表尾部

public boolean add(E e) {  
   ensureCapacity(size + 1);   
   elementData[size++] = e;  
   return true;  
}

添加元素至列表的指定位置

public void add(int index, E element) {  
   if (index > size || index < 0)  
       throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);  
   // 如果数组长度不足,将进行扩容。  
   ensureCapacity(size+1);  
   // 将 elementData中从Index位置开始、长度为size-index的元素,  
   // 拷贝到从下标为index+1位置开始的新的elementData数组中。  
   // 即将当前位于该位置的元素以及所有后续元素右移一个位置。  
   System.arraycopy(elementData, index, elementData, index + 1, size - index);  
   elementData[index] = element;  
   size++;  
}

将Collection的所有元素添加至列表尾部

public boolean addAll(Collection<? extends E> c) {  
   Object[] a = c.toArray();  
   int numNew = a.length;  
   ensureCapacity(size + numNew);  // Increments modCount  
   System.arraycopy(a, 0, elementData, size, numNew);  
   size += numNew;  
   return numNew != 0;  
}

将Collection的所有元素添加至列表指定位置

public boolean addAll(int index, Collection<? extends E> c) {  
   if (index > size || index < 0)  
       throw new IndexOutOfBoundsException(  
           "Index: " + index + ", Size: " + size);  
 
   Object[] a = c.toArray();  
   int numNew = a.length;  
   ensureCapacity(size + numNew);   
 
   int numMoved = size - index;  
   if (numMoved > 0)  
       System.arraycopy(elementData, index, elementData, index + numNew, numMoved);  
 
   System.arraycopy(a, 0, elementData, index, numNew);  
   size += numNew;  
   return numNew != 0;  
}

我们知道ArrayList的底层是基于数组实现的,而数组是有长度的,当进行增删改查时,若数组下标没有越界自然一切好说。但,比如像上面的add方法,若增加元素时数组越界了怎么办?ArrayList自然不会允许这种情况发生,其中的精髓便在于ensureCapacity方法,它实现了数组的扩容,这里我们只需对它有一个初始概念即可,下面我们会对其进行源码分析。

查阅元素

返回指定位置的元素

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

删除元素

删除指定位置的元素

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) {  
    // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。  
    if (o == null) {  
        for (int index = 0; index < size; index++)  
            if (elementData[index] == null) {  
                // 类似remove(int index),移除列表中指定位置上的元素。  
                fastRemove(index);  
                return true;  
            }  
    } else {  
        for (int index = 0; index < size; index++)  
            if (o.equals(elementData[index])) {  
                fastRemove(index);  
                return true;  
            }  
        }  
        return false;  
    } 
}

E remove(int index)与void fastRemove(int index)的区别

void fastRemove(int index)与E remove(int index)相比,跳过了判断边界的处理。从上面的boolean remove(Object o)方法可以发现,当我们找到符合条件的对象,自然易得一个数组下标index,而该index是不可能越界的,故使用void fastRemove(int index)对对象进行删除。

下面是fastRemove的代码

private void fastRemove(int index) {  
     modCount++;  
     int numMoved = size - index - 1;  
     if (numMoved > 0)  
         System.arraycopy(elementData, index+1, elementData, index,  
                          numMoved);  
     elementData[--size] = null; 
 }

上面挺多方法的实现都用到了removeRange方法,这个方法的功能是将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。下面直接上源码。

protected void removeRange(int fromIndex, int toIndex) {  
     modCount++;  
     int numMoved = size - toIndex;  
         System.arraycopy(elementData, toIndex, elementData, fromIndex,  
                          numMoved);  
   
     int newSize = size - (toIndex-fromIndex);  
     while (size != newSize)  
         elementData[--size] = null;  
}

ArrayList的关键方法ensureCapacity

每次我们向ArrayList添加元素时,都会检查ArrayList是否会发生数组越界。如果发生数组越界,ArrayList会通过void ensureCapacity(int minCapacity)来进行扩容。

public void ensureCapacity(int minCapacity) {  
    modCount++;  
    int oldCapacity = elementData.length;  
    if (minCapacity > oldCapacity) {  
        Object oldData[] = elementData;  
        int newCapacity = (oldCapacity * 3)/2 + 1;  
            if (newCapacity < minCapacity)  
                newCapacity = minCapacity;  
      
      elementData = Arrays.copyOf(elementData, newCapacity);  
    }  
 }

我们可以发现,当数组进行扩容时,会将老数组的元素拷贝一份放在新数组中,并进行大约1.5倍的容量增长。其实这种代价是相当昂贵的,为此我们可以在ArrayList初始化时便指定一个相当大的容量或手动通过ensureCapacity方法来实现数组扩容。

ArrayList和Vector的区别之处

  • ArrayList在内存不够时默认是扩展50% + 1,Vector是默认扩展1倍
  • Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销
  • Vector提供indexOf(obj, start)接口,ArrayList没有

ArrayList的一些其他方法

public void trimToSize() {  
   modCount++;  
   int oldCapacity = elementData.length;  
   if (size < oldCapacity) {  
       elementData = Arrays.copyOf(elementData, size);  
   }  
}

该方法将底层数组的容量调整为当前列表保存的实际元素的大小,在某些特定情况下用于节约空间。

public Object[] toArray() {  
         return Arrays.copyOf(elementData, size);  
} 

该方法用于将ArrayList转化为静态数组,拷贝了elementData从0至size-1位置的元素到新数组并返回。

public <T> T[] toArray(T[] a) {
        if (a.length < size)
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    	System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
}

如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同。所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。

在ArrayList的实现中大量使用了Arrays.copyof()和System.arraycopy()方法,下面我们对其进行一定的讲解。

Arrays.copyof()

public static <T> T[] copyOf(T[] original, int newLength) {  
    return (T[]) copyOf(original, newLength, original.getClass());  
}

明显调用了另外一个方法,其中最后一个参数指明要转换的数据的类型。下面我闷分析其源码

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {  
    T[] copy = ((Object)newType == (Object)Object[].class)  
        ? (T[]) new Object[newLength]  
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);  
    System.arraycopy(original, 0, copy, 0,  
                     Math.min(original.length, newLength));  
    return copy;  
}

该方法实际上是在其内部又创建了一个长度为newlength的数组,调用System.arraycopy()方法,将原来数组中的元素复制到了新的数组中。

System.arraycopy()

该方法被标记了native,调用了系统的C/C++代码,在JDK中是看不到的,但在openJDK中可以看到其源码。该函数实际上最终调用了C语言的memmove()函数,因此它可以保证同一个数组内元素的正确复制和移动,比一般的复制方法的实现效率要高很多,很适合用来批量处理数组。Java强烈推荐在复制大量数组元素时用该方法,以取得更高的效率。

对ArrayList的总结

  1. 在确定ArrayList的元素数量之后使用ArrayList较好,否则建议使用LinkedList,因为若不确定元素数量可能会不断发生数组扩容,此操作非常耗时。
  2. ArrayList基于数组实现,可以通过下标索引直接查找元素,但进行增加或删除操作时需要大量移动元素,故查找效率高,增加或删除效率低。
  3. ArrayList中允许元素为null,故在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理。

参考:Java集合—ArrayList的实现原理

猜你喜欢

转载自blog.csdn.net/Geffin/article/details/89788535
今日推荐