Java集合系列之ArrayList
-
ArrayList继承于AbstractList,实现了List,RandomAccess,Cloneable,Java.io.Serializable这些接口。数据结构中线性表进行顺序存储,插入删除元素的时间复杂度为O(n),求表长以增加元素。取第i元素的时间复杂度为O(1);它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
-
ArrayList实现了RandonAccess接口,RandomAccess是一个标志接口,表明实现这个接口的List集合是支持快速随机访问的。在ArrayList中,我们可以通过元素的序号快速获取元素对象,这就是快速随机访问。
-
ArrayList实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
-
ArrayList实现了java.io.Serializable接口,这意味ArrayList支持序列化,能通过序列化去传输。
-
ArrayList中操作不是线程安全的,所以单线程中的才使用ArrayList,而多线程中使用CopyOnWriteArrayList。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * 默认初始容量大小为10 */ private static final int DEFAULT_CAPACITY = 10 ; /** * 空对象数组(用于空实例)。 */ private static final Object[] EMPTY_ELEMENTDATA = {}; //用于默认大小空实例的共享空数组实例。 //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 保存ArrayList数据的数组,底层用数组实现 */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList 所包含的元素个数 */ private int size; /** * 带初始容量参数的构造函数。(用户自己指定容量) */ public ArrayList( int initialCapacity) { if (initialCapacity > 0 ) { //创建initialCapacity大小的数组 this .elementData = new Object[initialCapacity]; } else if (initialCapacity == 0 ) { //创建空数组 this .elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException( "Illegal Capacity: " + initialCapacity); } } /** *默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10 */ public ArrayList() { this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。 */ public ArrayList(Collection<? extends E> c) { // elementData = c.toArray(); //如果指定集合元素个数不为0 if ((size = elementData.length) != 0 ) { // c.toArray 可能返回的不是Object类型的数组所以加上下面的语句用于判断, //这里用到了反射里面的getClass()方法 if (elementData.getClass() != Object[]. class ) elementData = Arrays.copyOf(elementData, size, Object[]. class ); } else { // 用空数组代替 this .elementData = EMPTY_ELEMENTDATA; } } } |
可以看到ArrayList的内部存储结构就是一个Object类型的数组,因此它可以存放任意类型的元素。在构造ArrayList的时候,如果传入初始大小那么它将新建一个指定容量的Object数组,如果不设置初始大小那么它将不会分配内存空间而是使用空的对象数组,在实际要放入元素时再进行内存分配。下面再看看它的增删改查方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | //增(添加) public boolean add(E e) { //添加前先检查是否需要拓展数组, 此时数组长度最小为size+1 ensureCapacityInternal(size + 1 ); //将元素添加到数组末尾 elementData[size++] = e; return true ; } //增(插入) public void add( int index, E element) { //插入位置范围检查 rangeCheckForAdd(index); //检查是否需要扩容 ensureCapacityInternal(size + 1 ); //挪动插入位置后面的元素 System.arraycopy(elementData, index, elementData, index + 1 , size - index); //在要插入的位置赋上新值 elementData[index] = element; size++; } //删 public E remove( int index) { //index不能大于size rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1 ; if (numMoved > 0 ) { //将index后面的元素向前挪动一位 System.arraycopy(elementData, index+ 1 , elementData, index, numMoved); } //置空引用 elementData[--size] = null ; return oldValue; } //改 public E set( int index, E element) { //index不能大于size rangeCheck(index); E oldValue = elementData(index); //替换成新元素 elementData[index] = element; return oldValue; } //查 public E get( int index) { //index不能大于size rangeCheck(index); //返回指定位置元素 return elementData(index); } |
- 增(添加):仅是将这个元素添加到末尾。操作快速。
- 增(插入):由于需要移动插入位置后面的元素,并且涉及数组的复制,所以操作较慢。
- 删:由于需要将删除位置后面的元素向前挪动,也会设计数组复制,所以操作较慢。
- 改:直接对指定位置元素进行修改,不涉及元素挪动和数组复制,操作快速。
- 查:直接返回指定下标的数组元素,操作快速。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | private void ensureCapacityInternal( int minCapacity) { //如果此时还是空数组 if (elementData == EMPTY_ELEMENTDATA) { //和默认容量比较, 取较大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } //数组已经初始化过就执行这一步 ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity( int minCapacity) { modCount++; //如果最小容量大于数组长度就扩增数组 if (minCapacity - elementData.length > 0 ) { grow(minCapacity); } } //集合最大容量 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ; //增加数组长度 private void grow( int minCapacity) { //获取数组原先的容量,oldCapacity为旧容量,newCapacity为新容量 int oldCapacity = elementData.length; //新数组的容量, 在原来的基础上增加一半 //位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1 ); //检验新的容量是否小于最小容量 if (newCapacity - minCapacity < 0 ) { newCapacity = minCapacity; } //检验新的容量是否超过最大数组容量 if (newCapacity - MAX_ARRAY_SIZE > 0 ) { newCapacity = hugeCapacity(minCapacity); } //拷贝原来的数组到新数组 elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity( int minCapacity) { if (minCapacity < 0 ) // overflow throw new OutOfMemoryError(); //对minCapacity和MAX_ARRAY_SIZE进行比较 //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小 //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小 //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } |
每次添加元素前会调用ensureCapacityInternal这个方法进行集合容量检查。在这个方法内部会检查当前集合的内部数组是否还是个空数组,如果是就新建默认大小为10的Object数组。如果不是则证明当前集合已经被初始化过,那么就调用ensureExplicitCapacity方法检查当前数组的容量是否满足这个最小所需容量,不满足的话就调用grow方法进行扩容。在grow方法内部可以看到,每次扩容都是增加原来数组长度的一半,扩容实际上是新建一个容量更大的数组,将原先数组的元素全部复制到新的数组上,然后再抛弃原先的数组转而使用新的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | /** *返回此列表中的元素数。 */ public int size() { return size; } /** * 如果此列表不包含元素,则返回 true 。 */ public boolean isEmpty() { //注意=和==的区别 return size == 0 ; } /** * 如果此列表包含指定的元素,则返回true 。 */ public boolean contains(Object o) { //indexOf()方法:返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 return indexOf(o) >= 0 ; } /** *返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 */ public int indexOf(Object o) { if (o == null ) { for ( int i = 0 ; i < size; i++) if (elementData[i]== null ) return i; } else { for ( int i = 0 ; i < size; i++) //equals()方法比较 if (o.equals(elementData[i])) return i; } return - 1 ; } /** * 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。. */ public int lastIndexOf(Object o) { if (o == null ) { for ( int i = size- 1 ; i >= 0 ; i--) if (elementData[i]== null ) return i; } else { for ( int i = size- 1 ; i >= 0 ; i--) if (o.equals(elementData[i])) return i; } return - 1 ; } /** * 返回此ArrayList实例的浅拷贝。 (元素本身不被复制。) */ public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super .clone(); //Arrays.copyOf功能是实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度 v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0 ; return v; } catch (CloneNotSupportedException e) { // 这不应该发生,因为我们是可以克隆的 throw new InternalError(e); } } /** *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。 */ public Object[] toArray() { return Arrays.copyOf(elementData, size); } /** * 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); *返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。 *否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。 *如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。 *(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。) */ @SuppressWarnings ( "unchecked" ) public <T> T[] toArray(T[] a) { if (a.length < size) // 新建一个运行时类型的数组,但是ArrayList数组的内容 return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //调用System提供的arraycopy()方法实现数组之间的复制 System.arraycopy(elementData, 0 , a, 0 , size); if (a.length > size) a[size] = null ; return a; } // Positional Access Operations @SuppressWarnings ( "unchecked" ) E elementData( int index) { return (E) elementData[index]; } /** * 返回此列表中指定位置的元素。 */ public E get( int index) { rangeCheck(index); return elementData(index); } /** * 用指定的元素替换此列表中指定位置的元素。 */ public E set( int index, E element) { //对index进行界限检查 rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; //返回原来在这个位置的元素 return oldValue; } /** * 将指定的元素追加到此列表的末尾。 */ public boolean add(E e) { ensureCapacityInternal(size + 1 ); // Increments modCount!! //这里看到ArrayList添加元素的实质就相当于为数组赋值 elementData[size++] = e; return true ; } /** * 在此列表中的指定位置插入指定的元素。 *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 */ public void add( int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1 ); // Increments modCount!! //arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己 System.arraycopy(elementData, index, elementData, index + 1 , size - index); elementData[index] = element; size++; } /** * 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。 */ public E remove( int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1 ; if (numMoved > 0 ) System.arraycopy(elementData, index+ 1 , elementData, index, numMoved); elementData[--size] = null ; // clear to let GC do its work //从列表中删除的元素 return oldValue; } //注意:有了垃圾收集器并不意味着一定不会有内存泄漏。对象能否被GC的依据是是否还有引用指向它,上面代码中如果不手动赋null值,除非对应的位置被其他元素覆盖,否则原来的对象就一直不会被回收。 /** * 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。 *返回true,如果此列表包含指定的元素 */ public boolean remove(Object o) { if (o == null ) { for ( int index = 0 ; index < size; index++) if (elementData[index] == null ) { fastRemove(index); return true ; } } else { for ( int index = 0 ; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true ; } } return false ; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove( int index) { modCount++; int numMoved = size - index - 1 ; if (numMoved > 0 ) System.arraycopy(elementData, index+ 1 , elementData, index, numMoved); elementData[--size] = null ; // clear to let GC do its work } /** * 从列表中删除所有元素。 */ public void clear() { modCount++; // 把数组中所有的元素的值设为null for ( int i = 0 ; i < size; i++) elementData[i] = null ; size = 0 ; } /** * 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。 */ public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0 , elementData, size, numNew); size += numNew; return numNew != 0 ; } /** * 将指定集合中的所有元素插入到此列表中,从指定的位置开始。一次添加多个元素,根据位置不同也有两个把本,一个是在末尾添加的,一个是在指定位置插入与add()方法类似 */ public boolean addAll( int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0 ) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0 , elementData, index, numNew); size += numNew; return numNew != 0 ; } /** * 从此列表中删除所有索引为fromIndex (含)和toIndex之间的元素。 *将任何后续元素移动到左侧(减少其索引)。 */ protected void removeRange( int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); // clear to let GC do its work int newSize = size - (toIndex-fromIndex); for ( int i = newSize; i < size; i++) { elementData[i] = null ; } size = newSize; } /** * 检查给定的索引是否在范围内。 */ private void rangeCheck( int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * add和addAll使用的rangeCheck的一个版本 */ private void rangeCheckForAdd( int index) { if (index > size || index < 0 ) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * 返回IndexOutOfBoundsException细节信息 */ private String outOfBoundsMsg( int index) { return "Index: " +index+ ", Size: " +size; } /** * 从此列表中删除指定集合中包含的所有元素。 */ public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); //如果此列表被修改则返回true return batchRemove(c, false ); } /** * 仅保留此列表中包含在指定集合中的元素。 *换句话说,从此列表中删除其中不包含在指定集合中的所有元素。 */ public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true ); } /** * 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。 *指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。 *返回的列表迭代器是fail-fast 。 */ public ListIterator<E> listIterator( int index) { if (index < 0 || index > size) throw new IndexOutOfBoundsException( "Index: " +index); return new ListItr(index); } /** *返回列表中的列表迭代器(按适当的顺序)。 *返回的列表迭代器是fail-fast 。 */ public ListIterator<E> listIterator() { return new ListItr( 0 ); } /** *以正确的顺序返回该列表中的元素的迭代器。 *返回的迭代器是fail-fast 。 */ public Iterator<E> iterator() { return new Itr(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | /** * 默认初始容量大小 */ private static final int DEFAULT_CAPACITY = 10 ; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** *默认构造函数,使用初始容量10构造一个空列表(无参数构造) */ public ArrayList() { this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 带初始容量参数的构造函数。(用户自己指定容量) */ public ArrayList( int initialCapacity) { if (initialCapacity > 0 ) //初始容量大于0 { //创建initialCapacity大小的数组 this .elementData = new Object[initialCapacity]; } else if (initialCapacity == 0 ) //初始容量等于0 { //创建空数组 this .elementData = EMPTY_ELEMENTDATA; } else //初始容量小于0,抛出异常 { throw new IllegalArgumentException( "Illegal Capacity: " + initialCapacity); } } /** *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回 *如果指定的集合为null,throws NullPointerException。 */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0 ) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[]. class ) elementData = Arrays.copyOf(elementData, size, Object[]. class ); } else { // replace with empty array. this .elementData = EMPTY_ELEMENTDATA; } } |
以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。
1 2 3 4 5 6 7 8 9 10 11 | /** * 将指定的元素追加到此列表的末尾。 */ public boolean add(E e) { //添加元素之前,先调用ensureCapacityInternal方法 ensureCapacityInternal(size + 1 ); // Increments modCount!! //这里看到ArrayList添加元素的实质就相当于为数组赋值 elementData[size++] = e; return true ; } |
1 2 3 4 5 6 7 8 9 10 | //得到最小扩容量 private void ensureCapacityInternal( int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 获取默认的容量和传入参数的较大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } |
注意:当要add进第1个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity 为10。
1 2 3 4 5 6 7 8 9 | //判断是否需要扩容 private void ensureExplicitCapacity( int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0 ) //调用grow方法进行扩容,调用此方法代表已经开始扩容了 grow(minCapacity); } |
-
当add第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal()方法 ,所以 minCapacity 此时为10。此时,minCapacity - elementData.length > 0 成立,所以会进入 grow(minCapacity)方法。
-
当add第2个元素时,minCapacity 为2,此时elementData.length(数组容量)在添加第一个元素后扩容成10了。此时,minCapacity - elementData.length > 0 不成立,所以不会进入(执行)grow(minCapacity)方法。
-
添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。
-
直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。再次进入grow方法进行扩容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * 要分配的最大数组大小 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ; /** * ArrayList扩容的核心方法。 */ private void grow( int minCapacity) { // oldCapacity为旧容量,newCapacity为新容量 int oldCapacity = elementData.length; //将oldCapacity 右移一位,其效果相当于oldCapacity / 2, //位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, int newCapacity = oldCapacity + (oldCapacity >> 1 ); //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, if (newCapacity - minCapacity < 0 ) newCapacity = minCapacity; // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity()方法来比较 minCapacity 和MAX_ARRAY_SIZE, //如果minCapacity大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8。 if (newCapacity - MAX_ARRAY_SIZE > 0 ) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } |
int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!(JDK1.6版本以后)JDk1.6版本时,扩容之后容量为 1.5 倍+1!
1 2 3 4 5 6 7 8 9 10 | private static int hugeCapacity( int minCapacity) { if (minCapacity < 0 ) // overflow throw new OutOfMemoryError(); //对minCapacity和MAX_ARRAY_SIZE进行比较 //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小 //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小 //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * 在此列表中的指定位置插入指定的元素。 *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 */ public void add( int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1 ); //Increments modCount!! //arraycopy()方法实现数组自己复制自己 //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量; System.arraycopy(elementData, index, elementData, index + 1 , size - index); elementData[index] = element; size++; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class ArraycopyTest { public static void main(String[] args) { // TODO Auto-generated method stub int [] a = new int [ 10 ]; a[ 0 ] = 0 ; a[ 1 ] = 1 ; a[ 2 ] = 2 ; a[ 3 ] = 3 ; System.arraycopy(a, 2 , a, 3 , 3 ); a[ 2 ]= 99 ; for ( int i = 0 ; i < a.length; i++) { System.out.println(a[i]); } } } 输出结果: 0 1 99 2 3 0 0 0 0 0 |
1 2 3 4 5 6 7 8 | /** 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。 */ public Object[] toArray() { //elementData:要复制的数组;size:要复制的长度 return Arrays.copyOf(elementData, size); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class ArrayscopyOfTest { public static void main(String[] args) { int [] a = new int [ 3 ]; a[ 0 ] = 0 ; a[ 1 ] = 1 ; a[ 2 ] = 2 ; int [] b = Arrays.copyOf(a, 10 ); System.out.println( "b.length" +b.length); } } 输出结果: 10 |
-
联系:看两者源代码可以发现copyOf()内部实际调用了System.arraycopy()方法
-
区别:arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf()是系统自动在内部新建一个数组,并返回该数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** 如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。 * * @param minCapacity 所需的最小容量 */ public void ensureCapacity( int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } |
最好在 add 大量元素之前用ensureCapacity方法,以减少增量重新分配的次数。我们通过下面的代码实际测试以下这个方法的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class EnsureCapacityTest { public static void main(String[] args) { ArrayList<Object> list = new ArrayList<Object>(); final int N = 10000000 ; long startTime = System.currentTimeMillis(); for ( int i = 0 ; i < N; i++) { list.add(i); } long endTime = System.currentTimeMillis(); System.out.println( "使用ensureCapacity方法前:" +(endTime - startTime)); } } 运行结果: 使用ensureCapacity方法前: 2158 |
通过运行结果,我们可以看出向ArrayList添加大量元素之前最好先使用ensureCapacity方法,以减少增量重新分配的次数。
8). 删除元素
1 2 3 4 5 6 7 8 9 10 | public E remove( int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1 ; if (numMoved > 0 ) System.arraycopy(elementData, index+ 1 , elementData, index, numMoved); elementData[--size] = null ; // clear to let GC do its work return oldValue; } |
1 2 3 4 | private class Itr implements Iterator<E> private class ListItr extends Itr implements ListIterator<E> private class SubList extends AbstractList<E> implements RandomAccess static final class ArrayListSpliterator<E> implements Spliterator<E> |
1 2 3 4 5 6 7 | SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) { this .parent = parent; this .parentOffset = fromIndex; this .offset = offset + fromIndex; this .size = toIndex - fromIndex; this .modCount = ArrayList. this .modCount; } |
可以看到,这个构造函数中把原来的 List 以及该 List 中的部分属性直接赋值给自己的一些属性了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | import java.util.ArrayList; import java.util.Iterator; public class ArrayListDemo { public static void main(String[] srgs){ ArrayList<Integer> arrayList = new ArrayList<Integer>(); System.out.printf( "Before add:arrayList.size() = %d\n" ,arrayList.size()); arrayList.add( 1 ); arrayList.add( 3 ); arrayList.add( 5 ); arrayList.add( 7 ); arrayList.add( 9 ); System.out.printf( "After add:arrayList.size() = %d\n" ,arrayList.size()); System.out.println( "Printing elements of arrayList" ); // 三种遍历方式打印元素 // 第一种:通过迭代器遍历 System.out.print( "通过迭代器遍历:" ); Iterator<Integer> it = arrayList.iterator(); while (it.hasNext()){ System.out.print(it.next() + " " ); } System.out.println(); // 第二种:通过索引值遍历 System.out.print( "通过索引值遍历:" ); for ( int i = 0 ; i < arrayList.size(); i++){ System.out.print(arrayList.get(i) + " " ); } System.out.println(); // 第三种:for循环遍历 System.out.print( "for循环遍历:" ); for (Integer number : arrayList){ System.out.print(number + " " ); } // toArray用法 // 第一种方式(最常用) Integer[] integer = arrayList.toArray( new Integer[ 0 ]); // 第二种方式(容易理解) Integer[] integer1 = new Integer[arrayList.size()]; arrayList.toArray(integer1); // 抛出异常,java不支持向下转型 //Integer[] integer2 = new Integer[arrayList.size()]; //integer2 = arrayList.toArray(); System.out.println(); // 在指定位置添加元素 arrayList.add( 2 , 2 ); // 删除指定位置上的元素 arrayList.remove( 2 ); // 删除指定元素 arrayList.remove((Object) 3 ); // 判断arrayList是否包含5 System.out.println( "ArrayList contains 5 is: " + arrayList.contains( 5 )); // 清空ArrayList arrayList.clear(); // 判断ArrayList是否为空 System.out.println( "ArrayList is empty: " + arrayList.isEmpty()); } } |
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术