1、ArrayList-底层使用数组实现

  1. /**
  2. * 默认初始容量大小为10
  3. */
  4. private static final int DEFAULT_CAPACITY = 10;
  5. /**
  6. * 如果自定义容量为0,则会默认用它来初始化ArrayList,或者用于空数组替换
  7. */
  8. private static final Object[] EMPTY_ELEMENTDATA = {};
  9. /**
  10. * 如果没有自定义容量,则会使用它来初始化ArrayList,或者用于空数组比对。
  11. */
  12. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  13. /**
  14. * 实际ArrayList的大小/集合中元素的个数
  15. */
  16. private int size;
  17. /**
  18. 存储ArrayList元素的数组缓冲区。ArrayList的容量是这个数组缓冲区的长度。任何elementData==DEFAULTCAPACITY_empty_elementData的空ArrayList,当添加第一个元素时,将扩展到默认的容量。
  19. */
  20. transient Object[] elementData;

2、ArrayList-构造方法

  1. 1ArrayList提供了三种方式的构造器:
  2. /**
  3. * 构造一个初始容量为10的空列表。
  4. */
  5. public ArrayList() {
  6. //如果没有传入初始化容量,则使用空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  7. //使用这个数组是在添加第一个元素的时候就会扩容到默认大小10
  8. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  9. }
  10. /**
  11. * 构造具有指定初始容量的空列表。
  12. * 这就是ArrayList底层用到的数组
  13. * 非私有,用于简化嵌套类访问
  14. * transient 在已经实现序列化的类中,不允许某变量序列化(让某些被修饰的变量不参加序列化。)。
  15. *
  16. * @param 列表的初始容量
  17. * @throws 如果指定的初始容量为负数,则为IllegalArgumentException
  18. */
  19. public ArrayList(int initialCapacity) {
  20. //如果传入的初始容量大于0,则新建一个数组来存储元素
  21. if (initialCapacity > 0) {
  22. this.elementData = new Object[initialCapacity];
  23. } else if (initialCapacity == 0) {
  24. //如果传入的初始容量等于0,则使用空数组EMPTY_ELEMENTDATA
  25. this.elementData = EMPTY_ELEMENTDATA;
  26. } else {
  27. throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
  28. }
  29. }
  30. /**
  31. * 构造一个包含指定集合元素的列表,其顺序由集合的迭代器返回
  32. *
  33. * @param 要将其元素放入此列表中的集合
  34. * @throws 如果指定的集合为null,则发生NullPointerException
  35. */
  36. public ArrayList(Collection<? extends E> c) {
  37. //.toArray();集合转数组
  38. elementData = c.toArray();
  39. if ((size = elementData.length) != 0) {
  40. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  41. //检查c.toArray()返回值是不是Object[]类型,如果不是,重新拷贝成Object[].class类型
  42. if (elementData.getClass() != Object[].class)
  43. elementData = Arrays.copyOf(elementData, size, Object[].class);
  44. } else {
  45. // replace with empty array.
  46. //如果c是空集合,则初始化为空数组EMPTY_ELEMENTDATA
  47. this.elementData = EMPTY_ELEMENTDATA;
  48. }
  49. }

3、ArrayList-存储元素方法

  1. 1ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。
  2. /**
  3. * 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。
  4. *
  5. * @param 要替换的元素的索引
  6. * @param 要存储在指定位置的元素元素
  7. * @return 以前位于指定位置的元素
  8. * @throws 数组下标越界异常
  9. */
  10. public E set(int index, E element) {
  11. rangeCheck(index);
  12. E oldValue = elementData(index);
  13. elementData[index] = element;
  14. return oldValue;
  15. }
  16. /**
  17. * 将指定的元素追加到此列表的末尾。时间复杂度为O(1)
  18. *
  19. * @param 要附加到此列表的元素
  20. * @return 元素添加成功,返回true
  21. */
  22. public boolean add(E e) {
  23. //检查是否需要扩容
  24. //size+1???
  25. //1.如果集合添加元素成功后,集合中的实际元素个数。 2.为了确保扩容不会出现错误。
  26. //如果默认大小是0,则0 + 0 » 1还是0。 如果size是1,则1 + 1 » 1还是1
  27. //jdk1.8版本以后,ArrayList中的扩容放在add()方法中。之前放在构造方法中。
  28. //默认所以ArrayList arrayList = new ArrayList();size应该是0。所以,size+ 1对扩容来讲很必要。
  29. ensureCapacityInternal(size + 1); // Increments modCount!!
  30. elementData[size++] = e;
  31. return true;
  32. }
  33. /**
  34. * 在此列表的指定位置插入指定的元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(在其索引中添加一个元素)
  35. * 时间复杂度O(n)
  36. * @param 要插入指定元素的索引
  37. * @param 要插入的元素
  38. * @throws 数组下标越界异常
  39. */
  40. public void add(int index, E element) {
  41. //检查是否越界
  42. rangeCheckForAdd(index);
  43. //如果数组长度不足,将进行扩容。
  44. ensureCapacityInternal(size + 1); // Increments modCount!!
  45. //.arraycopy():
  46. // 将 elementData中从Index位置开始、长度为size-index的元素,
  47. // 拷贝到从下标为index+1位置开始的新的elementData数组中。
  48. // 即将当前位于该位置的元素以及所有后续元素右移一个位置。
  49. System.arraycopy(elementData, index, elementData, index + 1,
  50. size - index);
  51. //将元素插入到index的位置
  52. elementData[index] = element;
  53. size++;
  54. }
  55. /**
  56. * .arraycopy();
  57. 1.Object src:原数组
  58. 2.int srcPos:从元数据的起始位置开始
  59. 3.Object dest:目标数组
  60. 4.int destPos:目标数组的开始起始位置
  61. 5.int length:要复制的数组的长度
  62. */
  63. public static native void arraycopy(Object src, int srcPos,
  64. Object dest, int destPos,
  65. int length);
  66. /**
  67. * 将指定集合中的所有元素追加到此列表,按照指定集合的迭代器。
  68. * @param 包含要添加到此列表的元素的c集合
  69. * @return 添加成功则返回true
  70. * @throws 如果追加的集合是空的,则抛出空指针异常
  71. */
  72. public boolean addAll(Collection<? extends E> c) {
  73. //将集合转为数组
  74. Object[] a = c.toArray();
  75. int numNew = a.length;
  76. ensureCapacityInternal(size + numNew); // Increments modCount
  77. System.arraycopy(a, 0, elementData, size, numNew);
  78. size += numNew;
  79. //如果c不为空就返回true,否则返回false。
  80. return numNew != 0;
  81. }
  82. /**
  83. * 从指定的位置开始,将指定collection中的所有元素插入到此列表中。
  84. *
  85. * @param 从指定集合插入第一个元素的索引
  86. * @param 包含要添加到此列表的元素的c集合
  87. * @return 添加成功则返回true
  88. * @throws 数组下标越界异常
  89. * @throws 如果追加的集合是空的,则抛出空指针异常
  90. */
  91. public boolean addAll(int index, Collection<? extends E> c) {
  92. rangeCheckForAdd(index);
  93. Object[] a = c.toArray();
  94. int numNew = a.length;
  95. ensureCapacityInternal(size + numNew); // Increments modCount
  96. int numMoved = size - index;
  97. if (numMoved > 0)
  98. System.arraycopy(elementData, index, elementData, index + numNew,
  99. numMoved);
  100. System.arraycopy(a, 0, elementData, index, numNew);
  101. size += numNew;
  102. return numNew != 0;
  103. }

4、ArrayList-获取元素方法

  1. 1ArrayList提供了get(int index);获取元素的方法。
  2. //返回此列表中指定位置上的元素。 时间复杂度O(1)
  3. public E get(int index) {
  4. //检查元素是否越界
  5. rangeCheck(index);
  6. checkForComodification();
  7. //返回数组index位置的元素
  8. return ArrayList.this.elementData(offset + index);
  9. }

5、ArrayList-删除元素方法

  1. 1ArrayList提供了根据下标或者指定对象两种方式的删除功能。
  2. 2、[!!!]从数组中移除元素的操作,也会导致被移除的元素以后的所有元素的向左移动一个位置。
  3. /**
  4. * 移除此列表中指定位置上的元素。 时间复杂度为O(n)
  5. *
  6. * @param 索引要删除的元素的索引
  7. * @return 从列表中删除的元素
  8. * @throws 数组下标越界异常
  9. */
  10. public E remove(int index) {
  11. rangeCheck(index);
  12. modCount++;
  13. E oldValue = elementData(index);
  14. // 如果index不是最后一位,则将index之后的元素往前挪一位
  15. int numMoved = size - index - 1;
  16. if (numMoved > 0)
  17. System.arraycopy(elementData, index+1, elementData, index,
  18. numMoved);
  19. // 将最后一个元素删除,帮助GC
  20. elementData[--size] = null; // clear to let GC do its work
  21. // 返回旧值
  22. return oldValue;
  23. }
  24. /**
  25. * 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。
  26. * 时间复杂度为O(n)
  27. */
  28. public boolean remove(Object o) {
  29. //由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
  30. if (o == null) {
  31. for (int index = 0; index < size; index++)
  32. // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除
  33. if (elementData[index] == null) {
  34. fastRemove(index);
  35. return true;
  36. }
  37. } else {
  38. // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除
  39. for (int index = 0; index < size; index++)
  40. if (o.equals(elementData[index])) {
  41. //.fastRemove(index)快速删除相对于remove(int index)少了检查索引越界的操作。
  42. fastRemove(index);
  43. return true;
  44. }
  45. }
  46. return false;
  47. }
  48. private void fastRemove(int index) {
  49. modCount++;
  50. // 如果index不是最后一位,则将index之后的元素往前挪一位
  51. int numMoved = size - index - 1;
  52. if (numMoved > 0)
  53. System.arraycopy(elementData, index+1, elementData, index,
  54. numMoved);
  55. // 将最后一个元素删除,帮助GC
  56. elementData[--size] = null; // clear to let GC do its work
  57. }

6、ArrayList-数组容量调整方法

  1. 1、每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。
  2. 2、数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。
  3. 3、[!!!]在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。
  4. /**
  5. * 增加这个<tt>ArrayList</tt>实例的容量
  6. * minCapacity = size+1
  7. * @param 期望的最小容量
  8. */
  9. public void ensureCapacity(int minCapacity) {
  10. //如果元素数据不为空,则初始化为0,否则为默认初始容量大小:为10
  11. int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
  12. // any size if not default element table
  13. ? 0
  14. // larger than default for default empty table. It's already
  15. // supposed to be at default size.
  16. : DEFAULT_CAPACITY;
  17. //比较期望的最小容量和最小扩容值,来判断是否需要进行计算扩容
  18. if (minCapacity > minExpand) {
  19. ensureExplicitCapacity(minCapacity);
  20. }
  21. }
  22. // 存储元素相关方法中调用该方法,比如:.add()中
  23. // minCapacity = size+1
  24. // 确保内部容量
  25. private void ensureCapacityInternal(int minCapacity) {
  26. //如果是空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则初始化为默认大小10.
  27. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  28. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  29. }
  30. //确保许可容量
  31. ensureExplicitCapacity(minCapacity);
  32. }
  33. //确保许可容量
  34. private void ensureExplicitCapacity(int minCapacity) {
  35. //modCount不用理它,它用来计算修改次数
  36. //ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。
  37. modCount++;
  38. // overflow-conscious code
  39. if (minCapacity - elementData.length > 0)
  40. //扩容,增加容量
  41. grow(minCapacity);
  42. }
  43. /**
  44. * 增加容量,以确保它至少可以容纳minimum capacity参数指定的元素数。
  45. * minCapacity=size+1
  46. * 假如不size+1处理,如果默认大小是0,则0 + 0 » 1还是0。 如果size是1,则1 + 1 » 1还是1。
  47. * 默认所以ArrayList arrayList = new ArrayList();size应该是0。因为size在只会调用add()方法时才会自增。
  48. * @param 最小容量所需的最小容量
  49. */
  50. private void grow(int minCapacity) {
  51. // overflow-conscious code
  52. //计算元素数据的大小
  53. int oldCapacity = elementData.length;
  54. //默认扩容1.5倍
  55. //表示将oldCapacity右移一位(位运算),相当于除2.再加上1,相当于新容量扩容1.5倍。
  56. int newCapacity = oldCapacity + (oldCapacity >> 1);
  57. //如果新容量小于需要的容量,则以需要的容量为准。
  58. if (newCapacity - minCapacity < 0)
  59. newCapacity = minCapacity;
  60. //如果新容量比最大值还要大,则将新容量赋值为最大值。
  61. if (newCapacity - MAX_ARRAY_SIZE > 0)
  62. newCapacity = hugeCapacity(minCapacity);
  63. // minCapacity is usually close to size, so this is a win:
  64. //以新容量拷贝出来一个新的数组赋值给elementData
  65. elementData = Arrays.copyOf(elementData, newCapacity);
  66. }
  67. private static int hugeCapacity(int minCapacity) {
  68. if (minCapacity < 0) // overflow
  69. throw new OutOfMemoryError();
  70. // 如果新容量大于最大容量,则新容量为 Integer.MAX_VALUE;否则为 Integer.MAX_VALUE - 8
  71. return (minCapacity > MAX_ARRAY_SIZE) ?
  72. Integer.MAX_VALUE :
  73. MAX_ARRAY_SIZE;
  74. }
  75. /**
  76. * 将这个集合实例的容量调整为列表的当前大小(将elementData的数组设置为ArrayList实际的容量,动态增长的多余容量被删除了。)。
  77. * 应用程序可以使用此操作最小化<tt>ArrayList</tt>实例的存储。
  78. * 将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。
  79. */
  80. public void trimToSize() {
  81. modCount++;
  82. if (size < elementData.length) {
  83. elementData = (size == 0)
  84. ? EMPTY_ELEMENTDATA
  85. : Arrays.copyOf(elementData, size);
  86. }
  87. }

7、ArrayList-集合运算方法

  1. 1 并集:.addAll(Collection<? extends E> c) :按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾
  2. 交集:.retainAll(Collection<?> c): 仅保留此列表中包含在指定集合中的元素。
  3. 差集:.removeAll(Collection<?> c) :从此列表中删除指定集合中包含的所有元素。
  4. 无重复并集:list2.removeAll(list1);list1.addAll(list2);
  5. 1-1、求两个集合的交集:list1.retainAll(list2);
  6. //获取两个集合的交集
  7. public boolean retainAll(Collection<?> c) {
  8. Objects.requireNonNull(c);
  9. // 调用批量删除方法,这时complement传入true,表示删除不包含在c中的元素
  10. return batchRemove(c, true);
  11. }
  12. //具体操作集合的方法,当complement为false时表示删除c中包含的元素、为true时表示删除c中不包含的元素
  13. private boolean batchRemove(Collection<?> c, boolean complement) {
  14. //获得当前对象的所有元素,final修改
  15. final Object[] elementData = this.elementData;
  16. // 使用读写两个指针同时遍历数组
  17. // 读指针每次自增1,写指针放入元素的时候才加1
  18. int r = 0, w = 0;
  19. // 设置标记位
  20. boolean modified = false;
  21. try {
  22. // 遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准)
  23. for (; r < size; r++)
  24. if (c.contains(elementData[r]) == complement)
  25. elementData[w++] = elementData[r];//存在则直接保存
  26. } finally {
  27. // 正常来说r最后是等于size的,除非c.contains()抛出了异常
  28. // Preserve behavioral compatibility with AbstractCollection,
  29. // even if c.contains() throws.
  30. if (r != size) {
  31. // 如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后
  32. System.arraycopy(elementData, r,
  33. elementData, w,
  34. size - r);
  35. w += size - r;//当前集合大小
  36. }
  37. if (w != size) {
  38. //将写指针之后的元素置为空,帮助GC
  39. // clear to let GC do its work
  40. for (int i = w; i < size; i++)
  41. elementData[i] = null;
  42. //新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置)
  43. modCount += size - w;
  44. size = w;//交集大小
  45. modified = true;
  46. }
  47. }
  48. return modified;
  49. }
  50. 1-2、求两个集合的差集:list1.removeAll(list2);
  51. //只保留当前集合中不在c中的元素,不保留在c中不在当前集体中的元素。
  52. public boolean removeAll(Collection<?> c) {
  53. // 集合c不能为空
  54. Objects.requireNonNull(c);
  55. // 同样调用批量删除方法,这时complement传入false,表示删除包含在c中的元素
  56. return batchRemove(c, false);
  57. }
  58. // 删除指定范围的元素
  59. protected void removeRange(int fromIndex, int toIndex) {
  60. modCount++;
  61. int numMoved = size - toIndex;
  62. System.arraycopy(elementData, toIndex, elementData, fromIndex,
  63. numMoved);
  64. // 同样是利用copy去操作的 计算出位置直接赋值
  65. // clear to let GC do its work
  66. int newSize = size - (toIndex-fromIndex);
  67. for (int i = newSize; i < size; i++) {
  68. elementData[i] = null;
  69. }
  70. size = newSize;
  71. }
  72. 1-3、求两个集合的并集:list1.addAll(list2);
  73. 见.addAll();方法

8、ArrayList-集合源码分析总结

  1. 1ArrayList-集合源码分析总结
  2. 1-1ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;
  3. 1-2ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);
  4. 1-3ArrayList添加元素到尾部极快,平均时间复杂度为O(1);
  5. 1-4ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);
  6. 1-5ArrayList从尾部删除元素极快,时间复杂度为O(1);
  7. 1-6ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);
  8. 1-7ArrayList支持求并集,调用addAll(Collection c)方法即可;
  9. 1-8ArrayList支持求交集,调用retainAll(Collection c)方法即可;
  10. 1-9ArrayList支持求单向差集,调用removeAll(Collection c)方法即可;