ArrayList底层是由线性数组来实现的动态数组。新建一个ArrayList时,就是在底层新建一个线性数组。ArrayList的默认初始的数组长度为10(JDK1.8);
二、关注点
1、ArrayList集合的数据是可以为空的;
2、数据有序(集合中数据的顺序和添加数据的顺序相同),从头向尾插入数据;
三、ArrayList集合API
1、新增元素
1)在集合中追加元素
1 protected transient int modCount = 0; 2 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 3 public boolean add(E e) { 4 //计数器 5 modCount++; 6 //新增元素操作 7 add(e, elementData, size); 8 return true; 9 } 10 11 private void add(E e, Object[] elementData, int s) { 12 if (s == elementData.length){ 13 //数组已满,扩展数组 14 elementData = grow(); 15 } 16 //将E放置在数组索引为S的位置 17 elementData[s] = e; 18 //将整个数组的大小加1 19 size = s + 1; 20 } 21 22 private Object[] grow() { 23 //在原始数组长度的基础上加1 24 return grow(size + 1); 25 } 26 27 private Object[] grow(int minCapacity) { 28 //获取原始数组的长度,赋值给oldCapacity 29 int oldCapacity = elementData.length; 30 //判空操作(原始数组的长度大于0,或者该数组不为空数组) 31 if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 32 int newCapacity = ArraysSupport.newLength(oldCapacity, 33 // minimum growth 按照上面代码逻辑差值是1 34 minCapacity - oldCapacity, 35 // preferred growth oldCapacity对2取余 36 oldCapacity >> 1); 37 return elementData = Arrays.copyOf(elementData, newCapacity); 38 } else { 39 //新建一个Object数组,长度为默认长度10和最小扩展量的最大值 40 return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; 41 } 42 } 43 44 //扩容算法 45 public static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; 46 public static int newLength(int oldLength, int minGrowth, int prefGrowth) { 47 //保证最小扩展量为1,因为minGrowth一直为1。若preGrowth大于minGrowth,则取preGrowth,否则为1; 48 int newLength = Math.max(minGrowth, prefGrowth) + oldLength; 49 if (newLength - MAX_ARRAY_LENGTH <= 0) { 50 //若扩展值小于MAX_ARRAY_LENGTH,则直接返回扩展值 51 return newLength; 52 } 53 //当newLength的大小(最大扩容量)大于等于Integer.MAX-8的值后,使用该方法 54 return hugeLength(oldLength, minGrowth); 55 } 56 57 private static int hugeLength(int oldLength, int minGrowth) { 58 //获取最小扩增量1 59 int minLength = oldLength + minGrowth; 60 if (minLength < 0) { // overflow 61 throw new OutOfMemoryError("Required array length too large"); 62 } 63 //原始容量+1后的值(最小扩容量)小于等于integer最大值-8 64 if (minLength <= MAX_ARRAY_LENGTH) { 65 //返回Integer的最大值-8 66 return MAX_ARRAY_LENGTH; 67 } 68 //最小扩容量大于Integre最大值-8时,直接返回Integrer的最大值 69 return Integer.MAX_VALUE; 70 }
2)在集合中指定索引位置添加元素
1 public void add(int index, E element) { 2 //判断添加的索引是否在数组中 3 rangeCheckForAdd(index); 4 modCount++; 5 final int s; 6 Object[] elementData; 7 //判断原始数组大小等于数组中元素的长度 8 if ((s = size) == (elementData = this.elementData).length){ 9 //执行扩容操作 10 elementData = grow(); 11 } 12 //数组复制,将原数组(第一个elementData)从index的索引位置开始复制一系列元素A(长度为s-index) 13 //复制到新数组(第二个elementData),将A从index+1的位置开始复制。完成后就将index位置空置出来 14 System.arraycopy(elementData, index, 15 elementData, index + 1, 16 s - index); 17 //将元素放置在index的位置 18 elementData[index] = element; 19 //将数组长度加1; 20 size = s + 1; 21 } 22 23 private void rangeCheckForAdd(int index) { 24 if (index > size || index < 0) 25 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 26 }
3)删除指定索引的元素
1 public E remove(int index) { 2 //检查索引有没有越界 3 Objects.checkIndex(index, size); 4 //对数据做一个备份 5 final Object[] es = elementData; 6 //获取要删除的数据,用户结果返回 7 @SuppressWarnings("unchecked") E oldValue = (E) es[index]; 8 //删除数组中数据 9 fastRemove(es, index); 10 //返回被删除的数据 11 return oldValue; 12 } 13 14 private void fastRemove(Object[] es, int i) { 15 modCount++; 16 final int newSize; 17 //判断被删除元素后面是否还有元素 18 if ((newSize = size - 1) > i){ 19 //从原数组(第一个es)的第i+1索引处开始复制后面的所有元素 20 //新数组(第二个es),将旧数组复制的元素在新数组中从i的索引位置开始复制元素到新数组 21 System.arraycopy(es, i + 1, es, i, newSize - i); 22 } 23 //如果是最后一个元素或者执行完上一步的if语句,直接将最后一个索引的位置设置为null,方便GC回收内存 24 es[size = newSize] = null; 25 }
4)删除集合中指定的元素
1 public boolean remove(Object o) { 2 final Object[] es = elementData; 3 final int size = this.size; 4 int i = 0; 5 //获取对应元素的索引值i 6 found: { 7 if (o == null) { 8 //遍历整个数组,获取第一个元素为null的索引值i 9 for (; i < size; i++) 10 if (es[i] == null) 11 break found; 12 } else { 13 for (; i < size; i++) 14 //获取第一个元素值为o的索引值i 15 if (o.equals(es[i])) 16 break found; 17 } 18 //集合中不存在该元素,直接返回false 19 return false; 20 } 21 //删除指定索引的元素 22 fastRemove(es, i); 23 return true; 24 }
四、ArrayLsit的优缺点
1、优点
1)ArrayList的查询元素的速度是非常快的,由于数组是由索引存在的,直接根绝索引查询即可(指哪打哪)。由于ArrayList实现了RandomAccess接口,所以ArrayList被标记为使用二分法进行查询(使用二分法找到指定的索引);
2)ArrayList在最后的位置添加(删除)元素时,可以直接操作,时间复杂度为0(1);
2、缺点
在集合的指定位置(非最后的位置)进行添加和删除元素时,都要重新复制数组。增加空间复杂度O(n),比较耗费性能;
五、为什么ArrayList集合中定义的数组使用elementData[],使用transient关键字进行修饰?
由于ArrayList实现了Serializable接口,则该集合允许进行序列化和反序列化。对于未满的数组来说,真正需要序列化的只有那几个不为null的元素,其余的全部为null,序列化也没有意义。所以为了增强序列化(反序列化)的性能,使用transient来修饰,对数组不进行序列化,然后重写Serializable接口的WriteObject和ReadObject方法,使用重写的方法进行序列化和反序列化,保证只序列化或者反序列化存在的元素;
1 @java.io.Serial 2 private void writeObject(java.io.ObjectOutputStream s) 3 throws java.io.IOException { 4 // Write out element count, and any hidden stuff 5 int expectedModCount = modCount; 6 //先调用默认的序列化方法,序列化没有被Transient修饰的元素 7 s.defaultWriteObject(); 8 9 // Write out size as capacity for behavioral compatibility with clone() 10 s.writeInt(size); 11 12 // Write out all elements in the proper order. 13 for (int i=0; i<size; i++) { 14 //只序列化存在的元素 15 s.writeObject(elementData[i]); 16 } 17 18 if (modCount != expectedModCount) { 19 throw new ConcurrentModificationException(); 20 } 21 } 22 23 @java.io.Serial 24 private void readObject(java.io.ObjectInputStream s) 25 throws java.io.IOException, ClassNotFoundException { 26 27 // Read in size, and any hidden stuff 28 s.defaultReadObject(); 29 30 // Read in capacity 31 s.readInt(); // ignored 32 33 if (size > 0) { 34 // like clone(), allocate array based upon size not capacity 35 SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size); 36 Object[] elements = new Object[size]; 37 38 // Read in all elements in the proper order. 39 for (int i = 0; i < size; i++) { 40 elements[i] = s.readObject(); 41 } 42 43 elementData = elements; 44 } else if (size == 0) { 45 elementData = EMPTY_ELEMENTDATA; 46 } else { 47 throw new java.io.InvalidObjectException("Invalid size: " + size); 48 } 49 }