ArrayList 源码解析

ArrayList是我们使用得最多的一个集合类之一

一般用来做包装DTOview层来显示数据.

 支持随机访问)

1.存储
ArrayList使用一个Object的数组存储元素。
private transient Object elementData[];
ArrayList实现了java.io.Serializable接口,这儿的transient标示这个属性不需要自动序列化。下面会在writeObject()方法中详细讲解为什么要这样作。

2.add和remove

public boolean add(Object o) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = o;
return true;
}

注意这儿的ensureCapacity()方法,它的作用是保证elementData数组的长度可以容纳一个新元素。在“自动变长机制”中将详细讲解。
public Object remove(int index) {
RangeCheck(index);
modCount++;
Object oldValue = 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;
}

RangeCheck()的作用是进行边界检查。由于ArrayList采用一个对象数组存储元素,所以在删除一个元素时需要把后面的元素前移。删除一个元素时只是把该元素在elementData数组中的引用置为null,具体的对象的销毁由垃圾收集器负责。
modCount的作用将在下面的“iterator()中的同步”中说明。
注:在前移时使用了System提供的一个实用方法:arraycopy(),在本例中可以看出System.arraycopy()方法可以对同一个数组进行操作,这个方法是一个native方法,如果对同一个数组进行操作时,会首先把从源部分拷贝到一个临时数组,在把临时数组的元素拷贝到目标位置。

3.自动变长机制
在实例化一个ArrayList时,你可以指定一个初始容量。这个容量就是elementData数组的初始长度。如果你使用:
ArrayList list = new ArrayList();

则使用缺省的容量:10。
public ArrayList() {
this(10);
}

ArrayList提供了四种add()方法,

public boolean add(Object o)

public void add(int index, Object element)

public boolean addAll(Collection c)

public boolean addAll(int index, Collection c)

在每一种add()方法中,都首先调用了一个ensureCapacity(int miniCapacity)方法,这个方法保证elementData数组的长度不小于miniCapacity。ArrayList的自动变长机制就是在这个方法中实现的。
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 = new Object[newCapacity];
System.arraycopy(oldData, 0, elementData, 0, size);
}
}

从这个方法实现中可以看出ArrayList每次扩容,都扩大到原来大小的1.5倍。并且是生成一个新的数组,废弃原来老的数组.
每种add()方法的实现都大同小异,下面给出add(Object)方法的实现:
public boolean add(Object o) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = o;
return true;
}


4.iterator()中的同步
在父类AbstractList中定义了一个int型的属性:modCount,记录了ArrayList结构性变化的次数。
protected transient int modCount = 0;

在ArrayList的所有涉及结构变化的方法中都增加modCount的值,包括:add()、remove()、addAll()、removeRange()及clear()方法。这些方法每调用一次,modCount的值就加1。
注:add()及addAll()方法的modCount的值是在其中调用的ensureCapacity()方法中增加的。

AbstractList中的iterator()方法(ArrayList直接继承了这个方法)使用了一个私有内部成员类Itr,生成一个Itr对象(Iterator接口)返回:
public Iterator iterator() {
return new Itr();
}

Itr实现了Iterator()接口,其中也定义了一个int型的属性:expectedModCount,这个属性在Itr类初始化时被赋予ArrayList对象的modCount属性的值。
int expectedModCount = modCount;

注:内部成员类Itr也是ArrayList类的一个成员,它可以访问所有的AbstractList的属性和方法。理解了这一点,Itr类的实现就容易理解了。

在Itr.hasNext()方法中:
public boolean hasNext() {
return cursor != size();
}

调用了AbstractList的size()方法,比较当前光标位置是否越界。

在Itr.next()方法中,Itr也调用了定义在AbstractList中的get(int)方法,返回当前光标处的元素:
public Object next() {
try {
Object next = get(cursor);
checkForComodification();
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}

注意,在next()方法中调用了checkForComodification()方法,进行对修改的同步检查:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

现在对modCount和expectedModCount的作用应该非常清楚了。在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操作,这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。在AbstractList中,使用了一个简单的机制来规避这些风险。这就是modCount和expectedModCount的作用所在。

5.序列化支持
ArrayList实现了java.io.Serializable接口,所以ArrayList对象可以序列化到持久存储介质中。ArrayList的主要属性定义如下:

private static final long serialVersionUID = 8683452581122892189L;

private transient Object elementData[];

private int size;

可以看出serialVersionUID和size都将自动序列化到介质中,但elementData数组对象却定义为transient了。也就是说ArrayList中的所有这些元素都不会自动系列化到介质中。为什么要这样实现?因为elementData数组中存储的“元素”其实仅是对这些元素的一个引用,并不是真正的对象,序列化一个对象的引用是毫无意义的,因为序列化是为了反序列化,当你反序列化时,这些对象的引用已经不可能指向原来的对象了。所以在这儿需要手工的对ArrayList的元素进行序列化操作。这就是writeObject()的作用。
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
}

这样元素数组elementData中的所以元素对象就可以正确地序列化到存储介质了。
对应的readObject()也按照writeObject()方法的顺序从输入流中读取:
private synchronized void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in array length and allocate array
int arrayLength = s.readInt();
elementData = new Object[arrayLength];
// Read in all elements in the proper order.
for (int i=0; i<size; i++)
elementData[i] = s.readObject();
}

ArrayList继承了AbstractList,实现了List,RandomAccess,Cloneable接口

Java代码 

1.  public class ArrayList<E> extends AbstractList<E>  

2.          implements List<E>, RandomAccess, Cloneable, java.io.Serializable  

 

内部结构是一个Object类型的数组

Java代码 

1.  private transient Object[] elementData;  

 ArrayList的大小,也就是元素个数

Java代码 

1.  private int size;  

 

下面是几个构造函数:

1.自定义初始化容量的构造函数:

Java代码 

1.  public ArrayList(int initialCapacity) {  

2.  super();  

3.         if (initialCapacity < 0)//初始化容量不能小于0,抛出IllegalArgumentException异常  

4.             throw new IllegalArgumentException("Illegal Capacity: "+  

5.                                                initialCapacity);  

6.  this.elementData = new Object[initialCapacity];//根据参数初始化一个数组,底层是个object数组  

7.     }  

 2.默认构造函数:

调用上面的构造函数,默认初始化内部数组大小为10

Java代码 

1.  public ArrayList() {  

2.  s(10);//默认初始容量是10  

3.  }  

 3.collection转换的构造函数:

Java代码 

1.     public ArrayList(Collection<? extends E> c) {  

2.  elementData = c.toArray();//调用toArray()方法把collection转换成数组  

3.  size = elementData.length;//把数组的长度赋值给ArrayListsize属性  

4.  // c.toArray might (incorrectly) not return Object[] (see 6260652)  

5.  if (elementData.getClass() != Object[].class)  

6.      elementData = Arrays.copyOf(elementData, size, Object[].class);  

7.     }  

 返回ArrayList的大小:

Java代码 

1.  public int size() {  

2.  urn size;  

3.  }  

 

判断是否为空:

Java代码 

1.  public boolean isEmpty() {  

2.  urn size == 0;//就是看当前size是否为0  

3.  }  

 找出一个元素第一次出现的下标:

Java代码 

1.     public int indexOf(Object o) {  

2.      //由于数组的下标是从0开始的,所以判断是否存在只要大于0就可以了  

3.  if (o == null) {  

4.      for (int i = 0; i < size; i++)//注意这里是size属性而不是elementDatalength  

5.      if (elementData[i]==null)  

6.          return i;  

7.  else {  

8.      for (int i = 0; i < size; i++)  

9.      if (o.equals(elementData[i]))//equals方法来判断  

10.         return i;  

11. }  

12. return -1;//没有的话返回-1  

13.    }  

 

判断是否包含一个元素:

Java代码 

1.  public boolean contains(Object o) {  

2.  urn indexOf(o) >= 0;//不存在是-1  

3.  }  

 

元素最后一次出现的下标:

Java代码 

1.    public int lastIndexOf(Object o) {  

2.  if (o == null) {  

3.      for (int i = size-1; i >= 0; i--)//注意这里的i是等于数组长度减1,从数组的最后一位开始  

4.      if (elementData[i]==null)  

5.          return i;  

6.  else {  

7.      for (int i = size-1; i >= 0; i--)  

8.      if (o.equals(elementData[i]))  

9.          return i;  

10. }  

11. return -1;  

12.    }  

 重新分配ArrayList空间为元素多少的真实大小:

注意size一般和内部结构的数组长度是不一样的,通过上面的构造函数我们知道内部数组初始化容量是10,

size是在add()方法后才加一,这个方法是保证列表的大小和内部数组的大小一致

Java代码 

1.     public void trimToSize() {  

2.  modCount++;  

3.  int oldCapacity = elementData.length;  

4.  if (size < oldCapacity) {  

5.             elementData = Arrays.copyOf(elementData, size);  

6.  }  

7.     }  

 如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数

因为每次重新分配空间都是比较消耗时间的,所以如果能预计list可能的大小
的话可以通过自己的控制ArrayList的大小来提高效率

Java代码 

1.     public void ensureCapacity(int minCapacity) {//注意是public类型,也就是说可以我门自己来重新分配空间  

2.  modCount++;  

3.  int oldCapacity = elementData.length;//老的容量  

4.  if (minCapacity > oldCapacity) {  

5.      Object oldData[] = elementData;//把当前数组临时保存起来  

6.      int newCapacity = (oldCapacity * 3)/2 + 1;//1是为了保证oldCapacity1或者0的情况下  

7.          if (newCapacity < minCapacity)//是按1.5被来增加容量的  

8.      newCapacity = minCapacity;  

9.             // minCapacity is usually close to size, so this is a win:  

10.            elementData = Arrays.copyOf(elementData, newCapacity);  

11. }  

12.    }  

clone一个副本:

Java代码 

1.     public Object clone() {  

2.  try {  

3.      ArrayList<E> v = (ArrayList<E>) super.clone();  

4.      v.elementData = Arrays.copyOf(elementData, size);  

5.      v.modCount = 0;  

6.      return v;  

7.  catch (CloneNotSupportedException e) {  

8.      // this shouldn't happen, since we are Cloneable  

9.      throw new InternalError();  

10. }  

11.    }  

 转换为数组:

Java代码 

1.  public Object[] toArray() {  

2.      return Arrays.copyOf(elementData, size);//调用Arrays.copyOf()方法  

3.  }  

 下面是转换为泛型数组:

Java代码 

1.     public <T> T[] toArray(T[] a) {  

2.         if (a.length < size)  

3.             // Make a new array of a's runtime type, but my contents:  

4.             return (T[]) Arrays.copyOf(elementData, size, a.getClass());  

5.  System.arraycopy(elementData, 0, a, 0, size);  

6.         if (a.length > size)  

7.             a[size] = null;  

8.         return a;  

9.     }  

 

范围检查:臭名昭著的 IndexOutOfBoundsException异常

Java代码 

1.  private void RangeCheck(int index) {  

2.  (index >= size)//数组越界,这里没有判断小于0的情况  

3.   throw new IndexOutOfBoundsException(  

4.  ndex: "+index+", Size: "+size);  

5.  }  

 通过下标得到一个元素:

Java代码 

1.     public E get(int index) {  

2.  RangeCheck(index);//先检查是否越界  

3.    

4.  return (E) elementData[index];//返回的是数组中的下标    }  

 通过下标和一个元素赋值,返回的是原先的值:

Java代码 

1.     public E set(int index, E element) {  

2.  RangeCheck(index);//先检查是否越界  

3.    

4.  E oldValue = (E) elementData[index];//通过临时变量把当前下标的值保存  

5.  elementData[index] = element;//赋值  

6.  return oldValue;//注意返回的是当前下标的原先值  

7.     }  

 

添加一个新的元素到末尾,前面说道新增方法都要先调用ensureCapacity方法:

Java代码 

1.     public boolean add(E e) {  

2.  ensureCapacity(size + 1); //大小加一 // Increments modCount!!  

3.  elementData[size++] = e;//size默认是0所以是从0开始赋值  

4.  return true;  

5.     }  

 API文档中的说明是:将指定的元素插入此列表中的指定位置。向右移动当前位于该位置的元素(如果有)以及所有后续元素(将其索引加 1)。通俗的说法是在指定位置插入元素,指定元素和后面的元素后移

这个方法和set(int index, E element)不一样,set只是把元素赋值给指定的下标同时返回下标的原先值.

add(int index, E element)的判断越界是通过元素的大小来判断的

所以如果

Java代码 

1.  ArrayList list=new ArrayList();  

2.  list.add(18);  

3.  //报错,因为size元素大小还是0  

4.  //如果l  

5.  list.add(0,"")//就可以  

 

如果一致add同一下标所有后续元素索引加1

如下:

Java代码 

1.  ArrayList list=new ArrayList();  

2.  list.add(08);  

3.  list.add(08);  

4.  list.add(08);  

5.  System.out.println(list);  

6.  //结果为[8, 8, 8]  

 

:

Java代码 

1.     public void add(int index, E element) {  

2.  if (index > size || index < 0)//判断是否越界,注意这里是以元素的个数来判断的  

3.      throw new IndexOutOfBoundsException(  

4.      "Index: "+index+", Size: "+size);  

5.    

6.  ensureCapacity(size+1);  // Increments modCount!!  

7.  System.arraycopy(elementData, index, elementData, index + 1,  

8.           size - index);  

9.  //源数组中位置在 srcPos  srcPos+length-1 之间的组件被分别复制到  

10. //目标数组中的 destPos  destPos+length-1 位置  

11. elementData[index] = element;  

12. size++;//元素加一  

13.    }  

 删除指定位置的元素,返回被删除的元素,由于ArrayList采用一个对象数组存储元素,所以在删除一个元素时需要把后面的元素前移。删除一个元素时只是把该元素在elementData数组中的引用置为null,具体的对象的销毁由垃圾收集器负责

Java代码 

1.     public E remove(int index) {  

2.  RangeCheck(index);//判断是否越界  

3.    

4.  modCount++;  

5.  E oldValue = (E) elementData[index];  

6.    

7.  int numMoved = size - index - 1;//新的数组长度  

8.  if (numMoved > 0)  

9.      System.arraycopy(elementData, index+1, elementData, index,  

10.              numMoved);  

11. elementData[--size] = null// Let gc do its work  

12.   

13. return oldValue;//返回删除前的数据  

14.    }  

 内部删除方法,跳过越界检查,不返回删除元素的值:ArrayList内部调用的删除方法

Java代码 

1.  /* 

2.    * Private remove method that skips bounds checking and does not 

3.    * return the value removed. 

4.    */  

5.   private void fastRemove(int index) {  

6.       modCount++;  

7.       int numMoved = size - index - 1;  

8.       if (numMoved > 0)  

9.           System.arraycopy(elementData, index+1, elementData, index,  

10.                           numMoved);  

11.      elementData[--size] = null// Let gc do its work  

12.  }  

 删除指定元素:

Java代码 

1.     public boolean remove(Object o) {  

2.  if (o == null) {  

3.             for (int index = 0; index < size; index++)  

4.      if (elementData[index] == null) {  

5.          fastRemove(index);  

6.          return true;  

7.      }  

8.  else {  

9.      for (int index = 0; index < size; index++)  

10.     if (o.equals(elementData[index])) {  

11.         fastRemove(index);  

12.         return true;  

13.     }  

14.        }  

15. return false;  

16.    }  

 清空列表:

Java代码 

1.     public void clear() {  

2.  modCount++;  

3.    

4.  // Let gc do its work  

5.  for (int i = 0; i < size; i++)  

6.      elementData[i] = null;  

7.    

8.  size = 0;//设定元素大小为0  

9.     }  

 

添加集合c中的元素到ArrayList的末尾,添加成功返回true,如果集合c为空,返回false

Java代码 

1.    public boolean addAll(Collection<? extends E> c) {  

2.  Object[] a = c.toArray();  

3.         int numNew = a.length;  

4.  ensureCapacity(size + numNew);  // Increments modCount  

5.         System.arraycopy(a, 0, elementData, size, numNew);//添加到列表的末尾  

6.         size += numNew;  

7.  return numNew != 0;  

8.     }  

 

在指定位置插入集合中的所有元素,和上面一个方法基本差不多,指定位置元素和以后的都要后移

Java代码 

1.     public boolean addAll(int index, Collection<? extends E> c) {  

2.  if (index > size || index < 0)//判断越界   

3.      throw new IndexOutOfBoundsException(  

4.      "Index: " + index + ", Size: " + size);  

5.    

6.  Object[] a = c.toArray();  

7.  int numNew = a.length;  

8.  ensureCapacity(size + numNew);  // Increments modCount  

9.    

10. int numMoved = size - index;  

11. if (numMoved > 0)//两种情况  

12.     System.arraycopy(elementData, index, elementData, index + numNew,  

13.              numMoved);  

14.   

15.        System.arraycopy(a, 0, elementData, index, numNew);//这里是等于0的情况也就是说直接在数组后面加  

16. size += numNew;  

17. return numNew != 0;  

18.    }  

 

删除指定范围的元素

Java代码 

1.  protected void removeRange(int fromIndex, int toIndex) {  

2.  modCount++;  

3.  int numMoved = size - toIndex;  

4.         System.arraycopy(elementData, toIndex, elementData, fromIndex,  

5.                          numMoved);  

6.    

7.  // Let gc do its work  

8.  int newSize = size - (toIndex-fromIndex);  

9.  while (size != newSize)  

10.     elementData[--size] = null;  

11.    }  

 

ArrayListJava集合框架中的一个类,它实现了List接口,可以用来存储一组对象,这些对象可以是任意类型。 下面是ArrayList源码解析: 1. 成员变量 ```java /** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size; ``` ArrayList有三个成员变量,分别是DEFAULT_CAPACITY、EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA。DEFAULT_CAPACITY表示默认的容量大小,EMPTY_ELEMENTDATA是一个空数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA也是一个空数组,但它会在第一次添加元素时扩容为DEFAULT_CAPACITY大小。elementData是一个Object类型的数组,用于存储ArrayList中的元素,size表示ArrayList中元素的数量。 2. 构造方法 ```java /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // defend against c.toArray (incorrectly) not returning Object[] // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } } /** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ 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); } } ``` ArrayList提供了三个构造方法。第一个构造方法是无参的构造方法,它将elementData赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。第二个构造方法接收一个Collection类型的参数c,它将参数c中的元素转为数组并将其赋值给elementData。第三个构造方法接收一个int类型的参数initialCapacity,它根据参数initialCapacity的值创建一个Object类型的数组并将其赋值给elementData。 3. 常用方法 常用方法包括add()、get()、set()、remove()、size()等。 add()方法用于在ArrayList中添加一个元素,如果elementData的容量不足,就需要进行扩容。扩容的方式是将elementData数组的大小增加50%。 get()方法用于获取ArrayList中指定位置的元素。 set()方法用于将ArrayList中指定位置的元素替换为指定的元素。 remove()方法用于删除ArrayList中指定位置的元素。 size()方法用于获取ArrayList中元素的数量。 4. 总结 ArrayListJava集合框架中的一个类,它实现了List接口,可以用来存储一组对象。ArrayList源码解析包括成员变量、构造方法和常用方法。掌握ArrayList源码可以帮助我们更好地理解它的实现原理,从而更加灵活地应用它。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值