ArrayList源码分析--jdk1.8

hackerxian 2019-08-03 原文

  1. ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合。
  2. ArrayList支持随机访问、克隆、序列化,元素有序且可以重复。
  3. ArrayList初始默认长度10,超出扩容1.5倍,使用Object[]存储各种数据类型。

  数据结构是集合的精华所在,数据结构往往也限制了集合的作用和侧重点,了解各种数据结构是我们分析源码的必经之路。
  ArrayList的数据结构如下:
ArrayList源码分析--jdk1.8

  1. /*
  2. * 用数组实现的集合,支持随机访问,元素有序且可以重复
  3. * RandomAccess(ArrayList) 支持快速随机访问,使用for循环更加快速
  4. * LinkedList 使用 iterator迭代器更加 快速
  5. * RandomAccess 这是一个标记接口,一般此标记接口用于 List 实现,以表明它们支持快速(通常是恒定时间)的随机访问。
  6. * 该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或顺序访问列表时提供良好的性能
  7. * 包含类中的基础属性和3个构造方法
  8. */
  9. public class ArrayList<E> extends AbstractList<E>
  10. implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  11. {
  12. /**
  13. * 默认长度 10
  14. */
  15. private static final int DEFAULT_CAPACITY = 10;
  16. /**
  17. * 默认空的数组
  18. */
  19. private static final Object[] EMPTY_ELEMENTDATA = {};
  20. /**
  21. * ArrayList中的元素 是Object[]类型的数组
  22. */
  23. transient Object[] elementData; // non-private to simplify nested class access
  24. /**
  25. * 动态数组的实际大小 ,默认为0
  26. * @serial
  27. */
  28. private int size;
  29. /**
  30. * 最大数组容量2147483639
  31. */
  32. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  33. /**
  34. * 集合长度构造函数
  35. */
  36. public ArrayList(int initialCapacity) {
  37. super();
  38. if (initialCapacity < 0)
  39. throw new IllegalArgumentException("Illegal Capacity: "+
  40. initialCapacity);
  41. this.elementData = new Object[initialCapacity];
  42. }
  43. /**
  44. * 无参构造函数,设置元素数组为空 注意此时初始容量是0,而不是大家以为的 10
  45. */
  46. public ArrayList() {
  47. super();
  48. this.elementData = EMPTY_ELEMENTDATA;
  49. }
  50. /**
  51. * 集合参数构造函数
  52. */
  53. public ArrayList(Collection<? extends E> c) {
  54. elementData = c.toArray(); // 转化为数组
  55. size = elementData.length;
  56. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  57. if (elementData.getClass() != Object[].class) //是否成功转化为Object类型数组
  58. elementData = Arrays.copyOf(elementData, size, Object[].class); //不为Object数组的话就进行复制
  59. }

ArrayList源码分析--jdk1.8
   ArrayList extends AbstractList
   AbstractList extends AbstractCollection 
  java中所有类都继承Object,所以ArrayList的继承结构如上图。
   1. AbstractList是一个抽象类,实现了List<E>接口,List<E>定义了一些List通用方法,而AbstractList抽象类中可以有抽象方法,还可以有具体的实现方法,AbstractList实现接口中一些通用的方法,实现了基础的add/get/indexOf/iterator/subList/RandomAccessSubList方法,ArrayList再继承AbstractList,拿到通用基础的方法,然后自己在实现一些自己特有的方法,这样的好处是:让代码更简洁,继承结构最底层的类中通用的方法,减少重复代码。
   2.ArrayList实现了List<E>、RandomAccess、Cloneable、Serializable接口
     1)List<E>接口,ArrayList既然继承自AbstractList抽象类,而AbstractList已 经实现了List接口,那么ArrayList类为何还要再实现List接口呢?我们带着疑问往下看:

  1. public class Demo1 extends ArrayList {
  2. public static void main(String[] args) {
  3. //返回[]
  4. System.out.println(Arrays.toString(Demo1.class.getInterfaces()));
  5. }
  6. public class Demo2 implements Serializable {
  7. public static void main(String[] args) {
  8. //返回[interface java.io.Serializable]
  9. System.out.println(Arrays.toString(Demo2.class.getInterfaces()));
  10. }
  11. public class Test{
  12. public static void main(String[] args) {
  13. Serializable c1 = new Demo1();//未显示实现接口
  14. Serializable c2 = new Demo2();//显示实现接口
  15. Serializable proxy2 = createProxy(c2);
  16. proxy2.foo();
  17. Serializable proxy1 = createProxy(c1);
  18. proxy1.foo();
  19. }
  20. private static <T> T createProxy(final T obj) {
  21. final InvocationHandler handler = new InvocationHandler() {
  22. @Override
  23. public Object invoke(Object proxy, Method method, Object[] args)
  24. throws Throwable {
  25. return method.invoke(obj, args);
  26. }
  27. };
  28. //实现接口代理,Demo1报错,Demo2成功
  29. //java.lang.ClassCastException: $Proxy1 cannot be cast to
  30. //example.Test$Serializable
  31. return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
  32. .getClass().getInterfaces(), handler);
  33. }

可以看出这样这样设计是有道理的,因此,这并不是一个错误,很可能是作者Josh Bloch为了便于实现代理而精心设计的。
参考与:开发collection 的作者Josh说
     2)RandomAccess接口,这是一个标记接口,一般此标记接口用于 List 实现,以表明它们支持快速(通常是恒定时间)的随机访问,该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或顺序访问列表时提供良好的性能,实现了该接口的话使用普通的for循环来遍历,性能更高,而没有实现该接口的话,使用Iterator来迭代,这样性能更高,例如linkedList。所以这个标记性只是为了让我们知道我们用什么样的方式去获取数据性能更好
     3)Cloneable接口,可以使用Object.Clone()方法。
     4)Serializable接口,序列化接口,表明该类可以被序列化,什么是序列化?简单的说,就是能够从类变成字节流传输,反序列化,就是从字节流变成原来的类

ArrayList源码分析--jdk1.8

     1)add(E);//默认直接在末尾添加元素

  1. /**
  2. * 新增元素
  3. */
  4. public boolean add(E e) {
  5. //赋值初始长度 或者扩容,新增元素,当前实际size+1的长度
  6. ensureCapacityInternal(size + 1); // Increments modCount!!
  7. //添加元素
  8. elementData[size++] = e;
  9. return true;
  10. }
  11. /**
  12. * 确保elemenData数组有合适的大小
  13. * 如果元素为空,则复制长度默认为10 或者更大
  14. * @author jiaxiaoxian
  15. * @date 2019年2月12日
  16. */
  17. private void ensureCapacityInternal(int minCapacity) {
  18. if (elementData == EMPTY_ELEMENTDATA) {//如果数组为空,则从size+1的值和默认值10中取最大的
  19. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  20. }
  21. ensureExplicitCapacity(minCapacity);
  22. }
  23. /**
  24. * 确保elemenData数组有合适的大小
  25. * @author jiaxiaoxian
  26. * @date 2019年2月12日
  27. * 如果长度大于元素长度则扩容
  28. */
  29. private void ensureExplicitCapacity(int minCapacity) {
  30. //记录修改次数,迭代中不一致会触发fail-fast机制,因此在遍历中删除元素的正确做法应该是使用Iterator.remove()
  31. modCount++;
  32. if (minCapacity - elementData.length > 0)
  33. grow(minCapacity); //扩容
  34. }
  35. /**
  36. * 扩容
  37. */
  38. private void grow(int minCapacity) {
  39. int oldCapacity = elementData.length; // 旧容量
  40. int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
  41. if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
  42. newCapacity = minCapacity;
  43. if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
  44. newCapacity = hugeCapacity(minCapacity); // 指定新容量
  45. // minCapacity is usually close to size, so this is a win: 拷贝扩容
  46. elementData = Arrays.copyOf(elementData, newCapacity);
  47. }
  48. //如果小于0 就报错,如果大于最大值 则取最大值
  49. private static int hugeCapacity(int minCapacity) {
  50. if (minCapacity < 0) // overflow
  51. throw new OutOfMemoryError();
  52. return (minCapacity > MAX_ARRAY_SIZE) ?
  53. Integer.MAX_VALUE :
  54. MAX_ARRAY_SIZE;
  55. }

     2)add(int index, E element);//给指定下标,添加元素

  1. /**
  2. * 给指定下标,添加元素
  3. */
  4. public void add(int index, E element) {
  5. //判断下标是否越界
  6. rangeCheckForAdd(index);
  7. //赋值初始长度 或者扩容
  8. ensureCapacityInternal(size + 1); // Increments modCount!!
  9. //将源数组中从index位置开始后的size-index个元素统一后移一位
  10. System.arraycopy(elementData, index, elementData, index + 1,
  11. size - index);
  12. //赋值
  13. elementData[index] = element;
  14. size++;
  15. }
  16. /**
  17. * 判断下标是否越界
  18. */
  19. private void rangeCheckForAdd(int index) {
  20. if (index > size || index < 0)
  21. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  22. }
  23. /**
  24. * src:源数组
  25. *   srcPos:源数组要复制的起始位置
  26. *   dest:目的数组
  27. *   destPos:目的数组放置的起始位置
  28. *   length:复制的长度
  29. *   注意:src 和 dest都必须是同类型或者可以进行转换类型的数组
  30. */
  31. public static native void arraycopy(Object src, int srcPos,
  32. Object dest, int destPos,
  33. int length);

     3)addAll(Collection<? extends E> c);//添加Collection类型元素

  1. /**
  2. * 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部
  3. */
  4. public boolean addAll(Collection<? extends E> c) {
  5. Object[] a = c.toArray();
  6. int numNew = a.length;
  7. ensureCapacityInternal(size + numNew); // Increments modCount
  8. //将数组a[0,...,numNew-1]复制到数组elementData[size,...,size+numNew-1]
  9. System.arraycopy(a, 0, elementData, size, numNew);
  10. size += numNew;
  11. return numNew != 0;
  12. }

     4)addAll(int index, Collection<? extends E> c);//指定位置,添加Collection类型元素

  1. /**
  2. * 从指定的位置开始,将指定collection中的所有元素插入到此列表中,新元素的顺序为指定collection的迭代器所返回的元素顺序
  3. */
  4. public boolean addAll(int index, Collection<? extends E> c) {
  5. //判断下标是否越界
  6. rangeCheckForAdd(index);
  7. Object[] a = c.toArray();
  8. int numNew = a.length;
  9. ensureCapacityInternal(size + numNew); // Increments modCount
  10. int numMoved = size - index;
  11. //先将数组elementData[index,...,index+numMoved-1]复制到elementData[index+numMoved,...,index+2*numMoved-1]
  12. //即,将源数组中从index位置开始的后numMoved个元素统一后移numNew位
  13. if (numMoved > 0)
  14. System.arraycopy(elementData, index, elementData, index + numNew,
  15. numMoved);
  16. System.arraycopy(a, 0, elementData, index, numNew);
  17. size += numNew;
  18. return numNew != 0;
  19. }

总结:
   正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。
   

ArrayList源码分析--jdk1.8

     1)remove(int index); //根据指定下标 删除元素     

  1. /**
  2. * 根据指定下标 删除元素
  3. */
  4. public E remove(int index) {
  5. //判断索引是否越界
  6. rangeCheck(index);
  7. modCount++;
  8. //获取旧元素
  9. E oldValue = elementData(index);
  10. //将数组elementData中index位置之后的所有元素向前移一位
  11. int numMoved = size - index - 1;
  12. if (numMoved > 0)
  13. System.arraycopy(elementData, index+1, elementData, index,
  14. numMoved);
  15. //将原数组最后一个位置置为null,由GC清理
  16. elementData[--size] = null; // clear to let GC do its work
  17. return oldValue;
  18. }    

     2)remove(Object o); //根据指定元素 删除元素 

  1. /**
  2. * 移除ArrayList中首次出现的指定元素(如果存在),ArrayList中允许存放重复的元素
  3. */
  4. public boolean remove(Object o) {
  5. // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。
  6. if (o == null) {
  7. for (int index = 0; index < size; index++)
  8. if (elementData[index] == null) {
  9. //私有的移除方法,跳过index参数的边界检查以及不返回任何值
  10. fastRemove(index);
  11. return true;
  12. }
  13. } else {
  14. for (int index = 0; index < size; index++)
  15. if (o.equals(elementData[index])) {
  16. fastRemove(index);
  17. return true;
  18. }
  19. }
  20. return false;
  21. } 
  22. /*
  23. * 根据下标快速删除元素
  24. */
  25. private void fastRemove(int index) {
  26. modCount++;
  27. //将数组elementData中index位置之后的所有元素向前移一位
  28. int numMoved = size - index - 1;
  29. if (numMoved > 0)
  30. System.arraycopy(elementData, index+1, elementData, index,
  31. numMoved);
  32. elementData[--size] = null; // clear to let GC do its work
  33. }
  34. /**
  35. * 清空ArrayList,将全部的元素设为null,等待垃圾回收将这个给回收掉,所以叫clear
  36. */
  37. public void clear() {
  38. modCount++;
  39. // clear to let GC do its work
  40. for (int i = 0; i < size; i++)
  41. elementData[i] = null;
  42. size = 0;
  43. }

     3)removeAll(Collection<?> c); //删除包含在指定容器c中的所有元素 

  1. /**
  2. * 删除ArrayList中包含在指定容器c中的所有元素
  3. */
  4. public boolean removeAll(Collection<?> c) {
  5. //检查指定的对象c是否为空
  6. Objects.requireNonNull(c);
  7. return batchRemove(c, false);
  8. }
  9. /**
  10. * 删除全部
  11. * @author jiaxiaoxian
  12. * @date 2019年2月12日
  13. */
  14. private boolean batchRemove(Collection<?> c, boolean complement) {
  15. final Object[] elementData = this.elementData;
  16. int r = 0, w = 0; //读写双指针
  17. boolean modified = false;
  18. try {
  19. for (; r < size; r++)
  20. if (c.contains(elementData[r]) == complement) //判断指定容器c中是否含有elementData[r]元素
  21. elementData[w++] = elementData[r];
  22. } finally {
  23. // Preserve behavioral compatibility with AbstractCollection,
  24. // even if c.contains() throws.
  25. if (r != size) {
  26. System.arraycopy(elementData, r,
  27. elementData, w,
  28. size - r);
  29. w += size - r;
  30. }
  31. if (w != size) {
  32. // clear to let GC do its work
  33. for (int i = w; i < size; i++)
  34. elementData[i] = null;
  35. modCount += size - w;
  36. size = w;
  37. modified = true;
  38. }
  39. }
  40. return modified;
  41. }

     4)removeIf(Predicate<? super E> filter); //按照一定规则过滤(删除)集合中的元素 

  1. /**
  2. * 按照一定规则过滤(删除)集合中的元素
  3. * 如:idList.removeIf(id -> id == nul);
  4. * 去掉 List idList 集合中id 为 null 的
  5. * @param filter
  6. * @return
  7. */
  8. @Override
  9. public boolean removeIf(Predicate<? super E> filter) {
  10. Objects.requireNonNull(filter);
  11. // figure out which elements are to be removed
  12. // any exception thrown from the filter predicate at this stage
  13. // will leave the collection unmodified
  14. int removeCount = 0;
  15. final BitSet removeSet = new BitSet(size);
  16. final int expectedModCount = modCount;
  17. final int size = this.size;
  18. for (int i=0; modCount == expectedModCount && i < size; i++) {
  19. @SuppressWarnings("unchecked")
  20. final E element = (E) elementData[i];
  21. if (filter.test(element)) {
  22. removeSet.set(i);
  23. removeCount++;
  24. }
  25. }
  26. if (modCount != expectedModCount) {
  27. throw new ConcurrentModificationException();
  28. }
  29. // shift surviving elements left over the spaces left by removed elements
  30. final boolean anyToRemove = removeCount > 0;
  31. if (anyToRemove) {
  32. final int newSize = size - removeCount;
  33. for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
  34. i = removeSet.nextClearBit(i);
  35. elementData[j] = elementData[i];
  36. }
  37. for (int k=newSize; k < size; k++) {
  38. elementData[k] = null; // Let gc do its work
  39. }
  40. this.size = newSize;
  41. if (modCount != expectedModCount) {
  42. throw new ConcurrentModificationException();
  43. }
  44. modCount++;
  45. }
  46. return anyToRemove;
  47. }

总结:
   remove函数用户移除指定下标的元素,此时会把指定下标到数组末尾的元素向前移动一个单位,并且会把数组最后一个元素设置为null,这样是为了方便之后将整个数组不被使用时,会被GC,可以作为小的技巧使用。

  1. /**
  2. * 覆盖指定下标元素
  3. */
  4. public E set(int index, E element) {
  5. //判断索引是否越界
  6. rangeCheck(index);
  7. //获取旧元素
  8. E oldValue = elementData(index);
  9. //覆盖为新元素
  10. elementData[index] = element;
  11. //返回旧元素
  12. return oldValue;
  13. }
  14. /**
  15. * 判断下标是否越界
  16. */
  17. private void rangeCheck(int index) {
  18. if (index >= size)
  19. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  20. }
  1. /**
  2. * 返回指定索引的值
  3. */
  4. public E get(int index) {
  5. //判断索引是否越界
  6. rangeCheck(index);
  7. return elementData(index);
  8. }
  9. /**
  10. * @author jiaxiaoxian
  11. * @date 2019年2月12日
  12. * 返回下标元素的 值
  13. */
  14. @SuppressWarnings("unchecked")
  15. E elementData(int index) {
  16. return (E) elementData[index];
  17. }
  1. /**
  2. * 查找下标, 如果为null,直接和null比较,返回下标
  3. */
  4. public int indexOf(Object o) {
  5. if (o == null) {
  6. for (int i = 0; i < size; i++)
  7. if (elementData[i]==null)
  8. return i;
  9. } else {
  10. for (int i = 0; i < size; i++)
  11. if (o.equals(elementData[i]))
  12. return i;
  13. }
  14. return -1;
  15. }
  16. /**
  17. * 查找最后出现的下标,从大往下循环查找
  18. */
  19. public int lastIndexOf(Object o) {
  20. if (o == null) {
  21. for (int i = size-1; i >= 0; i--)
  22. if (elementData[i]==null)
  23. return i;
  24. } else {
  25. for (int i = size-1; i >= 0; i--)
  26. if (o.equals(elementData[i]))
  27. return i;
  28. }
  29. return -1;
  30. }
  1. /**
  2. * 复制,返回此ArrayList 的浅拷贝
  3. */
  4. public Object clone() {
  5. try {
  6. ArrayList<?> v = (ArrayList<?>) super.clone();
  7. v.elementData = Arrays.copyOf(elementData, size);
  8. v.modCount = 0;
  9. return v;
  10. } catch (CloneNotSupportedException e) {
  11. // this shouldn't happen, since we are Cloneable
  12. throw new InternalError(e);
  13. }
  14. }
  1. /**
  2. * 判断数据实际容量大小,删除自动增长后冗余的容量
  3. * 该方法用于回收多余的内存。也就是说一旦我们确定集合不在添加多余的元素之后,调用 trimToSize() 方法会将实现集合的数组大小刚好调整为集合元素的大小。
  4. *   注意:该方法会花时间来复制数组元素,所以应该在确定不会添加元素之后在调用
  5. */
  6. public void trimToSize() {
  7. modCount++;
  8. if (size < elementData.length) {
  9. elementData = Arrays.copyOf(elementData, size);
  10. }
  11. }
  1. 1arrayList可以存放null,本质是Object[]类型的数组。
  2. 2arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
  3. 3arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果,而LinkedList则相反。
  4. 4arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。
  5. 5)初始化数组时推荐给初始长度,反复扩容会增加时耗,影响性能效率。
 

版权声明:本文为hackerxian原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/hackerxian/p/11295794.html

ArrayList源码分析--jdk1.8的更多相关文章

  1. Java并发编程实战(5)- 线程生命周期

    在这篇文章中,我们来聊一下线程的生命周期。 在这篇文章中,我们来聊一下线程的生命周期。 目录 概述 操作系统中 […]...

  2. 8年经验面试官详解 Java 面试秘诀

      作者 | 胡书敏 责编 | 刘静 出品 | CSDN(ID:CSDNnews) 本人目前在一家知名外企担任 […]...

  3. java json转换工具类

    在java项目中,通常会用到json类型的转换,常常需要对 json字符串和对象进行相互转换。 在制作自定义的 […]...

  4. java 之 适配器模式(大话设计模式)

    适配器模式,笔者不是很推荐在项目初期阶段使用,在笔者看来这个设计模式就是套接了一层,从而达到能够迎合现有的外部 […]...

  5. java eclipse 程序没错,运行结果显示无法加载主类的解决方法 – backyyan

    java eclipse 程序没错,运行结果显示无法加载主类的解决方法 2017-07-05 21:04  b […]...

  6. HDFS 06 – HDFS 常用的 Java API 操作

    通过 HDFS 的 Java API,读写 HDFS 中的文件,包括创建目录、写文件、上传下载文件,遍历文件, […]...

  7. 用非常硬核的JAVA序列化手段实现对象流的持久化保存

    目录 背景 对象流的概念 对象流实例 引入一张组织结构图 定义组织架构图的类 类的完整结构 用对象流保存组织架 […]...

  8. Java 从数组来看值传递和引用传递

    从数组来看值传递和引用传递 惯例先看一段代码 public class DemoCollection14 { […]...

随机推荐

  1. 基于C#的机器学习–模糊逻辑-穿越障碍

      模糊逻辑-穿越障碍 模糊逻辑。另一个我们经常听到的术语。但它的真正含义是什么?它是否意味着不止一件事?我们 […]...

  2. Android布局中layout_gravity和gravity的区别 – 难忘理想

    Android布局中layout_gravity和gravity的区别 在Android开发过程中,写布局文件 […]...

  3. HNOI 2006 公路修建问题

    洛谷 P2323 [HNOI2006]公路修建问题 https://www.luogu.org/problem […]...

  4. MATLAB实现频数表——hist的使用 – 铁树银花

    MATLAB实现频数表——hist的使用 借助命令hist,matlab可以通过两个方式实现频数表。 1.[f […]...

  5. Windows恢复环境启动失败,重新配置WinRE

    前言 现在很多朋友追求系统镜像体积缩小,往往删除了系统镜像中C:\Windows\System32\Recov […]...

  6. 数据结构学习总结(一)

    首先数据结构分为逻辑结构和物理结构,那么下面我们就来分别总结逻辑结构与物理结构   首先是逻辑结构,逻辑结构实 […]...

  7. 盘点Linux运维常用工具(一)-web篇之httpd

    #前言:想把自己学的各种服务进行分类归档起来,于是就写了盘点Linux运维常用工具,Linux方面使用到的we […]...

  8. java 生成Excel开门篇

    本随笔的Excel所用的poi jar包(3.17版本)链接: https://pan.baidu.com/s […]...

展开目录

目录导航