当前位置:   article > 正文

Java容器深入研究(jdk 1.8)--- ArrayList总结与源码分析_java 容器 扩容

java 容器 扩容

结构: public class ArrayList<E> extends AbstractList<E>  

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

继承自 AbstractList<E> ,这是一个抽象类对一些基础的list操作做了一些封装,实现了RandomAccess 标记接口,表明可以实现快速随机访问,Cloneable接口的实现表示该容器具有Clone函数操作,Serializable是序列化。

内部存储结构:  

  1. transient Object[] elementData; //transient关键字修饰的一个Object数组,为什么会用到transient后面再解释;
  2. private int size;
构造方法:

  1. //构造一个指定容量的ArrayList
  2. public ArrayList(int initialCapacity) {
  3. if (initialCapacity > 0) {
  4. this.elementData = new Object[initialCapacity];
  5. } else if (initialCapacity == 0) {
  6. this.elementData = EMPTY_ELEMENTDATA;
  7. } else {
  8. throw new IllegalArgumentException("Illegal Capacity: "+
  9. initialCapacity);
  10. }
  11. }
  12. //构造一个默认的空ArrayList,这里并没有初始化,jdk 1.8之后是在进行add操作后初始化
  13. public ArrayList() {
  14. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  15. }
  16. //构造一个具有指定元素的ArrayList
  17. public ArrayList(Collection<? extends E> c) {
  18. elementData = c.toArray();
  19. if ((size = elementData.length) != 0) {
  20. // c.toArray might (incorrectly) not return Object[] (see 6260652)
  21. if (elementData.getClass() != Object[].class)
  22. elementData = Arrays.copyOf(elementData, size, Object[].class);
  23. } else {
  24. // replace with empty array.
  25. this.elementData = EMPTY_ELEMENTDATA;
  26. }
  27. }

增加操作:

  1. /**
  2. * 在数组末尾加上一个元素
  3. */
  4. public boolean add(E e) {
  5. ensureCapacityInternal(size + 1);
  6. elementData[size++] = e;
  7. return true;
  8. }
  9. /**
  10. * 在某个位置加入一个元素element
  11. * @param index
  12. * @param element
  13. */
  14. public void add(int index, E element) {
  15. //检查index是否越界
  16. rangeCheckForAdd(index);
  17. //进行扩容检查
  18. ensureCapacityInternal(size + 1); // Increments modCount!!
  19. //对数据进行复制操作,空出index位置,并插入element,后移index后面的元素
  20. System.arraycopy(elementData, index, elementData, index + 1,
  21. size - index);
  22. elementData[index] = element;
  23. size++;
  24. }
扩容检查:

  1. /**
  2. * 这个扩容方法,内部没有调用,判断ArrayList是否为空
  3. * @param minCapacity
  4. */
  5. public void ensureCapacity(int minCapacity) {
  6. int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
  7. ? 0
  8. : DEFAULT_CAPACITY;
  9. if (minCapacity > minExpand) {
  10. ensureExplicitCapacity(minCapacity);
  11. }
  12. }
  13. /**
  14. * 扩容检查
  15. * @param minCapacity
  16. */
  17. private void ensureCapacityInternal(int minCapacity) {
  18. //第一次add操作初始化,如果为空ArrayList,那么初始化容量为10
  19. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  20. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  21. }
  22. //判断是否需要扩容
  23. ensureExplicitCapacity(minCapacity);
  24. }
  25. /**
  26. * 判断是否需要扩容
  27. * @param minCapacity
  28. */
  29. private void ensureExplicitCapacity(int minCapacity) {
  30. //modCount这个参数运用到了 fail-fast 机制,后面解释
  31. modCount++;
  32. //扩容
  33. if (minCapacity - elementData.length > 0)
  34. grow(minCapacity);
  35. }
  36. //最大容量
  37. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  38. /**
  39. * 将整个数组size扩容为1.5倍
  40. */
  41. private void grow(int minCapacity) {
  42. int oldCapacity = elementData.length;
  43. //newCapacity为以前的1.5倍
  44. int newCapacity = oldCapacity + (oldCapacity >> 1);
  45. if (newCapacity - minCapacity < 0)
  46. newCapacity = minCapacity;
  47. //判断容量是否到达long int 最大临界值
  48. if (newCapacity - MAX_ARRAY_SIZE > 0)
  49. newCapacity = hugeCapacity(minCapacity);
  50. // 对数组进行复制处理
  51. elementData = Arrays.copyOf(elementData, newCapacity);
  52. }
  53. //检查是否超过最大容量 0x7fffffff ,是否抛出异常
  54. private static int hugeCapacity(int minCapacity) {
  55. if (minCapacity < 0) // overflow
  56. throw new OutOfMemoryError();
  57. return (minCapacity > MAX_ARRAY_SIZE) ?
  58. Integer.MAX_VALUE :
  59. MAX_ARRAY_SIZE;
  60. }
这里ArrayList扩充自己的容量是,扩充为以前的1.5倍

删除元素:

  1. /**
  2. * 根据索引删除元素
  3. * @param index
  4. * @return
  5. */
  6. public E remove(int index) {
  7. //数组越界检查
  8. rangeCheck(index);
  9. modCount++;
  10. E oldValue = elementData(index);
  11. //计算数组需要复制的数量
  12. int numMoved = size - index - 1;
  13. //将index后的数据都向前移一位
  14. if (numMoved > 0)
  15. System.arraycopy(elementData, index+1, elementData, index,
  16. numMoved);
  17. //let GC
  18. elementData[--size] = null;
  19. return oldValue;
  20. }
  21. /**
  22. * 根据元素内容匹配并删除,只删除第一个匹配成功的
  23. * @param o
  24. * @return
  25. */
  26. public boolean remove(Object o) {
  27. if (o == null) {
  28. for (int index = 0; index < size; index++)
  29. if (elementData[index] == null) {
  30. fastRemove(index);
  31. return true;
  32. }
  33. } else {
  34. for (int index = 0; index < size; index++)
  35. if (o.equals(elementData[index])) {
  36. fastRemove(index);
  37. return true;
  38. }
  39. }
  40. return false;
  41. }
  42. //找到对应的元素后,删除。删除元素后的元素都向前移动一位
  43. private void fastRemove(int index) {
  44. modCount++;
  45. int numMoved = size - index - 1;
  46. if (numMoved > 0)
  47. System.arraycopy(elementData, index+1, elementData, index,
  48. numMoved);
  49. elementData[--size] = null; // clear to let GC do its work
  50. }
数组节约内存空间,缩小容量的方法 :   trimToSize

  1. /**
  2. * 数组缩小容量,增加元素会进行扩容,但删除元素没有缩容,
  3. * 这个方法是用来提供缩小数组容量
  4. */
  5. public void trimToSize() {
  6. modCount++;
  7. //length是数组长度,size表示数组内元素个数
  8. //size<length那么就说明数组内有空元素,进行缩小容量操作
  9. if (size < elementData.length) {
  10. elementData = (size == 0)
  11. ? EMPTY_ELEMENTDATA
  12. : Arrays.copyOf(elementData, size);
  13. }
  14. }

现在ArrayList的增加和删除,及扩容操作都明白了吧。为什么ArrayList 不适合频繁插入和删除操作?就是因为在ArrayList中我们一直会调用 System.arraycopy 这个效率很低的操作来复制数组,所以导致ArrayList在插入和删除操作中效率不高。

在ArrayList中,还提供了 Iterator 和 ListIterator 这两种迭代器,他们有什么区别呢?

ListIterator 与普通的 Iterator相比,它增加了增、删、设定元素、向前向后遍历的操作。

在结构上面 :public interface ListIterator<E> extends Iterator<E> ,我们看看他们的结构图就会一目了然了。

                        

关于ModCount :

我们先说说 容器的“快速失败”机制,它是Java集合中的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。它是一种迭代器检测bug的机制,比如:我们有线程A和线程B,线程A通过Iterator访问集合中的元素,某个时刻,线程B修改了集合中的结构,那么程序就会抛出 ConcurrentModificationException 。

这个fail-fast机制是怎么实现的呢?

  1. final void checkForComodification() {
  2. if (modCount != expectedModCount)
  3. throw new ConcurrentModificationException();
  4. }
没错,在迭代器中的 add 、remove 、 next等方法中都调用了此方法,判断 modCount 和 expectedModCount是否相等,来决定是否抛出异常。

在迭代器中,int expectedModCount = modCount; 中定义了 expectedModCount , 这个值是不会变化的,所以使用迭代器遍历整个ArrayList时,我们只需要判断是否进行了那些会让 ModCount 改变的方法,从而知道了集合是否发生了结构上的变化。注明:在LruCahe当中,因为它的底层是一个 HashLinkedMap 实现,使用了最近最少使用算法,所以迭代器在使用 get 方法的时候,也会抛出这个异常,所以谨慎使用。

还有一个问题,为什么底层数组会有 transient 关键字修饰?貌似 HashMap底层也是这个关键字修饰的 :transient Node<K,V>[] table ?

我会单独写一篇博客来解释,transient关键字的作用,还有关于 Iterator迭代器模式。

有什么不好的地方还望指正~~~





声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/885407
推荐阅读
相关标签
  

闽ICP备14008679号