ArrayList简介

ArrayList简介

ArrayList以数组为底层数据结构的集合,是一个动态的数组队列,就是说该类的容量可以增长,与一般的数组不同。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

可以看出Arraylist其继承AbstractList抽象类,而AbstractList也实现了 List 接口。

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>

实现的接口:

  • List:表示该集合可以存储重复的元素,具有增删改查的功能。

  • RandomAccess:表明给类有随机访问元素的功能(数组下标实现)。

  • Cloneable:表明可以在堆中可以克隆出与对象一样的对象,并且两个对象地址都不一样,即对克隆出来的对象修改影响不了被克隆对象。

API接口

// Collection中定义的API
boolean             add(E object)
boolean             addAll(Collection<? extends E> collection)
void                clear()
boolean             contains(Object object)
boolean             containsAll(Collection<?> collection)
boolean             equals(Object object)
int                 hashCode()
boolean             isEmpty()
Iterator<E>         iterator()
boolean             remove(Object object)
boolean             removeAll(Collection<?> collection)
boolean             retainAll(Collection<?> collection)
int                 size()
<T> T[]             toArray(T[] array)
Object[]            toArray()
// AbstractCollection中定义的API
void                add(int location, E object)
boolean             addAll(int location, Collection<? extends E> collection)
E                   get(int location)
int                 indexOf(Object object)
int                 lastIndexOf(Object object)
ListIterator<E>     listIterator(int location)
ListIterator<E>     listIterator()
E                   remove(int location)
E                   set(int location, E object)
List<E>             subList(int start, int end)
// ArrayList新增的API
Object               clone()
void                 ensureCapacity(int minimumCapacity)
void                 trimToSize()
void                 removeRange(int fromIndex, int toIndex)

ArrayList的属性

// 序列化id  
private static final long serialVersionUID =8683452581122892189L;

//默认容量
private static final int DEFAULT_CAPACITY = 10;

//当使用有参构造方法且参数为0时给elementData赋值空对象
private static final Object[] EMPTY_ELEMENTDATA = {};
//当使用无参构造方法时给elementData赋值空对象
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//底层数据结构,数据对象存储地方
transient Object[] elementData;

//数组长度
private int size;
//数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

EMPTY_ELEMENTDATA与DEFAULTCAPACITY_EMPTY_ELEMENTDATA区别 得结合构造方法及扩容函数才比较弄得清楚。

MAX_ARRAY_SIZE 为什么不是 Integer.MAX_VALUE 而是 Integer.MAX_VALUE-8 呢,原因是数组需要8个字节存储数组长度。

ArrayList的构造方法

ArrayList的构造方法有三种。

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

无参构造方法 :创建数组对象,默认为空的数组对象,长度为1,size=0,当一次添加元素时,回默认扩容到10。

int的有参构造方法 :根据用户自定义的容量初始化,当容量为0时,回给数组赋值EMPTY_ELEMENTDATA空数组。

Collection的有参构造方法 :先对Collection c进行toArray()转换为数组形式,若其数组长度不为0,还得对数组元素类型进行判断,不同就转换为Object。若数组长度为0就赋值EMPTY_ELEMENTDATA空数组。

ArrayList的add方法

add方法有 4种 (包括AbstractList里面的2种)

在ArrayList定义的2种及使用的相关函数:

//第一种(无索引的加入单个元素)
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
 //第二种(有索引加入单个元素)
    public void add(int index, E element) {
        //判断索引是否有效
        rangeCheckForAdd(index);
        
        ensureCapacityInternal(size + 1);  
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);//将索引后的index都往后移一位
        elementData[index] = element;//放入目标索引
        size++;
    }
    
 
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //判断容器内elementData是否第一次初始化,且使用无参构造函数初始化,若是,则返回初始容量10.
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    //判断是否需要扩容函数
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 超出容量需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //扩容函数
    private void grow(int minCapacity) {
      
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//右移一位,相当于十进制除以2,则扩容的大小为原来的1.5倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

通过上面的构造函数,我们可以就之前那个问题 DEFAULTCAPACITY_EMPTY_ELEMENTDATA、EMPTY_ELEMENTDATA的区别 了, 通过无参构造函数进行构建的容器,调用calculateCapacity方法,minCapacity会变成10,而有参构造函数,参数为0的情况,调用calculateCapacity方法,minCapacity则还是为1(0+1),所以在扩容的时候,前者扩容为10,而后者扩容为1。这就是两个属性的区别。

还有两种add方法来自 AbstractList抽象类

//第三种(有索引的加入多个元素)
public boolean addAll(int index, Collection<? extends E> c) {
  		//检测下标是否正常
        rangeCheckForAdd(index);
        int cSize = c.size();
        if (cSize==0)
            return false;
				//fast-fail机制检测
        checkForComodification();
        l.addAll(offset+index, c);
        this.modCount = l.modCount;
        size += cSize;
        return true;
    }
//第四种(无索引的加入多个元素)
public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
}

private void rangeCheckForAdd(int index) {
  if (index < 0 || index > size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private void checkForComodification() {
        if (this.modCount != l.modCount)
            throw new ConcurrentModificationException();
    }

其实这两种的add方法跟前面两种没什么区别,这里就不做累述了,主要就是这里会抛出两个异常 IndexOutOfBoundsException 索引不正确与 ConcurrentModificationException fail-fast失败这两个异常。

想了解 fail-fast 机制的: java中的fail-fast(快速失败)机制

ArrayList的remove方法

  • remove(int index)
  • remove(Object o)
  • removeRange(int fromIndex, int toIndex)
  • clear()
  • removeAll(Collection c)

remove(int index)代码:

public E remove(int index) {
  //检测索引是否正常
  rangeCheck(index);
  //fail-fast机制
  modCount++;
  //获取删除对象
  E oldValue = elementData(index);
	//获取删除对象后面的那个索引
  int numMoved = size - index - 1;
  if (numMoved > 0)
    //后面元素向前移动一位。
    System.arraycopy(elementData, index+1, elementData, index,
                     numMoved);
  elementData[--size] = null; // clear to let GC do its work

  return oldValue;
}

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

remove(Object o)代码:

//大概思路就是遍历整个数组,找到该元素就删除掉。值得注意就是要分null与非null
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;
}

/*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     *这个注释的意思就是边界检查并不返回删除的值。
     *这就是与第一个remove方法不同的地方。
     */
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; // clear to let GC do its work
}

removeRange(int fromIndex, int toIndex)代码:

protected void removeRange(int fromIndex, int toIndex) {
  modCount++;
  int numMoved = size - toIndex;
  System.arraycopy(elementData, toIndex, elementData, fromIndex,
                   numMoved);

  // clear to let GC do its work
  int newSize = size - (toIndex-fromIndex);
  for (int i = newSize; i < size; i++) {
    elementData[i] = null;
  }
  size = newSize;
}

删除部分元素,使用System.arraycopy覆盖就完成此功能。

clear()代码:

public void clear() {
  modCount++;

  // clear to let GC do its work
  for (int i = 0; i < size; i++)
    elementData[i] = null;

  size = 0;
}

removeAll(Collection c)代码:

//从列表中移除指定 collection 中包含的其所有元素(可选操作)。
public boolean removeAll(Collection<?> c) {
  Objects.requireNonNull(c);
  return batchRemove(c, false);
}

private boolean batchRemove(Collection<?> c, boolean complement) {
  final Object[] elementData = this.elementData;
  int r = 0, w = 0;
  boolean modified = false;
  try {
    for (; r < size; r++)
      if (c.contains(elementData[r]) == complement)
        //collection有的直接抛弃
        elementData[w++] = elementData[r];
  } finally {
    // Preserve behavioral compatibility with AbstractCollection,
    // even if c.contains() throws.
    if (r != size) {
      System.arraycopy(elementData, r,
                       elementData, w,
                       size - r);
      w += size - r;
    }
    //整理数组。
    if (w != size) {
      // clear to let GC do its work
      for (int i = w; i < size; i++)
        elementData[i] = null;
      modCount += size - w;
      size = w;
      modified = true;
    }
  }
  return modified;
}

ArrayList查找方法

public E get(int index) {
  //检查索引是否正常
  rangeCheck(index);
	
  return elementData(index);
}

private void rangeCheck(int index) {
  if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//根据数组根据索引获取值
E elementData(int index) {
  return (E) elementData[index];
}

ArrayList修改方法

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

基本跟上面没什么区别,就不太累述了。

ArrayList的trimToSize方法

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

该函数就是去掉数组没有使用的空间,就是(elementData.length在size索引 之后的空间。这里有一个就是特殊情况就是size。

ArrayList的toArray方法

ArrayList提供了一个将List转为数组的方法,该方法有两个重载,分别是

  • Object[] toArray()
  • toArray(T[] a)

**toArray() **代码:

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

toArray(T[] a)代码:

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;
}

第二个方法相对第一个方法可以要求数组类型,所以第一个方法容易抛出java.lang.ClassCastException错误。

例子:

ArrayList<String> list= new ArrayList<String>();
	for(inti = 0 ; i <  10 ; i++) {
  	list.add( "" +i);
}

String[] array= (String[]) list.toArray();
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
	at demo01.main(demo01.java:10)

ArrayList的indexOf与lastIndexOf方法

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;
  }
  //没找到会返回-1
  return -1;
}
public int lastIndexOf(Object o) {
  if (o == null) {
    //从末尾开始找起
    for (int i = size-1; i >= 0; i--)
      if (elementData[i]==null)
        return i;
  } else {
    for (int i = size-1; i >= 0; i--)
      if (o.equals(elementData[i]))
        return i;
  }
  //没找到会返回-1
  return -1;
}

最后:

ArrayList底层是有一个Object[]数组实现,具有实例化、随机访问访问、克隆的功能,其内部大量使用 System.arraycopyArrays.copyOf 函数来实现它的功能。经过通过分析代码,ArrayList并不是并发安全,虽然其内部有 fail-fast 机制来保证,也就是有出现并发操作导致数据问题我就抛出问题出来,所以该类在并发下并不能使用。

补充

System.arraycopy与Arrays.copyOf区别

System.arraycopy代码:

public static native void arraycopy(Object src,int srcPos, Object dest, int destPos,int length);
/**
src:原数组
srcPos:原数数组起始位置
dest:目标数组
destPos:目标数组起始位置
length:要复制数组元素个数

该方法使用native,表示该方法是使用其他底层函数写的,相对来说更加高效。
*/

Arrays.copyOf代码:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
  @SuppressWarnings("unchecked")
  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;
}

/**
original:要复制的数组
newLength:新数组的长度
newType:新数组的类型
*/

两个方法区别:

  • System.arraycopy是对目标数组来进行复制数据操作。

  • Arrays.copyOf是内部创建一个数组再进行复制操作,其内部也是需要调用System.arraycopy来进行操作。

深入学习System.arraycopy: 《System.arraycopy为什么快》

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章