【转】ArrayList源码分析

java源码分析之ArrayList

标签: ArrayListarraylistjavaJavaJAVA

2013-01-25 08:52 8612人阅读 评论(7) 收藏 举报

分类:

java(35)

版权声明:本文为博主原创文章,未经博主允许不得转载。

ArrayList就是传说中的动态数组,就是Array的复杂版本,它提供了如下一些好处:动态的增加和减少元素、灵活的设置数组的大小......

认真阅读本文,我相信一定会对你有帮助。比如为什么ArrayList里面提供了一个受保护的removeRange方法?提供了其他没有被调用过的私有方法?

首先看到对ArrayList的定义:

[java] view plain copy

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

从ArrayList<E>可以看出它是支持泛型的,它继承自AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable接口。

AbstractList提供了List接口的默认实现(个别方法为抽象方法)。

List接口定义了列表必须实现的方法。

RandomAccess是一个标记接口,接口内没有定义任何内容。

实现了Cloneable接口的类,可以调用Object.clone方法返回该对象的浅拷贝。

通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。序列化接口没有方法或字段,仅用于标识可序列化的语义。

ArrayList的属性

ArrayList定义只定义类两个私有属性:

[java] view plain copy

  1. /**
  2. * The array buffer into which the elements of the ArrayList are stored.
  3. * The capacity of the ArrayList is the length of this array buffer.
  4. */
  5. private transient Object[] elementData;
  6. /**
  7. * The size of the ArrayList (the number of elements it contains).
  8. *
  9. * @serial
  10. */
  11. private int size;

[java] view plain copy

  1. 很容易理解,elementData存储ArrayList内的元素,size表示它包含的元素的数量。
  2. 有个关键字需要解释:transient。
  3. Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
  4. ansient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。
  5. 有点抽象,看个例子应该能明白。

[java] view plain copy

  1. public class UserInfo implements Serializable {
  2. private static final long serialVersionUID = 996890129747019948L;
  3. private String name;
  4. private transient String psw;
  5. public UserInfo(String name, String psw) {
  6. this.name = name;
  7. this.psw = psw;
  8. }
  9. public String toString() {
  10. return "name=" + name + ", psw=" + psw;
  11. }
  12. }
  13. public class TestTransient {
  14. public static void main(String[] args) {
  15. UserInfo userInfo = new UserInfo("张三", "123456");
  16. System.out.println(userInfo);
  17. try {
  18. // 序列化,被设置为transient的属性没有被序列化
  19. ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
  20. "UserInfo.out"));
  21. o.writeObject(userInfo);
  22. o.close();
  23. } catch (Exception e) {
  24. // TODO: handle exception
  25. e.printStackTrace();
  26. }
  27. try {
  28. // 重新读取内容
  29. ObjectInputStream in = new ObjectInputStream(new FileInputStream(
  30. "UserInfo.out"));
  31. UserInfo readUserInfo = (UserInfo) in.readObject();
  32. //读取后psw的内容为null
  33. System.out.println(readUserInfo.toString());
  34. } catch (Exception e) {
  35. // TODO: handle exception
  36. e.printStackTrace();
  37. }
  38. }
  39. }

被标记为transient的属性在对象被序列化的时候不会被保存。

接着回到ArrayList的分析中......

ArrayList的构造方法

看完属性看构造方法。ArrayList提供了三个构造方法:

[java] view plain copy

  1. /**
  2. * Constructs an empty list with the specified initial capacity.
  3. */
  4. public ArrayList(int initialCapacity) {
  5. super();
  6. if (initialCapacity < 0)
  7. throw new IllegalArgumentException("Illegal Capacity: "+
  8. initialCapacity);
  9. this.elementData = new Object[initialCapacity];
  10. }
  11. /**
  12. * Constructs an empty list with an initial capacity of ten.
  13. */
  14. public ArrayList() {
  15. this(10);
  16. }
  17. /**
  18. * Constructs a list containing the elements of the specified
  19. * collection, in the order they are returned by the collection‘s
  20. * iterator.
  21. */
  22. public ArrayList(Collection<? extends E> c) {
  23. elementData = c.toArray();
  24. size = elementData.length;
  25. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  26. if (elementData.getClass() != Object[].class)
  27. elementData = Arrays.copyOf(elementData, size, Object[].class);
  28. }

第一个构造方法使用提供的initialCapacity来初始化elementData数组的大小。第二个构造方法调用第一个构造方法并传入参数
10,即默认elementData数组的大小为10。第三个构造方法则将提供的集合转成数组返回给elementData(返回若不是Object[]
将调用Arrays.copyOf方法将其转为Object[])。

ArrayList的其他方法

add(E e)

add(E e)都知道是在尾部添加一个元素,如何实现的呢?

[java] view plain copy

  1. public boolean add(E e) {
  2. ensureCapacity(size + 1);  // Increments modCount!!
  3. elementData[size++] = e;
  4. return true;
  5. }

书上都说ArrayList是基于数组实现的,属性中也看到了数组,具体是怎么实现的呢?比如就这个添加元素的方法,如果数组大,则在将某个位置的值设置为指定元素即可,如果数组容量不够了呢?

看到add(E
e)中先调用了ensureCapacity(size+1)方法,之后将元素的索引赋给elementData[size],而后size自增。例如初
次添加时,size为0,add将elementData[0]赋值为e,然后size设置为1(类似执行以下两条语句
elementData[0]=e;size=1)。将元素的索引赋给elementData[size]不是会出现数组越界的情况吗?这里关键就在
ensureCapacity(size+1)中了。

根据ensureCapacity的方法名可以知道是确保容量用的。ensureCapacity(size+1)后面的注释可以明白是增加modCount的值(加了俩感叹号,应该蛮重要的,来看看)。

[java] view plain copy

  1. /**
  2. * Increases the capacity of this <tt>ArrayList</tt> instance, if
  3. * necessary, to ensure that it can hold at least the number of elements
  4. * specified by the minimum capacity argument.
  5. *
  6. * @param   minCapacity   the desired minimum capacity
  7. */
  8. public void ensureCapacity(int minCapacity) {
  9. modCount++;
  10. int oldCapacity = elementData.length;
  11. if (minCapacity > oldCapacity) {
  12. Object oldData[] = elementData;
  13. int newCapacity = (oldCapacity * 3)/2 + 1;
  14. if (newCapacity < minCapacity)
  15. newCapacity = minCapacity;
  16. // minCapacity is usually close to size, so this is a win:
  17. elementData = Arrays.copyOf(elementData, newCapacity);
  18. }
  19. }

The number of times this list has been structurally modified.

这是对modCount的解释,意为记录list结构被改变的次数(观察源码可以发现每次调用ensureCapacoty方法,modCount的值都将增加,但未必数组结构会改变,所以感觉对modCount的解释不是很到位)。

增加modCount之后,判断minCapacity(即
size+1)是否大于oldCapacity(即elementData.length),若大于,则调整容量为
max((oldCapacity*3)/2+1,minCapacity),调整elementData容量为新的容量,即将返回一个内容为原数组元
素,大小为新容量的数组赋给elementData;否则不做操作。

所以调用ensureCapacity至少将elementData的容量增加的1,所以elementData[size]不会出现越界的情况。

容量的拓展将导致数组元素的复制,多次拓展容量将执行多次整个数组内容的复制。若提前能大致判断list的长度,调用ensureCapacity调整容量,将有效的提高运行速度。

可以理解提前分配好空间可以提高运行速度,但是测试发现提高的并不是很大,而且若list原本数据量就不会很大效果将更不明显。

add(int index, E element)

add(int index,E element)在指定位置插入元素。

[java] view plain copy

  1. public void add(int index, E element) {
  2. if (index > size || index < 0)
  3. throw new IndexOutOfBoundsException(
  4. "Index: "+index+", Size: "+size);
  5. ensureCapacity(size+1);  // Increments modCount!!
  6. System.arraycopy(elementData, index, elementData, index + 1,
  7. size - index);
  8. elementData[index] = element;
  9. size++;
  10. }

首先判断指定位置index是否超出elementData的界限,之后调用ensureCapacity调整容量(若容量足够则不会拓展),调用
System.arraycopy将elementData从index开始的size-index个元素复制到index+1至size+1的位置(即
index开始的元素都向后移动一个位置),然后将index位置的值指向element。

addAll(Collection<? extends E> c)

[java] view plain copy

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

先将集合c转换成数组,根据转换后数组的程度和ArrayList的size拓展容量,之后调用System.arraycopy方法复制元素到
elementData的尾部,调整size。根据返回的内容分析,只要集合c的大小不为空,即转换后的数组长度不为0则返回true。

addAll(int index,Collection<? extends E> c)

[java] view plain copy

  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. Object[] a = c.toArray();
  6. int numNew = a.length;
  7. ensureCapacity(size + numNew);  // Increments modCount
  8. int numMoved = size - index;
  9. if (numMoved > 0)
  10. System.arraycopy(elementData, index, elementData, index + numNew,
  11. numMoved);
  12. System.arraycopy(a, 0, elementData, index, numNew);
  13. size += numNew;
  14. return numNew != 0;
  15. }

先判断index是否越界。其他内容与addAll(Collection<? extends E>
c)基本一致,只是复制的时候先将index开始的元素向后移动X(c转为数组后的长度)个位置(也是一个复制的过程),之后将数组内容复制到
elementData的index位置至index+X。

clear()

[java] view plain copy

  1. public void clear() {
  2. modCount++;
  3. // Let gc do its work
  4. for (int i = 0; i < size; i++)
  5. elementData[i] = null;
  6. size = 0;
  7. }

clear的时候并没有修改elementData的长度(好不容易申请、拓展来的,凭什么释放,留着搞不好还有用呢。这使得确定不再修改list内容之后最好调用trimToSize来释放掉一些空间),只是将所有元素置为null,size设置为0。

clone()

返回此 ArrayList 实例的浅表副本。(不复制这些元素本身。)

[java] view plain copy

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

调用父类的clone方法返回一个对象的副本,将返回对象的elementData数组的内容赋值为原对象elementData数组的内容,将副本的modCount设置为0。

contains(Object)

[html] view plain copy

  1. public boolean contains(Object o) {
  2. return indexOf(o) >= 0;
  3. }

indexOf方法返回值与0比较来判断对象是否在list中。接着看indexOf。

indexOf(Object)

[java] view plain copy

  1. public int indexOf(Object o) {
  2. if (o == null) {
  3. for (int i = 0; i < size; i++)
  4. if (elementData[i]==null)
  5. return i;
  6. } else {
  7. for (int i = 0; i < size; i++)
  8. if (o.equals(elementData[i]))
  9. return i;
  10. }
  11. return -1;
  12. }

通过遍历elementData数组来判断对象是否在list中,若存在,返回index([0,size-1]),若不存在则返回-1。所以contains方法可以通过indexOf(Object)方法的返回值来判断对象是否被包含在list中。

既然看了indexOf(Object)方法,接着就看lastIndexOf,光看名字应该就明白了返回的是传入对象在elementData数组中最后出现的index值。

[java] view plain copy

  1. public int lastIndexOf(Object o) {
  2. if (o == null) {
  3. for (int i = size-1; i >= 0; i--)
  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. }

采用了从后向前遍历element数组,若遇到Object则返回index值,若没有遇到,返回-1。

get(int index)

这个方法看着很简单,应该是返回elementData[index]就完了。

[java] view plain copy

  1. public E get(int index) {
  2. RangeCheck(index);
  3. return (E) elementData[index];
  4. }

但看代码的时候看到调用了RangeCheck方法,而且还是大写的方法,看看究竟有什么内容吧。

[java] view plain copy

  1. /**
  2. * Checks if the given index is in range.
  3. */
  4. private void RangeCheck(int index) {
  5. if (index >= size)
  6. throw new IndexOutOfBoundsException(
  7. "Index: "+index+", Size: "+size);
  8. }

就是检查一下是不是超出数组界限了,超出了就抛出IndexOutBoundsException异常。为什么要大写呢???

isEmpty()

直接返回size是否等于0。

remove(int index)

[java] view plain copy

  1. public E remove(int index) {
  2. RangeCheck(index);
  3. modCount++;
  4. E oldValue = (E) elementData[index];
  5. int numMoved = size - index - 1;
  6. if (numMoved > 0)
  7. System.arraycopy(elementData, index+1, elementData, index,
  8. numMoved);
  9. elementData[--size] = null; // Let gc do its work
  10. return oldValue;
  11. }

首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。

remove(Object o)

[java] view plain copy

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

首先通过代码可以看到,当移除成功后返回true,否则返回false。remove(Object
o)中通过遍历element寻找是否存在传入对象,一旦找到就调用fastRemove移除对象。为什么找到了元素就知道了index,不通过
remove(index)来移除元素呢?因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且
fastRemove并不返回被移除的元素。下面是fastRemove的代码,基本和remove(index)一致。

[java] view plain copy

  1. private void fastRemove(int index) {
  2. modCount++;
  3. int numMoved = size - index - 1;
  4. if (numMoved > 0)
  5. System.arraycopy(elementData, index+1, elementData, index,
  6. numMoved);
  7. elementData[--size] = null; // Let gc do its work
  8. }

removeRange(int fromIndex,int toIndex)

[java] view plain copy

  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. // Let gc do its work
  7. int newSize = size - (toIndex-fromIndex);
  8. while (size != newSize)
  9. elementData[--size] = null;
  10. }

执行过程是将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。

这个方法是protected,及受保护的方法,为什么这个方法被定义为protected呢?

这是一个解释,但是可能不容易看明白。http://stackoverflow.com/questions/2289183/why-is-javas-abstractlists-removerange-method-protected

先看下面这个例子

[java] view plain copy

  1. ArrayList<Integer> ints = new ArrayList<Integer>(Arrays.asList(0, 1, 2,
  2. 3, 4, 5, 6));
  3. // fromIndex low endpoint (inclusive) of the subList
  4. // toIndex high endpoint (exclusive) of the subList
  5. ints.subList(2, 4).clear();
  6. System.out.println(ints);

输出结果是[0,
1, 4, 5, 6],结果是不是像调用了removeRange(int fromIndex,int toIndex)!哈哈哈,就是这样的。但是为什么效果相同呢?是不是调用了removeRange(int fromIndex,int toIndex)呢?

set(int index,E element)

[java] view plain copy

  1. public E set(int index, E element) {
  2. RangeCheck(index);
  3. E oldValue = (E) elementData[index];
  4. elementData[index] = element;
  5. return oldValue;
  6. }

首先检查范围,用新元素替换旧元素并返回旧元素。

size()

size()方法直接返回size。

toArray()

[java] view plain copy

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

调用Arrays.copyOf将返回一个数组,数组内容是size个elementData的元素,即拷贝elementData从0至size-1位置的元素到新数组并返回。

toArray(T[] a)

[java] view plain copy

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

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

trimToSize()

[java] view plain copy

  1. public void trimToSize() {
  2. modCount++;
  3. int oldCapacity = elementData.length;
  4. if (size < oldCapacity) {
  5. elementData = Arrays.copyOf(elementData, size);
  6. }
  7. }

由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大
的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length很size相同,节省空
间。

学习Java最好的方式还必须是读源码。读完源码你才会发现这东西为什么是这么玩的,有哪些限制,关键点在哪里等等。而且这些源码都是大牛们写的,你能从中学习到很多。

java源码分析之ArrayList

标签: ArrayListarraylistjavaJavaJAVA

2013-01-25 08:52 8612人阅读 评论(7) 收藏 举报

分类:

java(35)

版权声明:本文为博主原创文章,未经博主允许不得转载。

ArrayList就是传说中的动态数组,就是Array的复杂版本,它提供了如下一些好处:动态的增加和减少元素、灵活的设置数组的大小......

认真阅读本文,我相信一定会对你有帮助。比如为什么ArrayList里面提供了一个受保护的removeRange方法?提供了其他没有被调用过的私有方法?

首先看到对ArrayList的定义:

[java] view plain copy

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

从ArrayList<E>可以看出它是支持泛型的,它继承自AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable接口。

AbstractList提供了List接口的默认实现(个别方法为抽象方法)。

List接口定义了列表必须实现的方法。

RandomAccess是一个标记接口,接口内没有定义任何内容。

实现了Cloneable接口的类,可以调用Object.clone方法返回该对象的浅拷贝。

通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。序列化接口没有方法或字段,仅用于标识可序列化的语义。

ArrayList的属性

ArrayList定义只定义类两个私有属性:

[java] view plain copy

  1. /**
  2. * The array buffer into which the elements of the ArrayList are stored.
  3. * The capacity of the ArrayList is the length of this array buffer.
  4. */
  5. private transient Object[] elementData;
  6. /**
  7. * The size of the ArrayList (the number of elements it contains).
  8. *
  9. * @serial
  10. */
  11. private int size;

[java] view plain copy

  1. 很容易理解,elementData存储ArrayList内的元素,size表示它包含的元素的数量。
  2. 有个关键字需要解释:transient。
  3. Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
  4. ansient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。
  5. 有点抽象,看个例子应该能明白。

[java] view plain copy

  1. public class UserInfo implements Serializable {
  2. private static final long serialVersionUID = 996890129747019948L;
  3. private String name;
  4. private transient String psw;
  5. public UserInfo(String name, String psw) {
  6. this.name = name;
  7. this.psw = psw;
  8. }
  9. public String toString() {
  10. return "name=" + name + ", psw=" + psw;
  11. }
  12. }
  13. public class TestTransient {
  14. public static void main(String[] args) {
  15. UserInfo userInfo = new UserInfo("张三", "123456");
  16. System.out.println(userInfo);
  17. try {
  18. // 序列化,被设置为transient的属性没有被序列化
  19. ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
  20. "UserInfo.out"));
  21. o.writeObject(userInfo);
  22. o.close();
  23. } catch (Exception e) {
  24. // TODO: handle exception
  25. e.printStackTrace();
  26. }
  27. try {
  28. // 重新读取内容
  29. ObjectInputStream in = new ObjectInputStream(new FileInputStream(
  30. "UserInfo.out"));
  31. UserInfo readUserInfo = (UserInfo) in.readObject();
  32. //读取后psw的内容为null
  33. System.out.println(readUserInfo.toString());
  34. } catch (Exception e) {
  35. // TODO: handle exception
  36. e.printStackTrace();
  37. }
  38. }
  39. }

被标记为transient的属性在对象被序列化的时候不会被保存。

接着回到ArrayList的分析中......

ArrayList的构造方法

看完属性看构造方法。ArrayList提供了三个构造方法:

[java] view plain copy

  1. /**
  2. * Constructs an empty list with the specified initial capacity.
  3. */
  4. public ArrayList(int initialCapacity) {
  5. super();
  6. if (initialCapacity < 0)
  7. throw new IllegalArgumentException("Illegal Capacity: "+
  8. initialCapacity);
  9. this.elementData = new Object[initialCapacity];
  10. }
  11. /**
  12. * Constructs an empty list with an initial capacity of ten.
  13. */
  14. public ArrayList() {
  15. this(10);
  16. }
  17. /**
  18. * Constructs a list containing the elements of the specified
  19. * collection, in the order they are returned by the collection‘s
  20. * iterator.
  21. */
  22. public ArrayList(Collection<? extends E> c) {
  23. elementData = c.toArray();
  24. size = elementData.length;
  25. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  26. if (elementData.getClass() != Object[].class)
  27. elementData = Arrays.copyOf(elementData, size, Object[].class);
  28. }

第一个构造方法使用提供的initialCapacity来初始化elementData数组的大小。第二个构造方法调用第一个构造方法并传入参数
10,即默认elementData数组的大小为10。第三个构造方法则将提供的集合转成数组返回给elementData(返回若不是Object[]
将调用Arrays.copyOf方法将其转为Object[])。

ArrayList的其他方法

add(E e)

add(E e)都知道是在尾部添加一个元素,如何实现的呢?

[java] view plain copy

  1. public boolean add(E e) {
  2. ensureCapacity(size + 1);  // Increments modCount!!
  3. elementData[size++] = e;
  4. return true;
  5. }

书上都说ArrayList是基于数组实现的,属性中也看到了数组,具体是怎么实现的呢?比如就这个添加元素的方法,如果数组大,则在将某个位置的值设置为指定元素即可,如果数组容量不够了呢?

看到add(E
e)中先调用了ensureCapacity(size+1)方法,之后将元素的索引赋给elementData[size],而后size自增。例如初
次添加时,size为0,add将elementData[0]赋值为e,然后size设置为1(类似执行以下两条语句
elementData[0]=e;size=1)。将元素的索引赋给elementData[size]不是会出现数组越界的情况吗?这里关键就在
ensureCapacity(size+1)中了。

根据ensureCapacity的方法名可以知道是确保容量用的。ensureCapacity(size+1)后面的注释可以明白是增加modCount的值(加了俩感叹号,应该蛮重要的,来看看)。

[java] view plain copy

  1. /**
  2. * Increases the capacity of this <tt>ArrayList</tt> instance, if
  3. * necessary, to ensure that it can hold at least the number of elements
  4. * specified by the minimum capacity argument.
  5. *
  6. * @param   minCapacity   the desired minimum capacity
  7. */
  8. public void ensureCapacity(int minCapacity) {
  9. modCount++;
  10. int oldCapacity = elementData.length;
  11. if (minCapacity > oldCapacity) {
  12. Object oldData[] = elementData;
  13. int newCapacity = (oldCapacity * 3)/2 + 1;
  14. if (newCapacity < minCapacity)
  15. newCapacity = minCapacity;
  16. // minCapacity is usually close to size, so this is a win:
  17. elementData = Arrays.copyOf(elementData, newCapacity);
  18. }
  19. }

The number of times this list has been structurally modified.

这是对modCount的解释,意为记录list结构被改变的次数(观察源码可以发现每次调用ensureCapacoty方法,modCount的值都将增加,但未必数组结构会改变,所以感觉对modCount的解释不是很到位)。

增加modCount之后,判断minCapacity(即
size+1)是否大于oldCapacity(即elementData.length),若大于,则调整容量为
max((oldCapacity*3)/2+1,minCapacity),调整elementData容量为新的容量,即将返回一个内容为原数组元
素,大小为新容量的数组赋给elementData;否则不做操作。

所以调用ensureCapacity至少将elementData的容量增加的1,所以elementData[size]不会出现越界的情况。

容量的拓展将导致数组元素的复制,多次拓展容量将执行多次整个数组内容的复制。若提前能大致判断list的长度,调用ensureCapacity调整容量,将有效的提高运行速度。

可以理解提前分配好空间可以提高运行速度,但是测试发现提高的并不是很大,而且若list原本数据量就不会很大效果将更不明显。

add(int index, E element)

add(int index,E element)在指定位置插入元素。

[java] view plain copy

  1. public void add(int index, E element) {
  2. if (index > size || index < 0)
  3. throw new IndexOutOfBoundsException(
  4. "Index: "+index+", Size: "+size);
  5. ensureCapacity(size+1);  // Increments modCount!!
  6. System.arraycopy(elementData, index, elementData, index + 1,
  7. size - index);
  8. elementData[index] = element;
  9. size++;
  10. }

首先判断指定位置index是否超出elementData的界限,之后调用ensureCapacity调整容量(若容量足够则不会拓展),调用
System.arraycopy将elementData从index开始的size-index个元素复制到index+1至size+1的位置(即
index开始的元素都向后移动一个位置),然后将index位置的值指向element。

addAll(Collection<? extends E> c)

[java] view plain copy

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

先将集合c转换成数组,根据转换后数组的程度和ArrayList的size拓展容量,之后调用System.arraycopy方法复制元素到
elementData的尾部,调整size。根据返回的内容分析,只要集合c的大小不为空,即转换后的数组长度不为0则返回true。

addAll(int index,Collection<? extends E> c)

[java] view plain copy

  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. Object[] a = c.toArray();
  6. int numNew = a.length;
  7. ensureCapacity(size + numNew);  // Increments modCount
  8. int numMoved = size - index;
  9. if (numMoved > 0)
  10. System.arraycopy(elementData, index, elementData, index + numNew,
  11. numMoved);
  12. System.arraycopy(a, 0, elementData, index, numNew);
  13. size += numNew;
  14. return numNew != 0;
  15. }

先判断index是否越界。其他内容与addAll(Collection<? extends E>
c)基本一致,只是复制的时候先将index开始的元素向后移动X(c转为数组后的长度)个位置(也是一个复制的过程),之后将数组内容复制到
elementData的index位置至index+X。

clear()

[java] view plain copy

  1. public void clear() {
  2. modCount++;
  3. // Let gc do its work
  4. for (int i = 0; i < size; i++)
  5. elementData[i] = null;
  6. size = 0;
  7. }

clear的时候并没有修改elementData的长度(好不容易申请、拓展来的,凭什么释放,留着搞不好还有用呢。这使得确定不再修改list内容之后最好调用trimToSize来释放掉一些空间),只是将所有元素置为null,size设置为0。

clone()

返回此 ArrayList 实例的浅表副本。(不复制这些元素本身。)

[java] view plain copy

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

调用父类的clone方法返回一个对象的副本,将返回对象的elementData数组的内容赋值为原对象elementData数组的内容,将副本的modCount设置为0。

contains(Object)

[html] view plain copy

  1. public boolean contains(Object o) {
  2. return indexOf(o) >= 0;
  3. }

indexOf方法返回值与0比较来判断对象是否在list中。接着看indexOf。

indexOf(Object)

[java] view plain copy

  1. public int indexOf(Object o) {
  2. if (o == null) {
  3. for (int i = 0; i < size; i++)
  4. if (elementData[i]==null)
  5. return i;
  6. } else {
  7. for (int i = 0; i < size; i++)
  8. if (o.equals(elementData[i]))
  9. return i;
  10. }
  11. return -1;
  12. }

通过遍历elementData数组来判断对象是否在list中,若存在,返回index([0,size-1]),若不存在则返回-1。所以contains方法可以通过indexOf(Object)方法的返回值来判断对象是否被包含在list中。

既然看了indexOf(Object)方法,接着就看lastIndexOf,光看名字应该就明白了返回的是传入对象在elementData数组中最后出现的index值。

[java] view plain copy

  1. public int lastIndexOf(Object o) {
  2. if (o == null) {
  3. for (int i = size-1; i >= 0; i--)
  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. }

采用了从后向前遍历element数组,若遇到Object则返回index值,若没有遇到,返回-1。

get(int index)

这个方法看着很简单,应该是返回elementData[index]就完了。

[java] view plain copy

  1. public E get(int index) {
  2. RangeCheck(index);
  3. return (E) elementData[index];
  4. }

但看代码的时候看到调用了RangeCheck方法,而且还是大写的方法,看看究竟有什么内容吧。

[java] view plain copy

  1. /**
  2. * Checks if the given index is in range.
  3. */
  4. private void RangeCheck(int index) {
  5. if (index >= size)
  6. throw new IndexOutOfBoundsException(
  7. "Index: "+index+", Size: "+size);
  8. }

就是检查一下是不是超出数组界限了,超出了就抛出IndexOutBoundsException异常。为什么要大写呢???

isEmpty()

直接返回size是否等于0。

remove(int index)

[java] view plain copy

  1. public E remove(int index) {
  2. RangeCheck(index);
  3. modCount++;
  4. E oldValue = (E) elementData[index];
  5. int numMoved = size - index - 1;
  6. if (numMoved > 0)
  7. System.arraycopy(elementData, index+1, elementData, index,
  8. numMoved);
  9. elementData[--size] = null; // Let gc do its work
  10. return oldValue;
  11. }

首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。

remove(Object o)

[java] view plain copy

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

首先通过代码可以看到,当移除成功后返回true,否则返回false。remove(Object
o)中通过遍历element寻找是否存在传入对象,一旦找到就调用fastRemove移除对象。为什么找到了元素就知道了index,不通过
remove(index)来移除元素呢?因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且
fastRemove并不返回被移除的元素。下面是fastRemove的代码,基本和remove(index)一致。

[java] view plain copy

  1. private void fastRemove(int index) {
  2. modCount++;
  3. int numMoved = size - index - 1;
  4. if (numMoved > 0)
  5. System.arraycopy(elementData, index+1, elementData, index,
  6. numMoved);
  7. elementData[--size] = null; // Let gc do its work
  8. }

removeRange(int fromIndex,int toIndex)

[java] view plain copy

  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. // Let gc do its work
  7. int newSize = size - (toIndex-fromIndex);
  8. while (size != newSize)
  9. elementData[--size] = null;
  10. }

执行过程是将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。

这个方法是protected,及受保护的方法,为什么这个方法被定义为protected呢?

这是一个解释,但是可能不容易看明白。http://stackoverflow.com/questions/2289183/why-is-javas-abstractlists-removerange-method-protected

先看下面这个例子

[java] view plain copy

  1. ArrayList<Integer> ints = new ArrayList<Integer>(Arrays.asList(0, 1, 2,
  2. 3, 4, 5, 6));
  3. // fromIndex low endpoint (inclusive) of the subList
  4. // toIndex high endpoint (exclusive) of the subList
  5. ints.subList(2, 4).clear();
  6. System.out.println(ints);

输出结果是[0,
1, 4, 5, 6],结果是不是像调用了removeRange(int fromIndex,int toIndex)!哈哈哈,就是这样的。但是为什么效果相同呢?是不是调用了removeRange(int fromIndex,int toIndex)呢?

set(int index,E element)

[java] view plain copy

  1. public E set(int index, E element) {
  2. RangeCheck(index);
  3. E oldValue = (E) elementData[index];
  4. elementData[index] = element;
  5. return oldValue;
  6. }

首先检查范围,用新元素替换旧元素并返回旧元素。

size()

size()方法直接返回size。

toArray()

[java] view plain copy

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

调用Arrays.copyOf将返回一个数组,数组内容是size个elementData的元素,即拷贝elementData从0至size-1位置的元素到新数组并返回。

toArray(T[] a)

[java] view plain copy

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

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

trimToSize()

[java] view plain copy

  1. public void trimToSize() {
  2. modCount++;
  3. int oldCapacity = elementData.length;
  4. if (size < oldCapacity) {
  5. elementData = Arrays.copyOf(elementData, size);
  6. }
  7. }

由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大
的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length很size相同,节省空
间。

学习Java最好的方式还必须是读源码。读完源码你才会发现这东西为什么是这么玩的,有哪些限制,关键点在哪里等等。而且这些源码都是大牛们写的,你能从中学习到很多。

时间: 2024-10-17 06:11:19

【转】ArrayList源码分析的相关文章

Java - ArrayList源码分析

java提高篇(二一)-----ArrayList 一.ArrayList概述 ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小. 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小.默认初始容量为10.随着ArrayList中元素的增加,它的容量也会不断的自动增长.在每次添加新的元素时,Array

Java集合系列之ArrayList源码分析

一.ArrayList简介 ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类. 该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加.如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能. ArrayList的用法和Vector向类似,但是Vect

Java笔记---ArrayList源码分析

一.前言 一直就想看看java的源码,学习一下大牛的编程.这次下狠心花了几个晚上的时间,终于仔细分析了下 ArrayList 的源码(PS:谁说的一个晚上可以看完的?太瞎扯了).现在记录一下所得. 二.ArrayList 源码分析 2.1 如何分析? 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 Eclipse 找到所需要分析的类(此处就是 ArrayList) 新建类:新建一个类,命名为 ArrayList,将源码拷贝到该类.因为我

集合类学习之Arraylist 源码分析

1.概述 ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小. 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小.它总是至少等于列表的大小(如果不指定capacity,默认是10).    /**      * Constructs an empty list with an initial capacity of ten.

ArrayList源码分析--jdk1.8

ArrayList概述   1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合.  2. ArrayList支持随机访问.克隆.序列化,元素有序且可以重复.  3. ArrayList初始默认长度10,使用Object[]存储各种数据类型. ArrayList数据结构   数据结构是集合的精华所在,数据结构往往也限制了集合的作用和侧重点,了解各种数据结构是我们分析源码的必经之路.  ArrayList的数据结构如下: ArrayList源码分析 /* * 用数

Java ArrayList源码分析(有助于理解数据结构)

arraylist源码分析 1.数组介绍 数组是数据结构中很基本的结构,很多编程语言都内置数组,类似于数据结构中的线性表 在java中当创建数组时会在内存中划分出一块连续的内存,然后当有数据进入的时候会将数据按顺序的存储在这块连续的内存中.当需要读取数组中的数据时,需要提供数组中的索引,然后数组根据索引将内 存中的数据取出来,返回给读取程序.在Java中并不是所有的数据都能存储到数组中,只有相同类型的数据才可以一起存储到数组中.    因为数组在存储数据时是按顺序存储的,存储数据的内存也是连续的

Java中ArrayList源码分析

一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保证容量能容纳所有数据. 1.1.ArrayList 的继承与实现接口 ArrayList继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口. public class  ArrayList<E> ex

Java集合框架之一:ArrayList源码分析

版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! ArrayList底层维护的是一个动态数组,每个ArrayList实例都有一个容量.该容量是指用来存储列表元素的数组的大小.它总是至少等于列表的大小.随着向 ArrayList 中不断添加元素,其容量也自动增长. ArrayList不是同步的(也就是说不是线程安全的),如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步,在多线程环境下,可以使用Collections.synch

Java集合之ArrayList源码分析

1.简介 List在数据结构中表现为是线性表的方式,其元素以线性方式存储,集合中允许存放重复的对象,List接口主要的实现类有ArrayList和LinkedList.Java中分别提供了这两种结构的实现,这一篇文章是要熟悉下ArrayList的源码实现.使用示例如下: package com.test.collections; import java.util.ArrayList; public class ArrayListTest { /** * @param args */ public