最近正准备回顾一下Java,所以在此做一些记录。
ArrayList作为Java的基础集合,因支持动态的扩容而经常被使用,下面记录一下其常用方法的实现
1. add(E e) 在集合末尾新增一个元素
1 /** 2 * 添加一个元素 3 * 4 * @param e 所要添加的元素 5 * @return 6 */ 7 public boolean add(E e) { 8 //将会进行判断数组是否是要扩容,需要的话将会进行扩容 9 ensureCapacityInternal(size + 1); 10 //存放元素,size表示当前集合中元素的个数 11 elementData[size++] = e; 12 return true; 13 } 14 15 /** 16 * 判断该集合是否满足最小容量并进行扩容 17 * @param minCapacity 最小容量 18 */ 19 private voidensureCapacityInternal (int minCapacity) { 20 if (elementData == EMPTY_ELEMENTDATA) { 21 //如果本来就是空数组,则根据默认值和当前所需的最小容量取一个最大值作为新数组的大小 22 //DEFAULT_CAPACITY = 10, 所以如果新建一个没有指定大小的集合,添加第一个元素后集合大小变成10 23 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 24 } 25 //扩容 26 ensureExplicitCapacity(minCapacity); 27 } 28 29 /** 30 * 判断当前集合大小是否需要扩容 31 * @param minCapacity 最小容量 32 */ 33 private void ensureExplicitCapacity(int minCapacity) { 34 modCount++; 35 36 // 如果最小容量大于当前数组大小,则进行真正的扩容操作 37 if (minCapacity - elementData.length > 0) 38 grow(minCapacity); 39 } 40 41 /** 42 * 真正的扩容操作 43 * 44 * @param minCapacity 最小容量 45 */ 46 private void grow(int minCapacity) { 47 // 获取旧数组的值 48 int oldCapacity = elementData.length; 49 //先将旧数组扩充1.5倍 50 int newCapacity = oldCapacity + (oldCapacity >> 1); 51 //判断旧数组扩充1.5倍是否够用,不够就使用传入的最小容量 52 if (newCapacity - minCapacity < 0) 53 newCapacity = minCapacity; 54 //判断是否为超大数组 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 55 if (newCapacity - MAX_ARRAY_SIZE > 0) 56 newCapacity = hugeCapacity(minCapacity); 57 // 使用Arrays.copyOf来生成新的数组 58 // 里面也是新创建了一个数组,使用System.arraycopy将旧数组的数据迁移到新数组中 59 elementData = Arrays.copyOf(elementData, newCapacity); 60 } 61 62 /** 63 * 超大数组的判断 64 * 65 * @param minCapacity 66 * @return 67 */ 68 private static int hugeCapacity(int minCapacity) { 69 //判断minCapacity是否已经溢出了,超过了int的最大值 70 if (minCapacity < 0) 71 throw new OutOfMemoryError(); 72 //超过MAX_ARRAY_SIZE则返回int最大值,否则返回MAX_ARRAY_SIZE 73 return (minCapacity > MAX_ARRAY_SIZE) ? 74 Integer.MAX_VALUE : 75 MAX_ARRAY_SIZE; 76 }
2.add(int index, E element) 在指定位置添加元素
1 /** 2 * 指定位置添加元素 3 * 4 * @param index 指定元素的位置 5 * @param element 添加的元素 6 */ 7 public void add(int index, E element) { 8 //判断需要插入的位置是否越界 9 rangeCheckForAdd(index); 10 //和add一样的扩容操作 11 ensureCapacityInternal(size + 1); 12 //直接使用了System.arraycopy,将指定位置之后的元素往后移动一位 13 System.arraycopy(elementData, index, elementData, index + 1, 14 size - index); 15 //插入指定的元素 16 elementData[index] = element; 17 //元素个数增加 18 size++; 19 } 20 21 /** 22 * 判断指定的位置是否越界 23 */ 24 private void rangeCheckForAdd(int index) { 25 if (index > size || index < 0) 26 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 27 }
3.get(int index)获取指定位置的元素
1 /** 2 * 获取指定位置处的元素 3 * 4 * @param index 指定的位置 5 * @return 返回该位置的元素 6 */ 7 public E get(int index) { 8 //判断是否越界 9 rangeCheck(index); 10 //获取元素 11 return elementData(index); 12 } 13 14 private void rangeCheck(int index) { 15 //判断是否超出 16 if (index >= size) 17 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 18 }
4.remove(int index) 删除指定位置的元素
1 /** 2 * 删除指定位置的元素 3 * 4 * @param index 指定的位置 5 * @return 返回删除的元素 6 */ 7 public E remove(int index) { 8 //判断指定的位置是否越界 9 rangeCheck(index); 10 11 modCount++; 12 //获取到该元素 13 E oldValue = elementData(index); 14 15 //计算需要移动的元素个数 16 int numMoved = size - index - 1; 17 //使用System.arraycopy将删除位置之后的元素往前移动 18 if (numMoved > 0) 19 System.arraycopy(elementData, index+1, elementData, index, 20 numMoved); 21 //元素个数减1并制设置空 22 elementData[--size] = null; 23 24 return oldValue; 25 }
5.remove(Object o) 删除指定元素
1 /** 2 * 删除指定元素,只删除从头开始第一个匹配的 3 * 4 * @param o 需要删除的元素 5 */ 6 public boolean remove(Object o) { 7 //判断是否为null 8 if (o == null) { 9 //遍历 10 for (int index = 0; index < size; index++) 11 //判断是否为null 12 if (elementData[index] == null) { 13 //删除 14 fastRemove(index); 15 return true; 16 } 17 } else { 18 //不为null也是遍历 19 for (int index = 0; index < size; index++) 20 if (o.equals(elementData[index])) { 21 //删除 22 fastRemove(index); 23 return true; 24 } 25 } 26 return false; 27 } 28 29 /* 30 * 删除指定位置元素 31 * 和remove方法差不多,只是少了越界判断和旧元素的返回 32 */ 33 private void fastRemove(int index) { 34 35 modCount++; 36 int numMoved = size - index - 1; 37 if (numMoved > 0) 38 System.arraycopy(elementData, index+1, elementData, index, 39 numMoved); 40 elementData[--size] = null; 41 }
6.indexOf(Object o) 查询指定元素的位置 lastIndexOf也一样,只是从尾部开始遍历
1 /** 2 * 查询指定元素的位置 3 * @param o 指定的元素 4 * @return 所在位置索引 5 */ 6 public int indexOf(Object o) { 7 //判断元素值是否为null 8 if (o == null) { 9 //从头开始遍历 10 for (int i = 0; i < size; i++) 11 if (elementData[i]==null) 12 return i; 13 } else { 14 //不为null,从头开始遍历 15 for (int i = 0; i < size; i++) 16 if (o.equals(elementData[i])) 17 return i; 18 } 19 return -1; 20 }
7.set(int index, E element) 设置指定位置的元素值
1 /** 2 * 设置指定位置的元素值 3 * 4 * @param index 指定的位置 5 * @param element 元素值 6 * @return 返回旧值 7 */ 8 public E set(int index, E element) { 9 //判断是否越界 10 rangeCheck(index); 11 //通过索引设置新值 12 E oldValue = elementData(index); 13 elementData[index] = element; 14 return oldValue; 15 } 16 17 private void rangeCheck(int index) { 18 //判断是否超出 19 if (index >= size) 20 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 21 }
8.retainAll(Collection<?> c) 求两个集合的交集
1 /** 2 * 求两个集合的交集 3 * @param c 4 * @return 5 */ 6 public boolean retainAll(Collection<?> c) { 7 //判断是否为空 8 Objects.requireNonNull(c); 9 return batchRemove(c, true); 10 } 11 12 /** 13 * 求交集或差集 14 * @param c 15 * @param complement 用于判断保存下来的集合是交集还是差集 16 * @return 17 */ 18 private boolean batchRemove(Collection<?> c, boolean complement) { 19 final Object[] elementData = this.elementData; 20 int r = 0, w = 0; 21 boolean modified = false; 22 try { 23 //遍历集合判断元素 24 for (; r < size; r++) 25 //根据complement的值保留元素 26 if (c.contains(elementData[r]) == complement) 27 elementData[w++] = elementData[r]; 28 } finally { 29 // r != size 应该是上面的循环抛出异常了 30 //正常循环应该r == size 31 //将异常之后的数据保留 32 if (r != size) { 33 System.arraycopy(elementData, r, 34 elementData, w, 35 size - r); 36 w += size - r; 37 } 38 //w!=size,说明集合数量有变动 39 // 清空一些元素值并重新设置size大小 40 if (w != size) { 41 for (int i = w; i < size; i++) 42 elementData[i] = null; 43 modCount += size - w; 44 size = w; 45 modified = true; 46 } 47 } 48 return modified; 49 }
总结一下
1.ArrayList作为集合,根据索引查询的速度最快,如果需要查询指定元素,需要遍历,并不会很快
2.ArrayList使用的时候最好指定集合大小,否则插入操作多的话会经常需要扩容,需要进行数组的复制,比较慢
3.ArrayList的删除元素和插入指定位置的元素会存在部分元素迁移的情况,如果数据量大也会有影响