|
1 | | -努力编写中... |
| 1 | +# 一文直接带你吃透 ArrayList |
| 2 | + |
| 3 | +> ArrayList 是日常开发中相当常见、面试也相当常考的一种 JDK 集合类,了解并熟悉、甚至能实现一个 ArrayList 对面试、提升自己编码功底大有益处。 |
| 4 | +
|
| 5 | +## 一、写给小白 ArrayList 简单使用技巧 |
| 6 | + |
| 7 | +这部分是 ArrayList 的简单使用技巧,主要是介绍 ArrayList 的几个常见方法。 |
| 8 | + |
| 9 | +```java |
| 10 | +/** |
| 11 | + * 编写一个ArrayList的简单实用demo |
| 12 | + * ArrayList 的常见方法包括: |
| 13 | + * add(element):添加元素 |
| 14 | + * get(index):获取下标元素 |
| 15 | + * remove(index):移除下标对应元素 |
| 16 | + * set(index,element):将index处的元素修改为element |
| 17 | + */ |
| 18 | +public class arrayList { |
| 19 | + public static void main(String[] args) { |
| 20 | + // 创建 ArrayList 的对象 |
| 21 | + ArrayList al = new ArrayList(); |
| 22 | + // 添加元素 |
| 23 | + al.add("finky"); |
| 24 | + // 构造随机数并进行添加 |
| 25 | + Random rnd = new Random(); |
| 26 | + for (int i = 0; i < 20; i++) { |
| 27 | + al.add(rnd.nextInt(1000)); |
| 28 | + } |
| 29 | + // 取出ArrayList里的元素进行打印 |
| 30 | + for (int i = 0; i < al.size(); i++) { |
| 31 | + System.out.print(al.get(i) + " "); |
| 32 | + } |
| 33 | + // 修改0号index成的元素为doocs |
| 34 | + System.out.println(); |
| 35 | + al.set(0, "doocs"); |
| 36 | + System.out.println(al.get(0)); |
| 37 | + // 移除“doocs”元素 |
| 38 | + al.remove(0); |
| 39 | + System.out.println(al.get(0)); |
| 40 | + } |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +```java |
| 45 | +// 这是上面打印后的demo,可以看到第0处下标元素先是修改成了doocs,进行移除后,第0处下标元素变成了912 |
| 46 | +finky 912 922 284 305 675 565 159 109 73 298 491 920 296 397 358 145 610 190 839 845 |
| 47 | +doocs |
| 48 | +912 |
| 49 | +``` |
| 50 | + |
| 51 | +## 二、ArrayList 的源码分析 |
| 52 | + |
| 53 | +我们来看看 ArrayList 的源码: |
| 54 | + |
| 55 | +#### 1、来看看 ArrayList 的初始化: |
| 56 | + |
| 57 | +```java |
| 58 | +// ArrayList 初始化时默认大小为10 |
| 59 | +private static final int DEFAULT_CAPACITY = 10; |
| 60 | + |
| 61 | +// 直接初始化的话一个空数组 |
| 62 | +private static final Object[] EMPTY_ELEMENTDATA = {}; |
| 63 | + |
| 64 | +// 初始化ArrayList,传入初始化时的大小 |
| 65 | +public ArrayList(int initialCapacity) { |
| 66 | + if (initialCapacity > 0) { |
| 67 | + this.elementData = new Object[initialCapacity]; |
| 68 | + } else if (initialCapacity == 0) { |
| 69 | + this.elementData = EMPTY_ELEMENTDATA; |
| 70 | + } else { |
| 71 | + throw new IllegalArgumentException("Illegal Capacity: "+ |
| 72 | + initialCapacity); |
| 73 | + } |
| 74 | +} |
| 75 | +// 如果不传入大小的话就默认大小是10,那么这里就有一个问题:我们上面插入的元素超过了10,继续插入元素就会进行拷贝扩容,性能不是特别高。所以我们一般情况下初始化时给定一个比较靠谱的数组大小,避免到时候导致元素不断拷贝 |
| 76 | +public ArrayList() { |
| 77 | + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; |
| 78 | +} |
| 79 | + |
| 80 | +``` |
| 81 | + |
| 82 | +总结一下 ArrayList 初始化:我们创建 ArrayList 对象时,如果没有传入对应的大小,就会默认创建一个元素大小为 10 的数组,下次插入元素超过 10 时,会进行数组的拷贝扩容,这样性能消耗太高,所以建议就是在初始化时给定一个不要太小的容量大小。== |
| 83 | + |
| 84 | +#### 2、 ArrayList 的 add 方法: |
| 85 | + |
| 86 | +先上`add` 方法的代码: |
| 87 | + |
| 88 | +```java |
| 89 | +public boolean add(E e) { |
| 90 | + ensureCapacityInternal(size + 1); // Increments modCount!! |
| 91 | + elementData[size++] = e; |
| 92 | + return true; |
| 93 | +} |
| 94 | + |
| 95 | +public void add(int index, E element) { |
| 96 | + rangeCheckForAdd(index); |
| 97 | + ensureCapacityInternal(size + 1); // Increments modCount!! |
| 98 | + System.arraycopy(elementData, index, elementData, index + 1, |
| 99 | + size - index); |
| 100 | + elementData[index] = element; |
| 101 | + size++; |
| 102 | +} |
| 103 | + |
| 104 | +public void add(E e) { |
| 105 | + checkForComodification(); |
| 106 | + try { |
| 107 | + int i = cursor; |
| 108 | + ArrayList.this.add(i, e); |
| 109 | + cursor = i + 1; |
| 110 | + lastRet = -1; |
| 111 | + expectedModCount = modCount; |
| 112 | + } catch (IndexOutOfBoundsException ex) { |
| 113 | + throw new ConcurrentModificationException(); |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +private void rangeCheck(int index) { |
| 118 | + if (index < 0 || index >= this.size) |
| 119 | + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | +先判断当前数组元素是否满了,如果塞满了就会进行数组扩容,随后进行数组拷贝。 |
| 127 | + |
| 128 | +再然后插入元素,同时对应的 index++。 |
| 129 | + |
| 130 | +#### 3、瞧瞧 ArrayList 的 set 方法: |
| 131 | + |
| 132 | +```java |
| 133 | +public E set(int index, E element) { |
| 134 | + rangeCheck(index); |
| 135 | + E oldValue = elementData(index); |
| 136 | + elementData[index] = element; |
| 137 | + return oldValue; |
| 138 | +} |
| 139 | + |
| 140 | +public void set(E e) { |
| 141 | + if (lastRet < 0) |
| 142 | + throw new IllegalStateException(); |
| 143 | + checkForComodification(); |
| 144 | + |
| 145 | + try { |
| 146 | + ArrayList.this.set(lastRet, e); |
| 147 | + } catch (IndexOutOfBoundsException ex) { |
| 148 | + throw new ConcurrentModificationException(); |
| 149 | + } |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +1、先进行 index 判断是否越界,如果没有越界的话获取原来的旧的值 |
| 154 | + |
| 155 | +2、进行替换并返回该位置原来的旧的值 |
| 156 | + |
| 157 | +#### 4、ArrayList 的 get 方法: |
| 158 | + |
| 159 | +```java |
| 160 | +public E get(int index) { |
| 161 | + rangeCheck(index); |
| 162 | + checkForComodification(); |
| 163 | + return ArrayList.this.elementData(offset + index); |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +进行 index 是否越界的判断,然后去取对应下标的值。 |
| 168 | + |
| 169 | +#### 5、ArrayList 的 remove 方法: |
| 170 | + |
| 171 | +```java |
| 172 | +public void remove() { |
| 173 | + if (lastRet < 0) |
| 174 | + throw new IllegalStateException(); |
| 175 | + checkForComodification(); |
| 176 | + |
| 177 | + try { |
| 178 | + ArrayList.this.remove(lastRet); |
| 179 | + cursor = lastRet; |
| 180 | + lastRet = -1; |
| 181 | + expectedModCount = modCount; |
| 182 | + } catch (IndexOutOfBoundsException ex) { |
| 183 | + throw new ConcurrentModificationException(); |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +public E remove(int index) { |
| 188 | + // 进行index是否越界的判断 |
| 189 | + rangeCheck(index); |
| 190 | + checkForComodification(); |
| 191 | + E result = parent.remove(parentOffset + index); |
| 192 | + this.modCount = parent.modCount; |
| 193 | + this.size--; |
| 194 | + return result; |
| 195 | +} |
| 196 | + |
| 197 | +public E remove(int index) { |
| 198 | + rangeCheck(index); |
| 199 | + modCount++; |
| 200 | + E oldValue = elementData(index); |
| 201 | + int numMoved = size - index - 1; |
| 202 | + if (numMoved > 0) |
| 203 | + System.arraycopy(elementData, index+1, elementData, index, |
| 204 | + numMoved); |
| 205 | + elementData[--size] = null; |
| 206 | + return oldValue; |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | + |
| 211 | + |
| 212 | +1、先进行下标是否越界的判断,获取 index 处的元素值(这是要删除的值) |
| 213 | + |
| 214 | +2、然后进行元素拷贝,把 index 后面的元素往前拷贝 |
| 215 | + |
| 216 | +#### 6、关于 ArrayList 动态扩容和数组拷贝: |
| 217 | + |
| 218 | +```java |
| 219 | +private void ensureCapacityInternal(int minCapacity) { |
| 220 | + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); |
| 221 | +} |
| 222 | + |
| 223 | +private void ensureExplicitCapacity(int minCapacity) { |
| 224 | + modCount++; |
| 225 | + if (minCapacity - elementData.length > 0) |
| 226 | + grow(minCapacity); |
| 227 | +} |
| 228 | + |
| 229 | +private void grow(int minCapacity) { |
| 230 | + // overflow-conscious code |
| 231 | + int oldCapacity = elementData.length; |
| 232 | + // 扩容的代码:这里做了位运算,相当于数组扩容了1.5倍 |
| 233 | + int newCapacity = oldCapacity + (oldCapacity >> 1); |
| 234 | + if (newCapacity - minCapacity < 0) |
| 235 | + newCapacity = minCapacity; |
| 236 | + if (newCapacity - MAX_ARRAY_SIZE > 0) |
| 237 | + newCapacity = hugeCapacity(minCapacity); |
| 238 | + // 随后进行元素拷贝 |
| 239 | + elementData = Arrays.copyOf(elementData, newCapacity); |
| 240 | +} |
| 241 | + |
| 242 | +``` |
| 243 | + |
| 244 | +现在假定场景:arraylist 中已经有 10 个元素类,要放第 11 个元素。 |
| 245 | + |
| 246 | +此时进行容量检测,出现问题:空间大小不够。 |
| 247 | + |
| 248 | +解决方法:此时进行数组扩容右位移 1(**相当于总容量多加 1.5 倍**)扩容,老的大小+老大小的一半,进行元素拷贝 |
| 249 | + |
| 250 | +## 三、来仿照 JDK 源码写一个自己的 ArrayList 把 |
| 251 | + |
| 252 | +```java |
| 253 | +public class OwnArrayList<E> { |
| 254 | + private E data[]; |
| 255 | + private int size; |
| 256 | + |
| 257 | + public OwnArrayList(int capacity) { |
| 258 | + data = (E[]) new Object[capacity]; |
| 259 | + size = 0; |
| 260 | + } |
| 261 | + // 初始化是默认设置大小为20 |
| 262 | + public OwnArrayList() { |
| 263 | + this(20); |
| 264 | + } |
| 265 | + |
| 266 | + // 获取数组容量 |
| 267 | + public int getCapacity() { |
| 268 | + return data.length; |
| 269 | + } |
| 270 | + |
| 271 | + // 获取数组元素个数 |
| 272 | + public int getSize() { |
| 273 | + return size; |
| 274 | + } |
| 275 | + |
| 276 | + // 判断数组是否为空 |
| 277 | + public boolean isEmpity() { |
| 278 | + return size == 0; |
| 279 | + } |
| 280 | + |
| 281 | + // 获取index索引位置的元素 |
| 282 | + public E get(int index) { |
| 283 | + if (index < 0 || index >= size) |
| 284 | + throw new IllegalArgumentException("add failed,the index should >= 0 or <= size"); |
| 285 | + return data[index]; |
| 286 | + } |
| 287 | + |
| 288 | + // 修改index索引位置的元素为e |
| 289 | + public void set(int index, E e) { |
| 290 | + if (index < 0 || index >= size) |
| 291 | + throw new IllegalArgumentException("add failed,the index should >= 0 or <= size"); |
| 292 | + data[index] = e; |
| 293 | + } |
| 294 | + |
| 295 | + // 在数组中间插入一个元素 |
| 296 | + public void add(int index, E element) { |
| 297 | + if (size == data.length) { |
| 298 | + throw new IllegalArgumentException("AddLast failed,array has already full"); |
| 299 | + } |
| 300 | + if (index < 0 || index > size) { |
| 301 | + throw new IllegalArgumentException("add failed,the index should >= 0 or <= size"); |
| 302 | + } |
| 303 | + for (int i = size - 1; i >= index; i--) { |
| 304 | + data[i + 1] = data[i]; |
| 305 | + } |
| 306 | + data[index] = element; |
| 307 | + size++; |
| 308 | + } |
| 309 | + |
| 310 | + // 向数组元素末尾添加一个元素 |
| 311 | + public void addLast(E element) { |
| 312 | + add(size,element); |
| 313 | + } |
| 314 | + |
| 315 | + // 在数组头部插入一个元素 |
| 316 | + public void addFirst(E element) { |
| 317 | + add(0, element); |
| 318 | + } |
| 319 | + |
| 320 | + // 判断是否含有元素 |
| 321 | + public boolean contains(E e) { |
| 322 | + for (int i = 0; i < size; i++) |
| 323 | + if (data[i] == e) |
| 324 | + return true; |
| 325 | + return false; |
| 326 | + } |
| 327 | + |
| 328 | + // 查找元素e的位置 |
| 329 | + public int find(E e) { |
| 330 | + for (int i = 0; i < size; i++) { |
| 331 | + if (data[i] == e) { |
| 332 | + return i; |
| 333 | + } |
| 334 | + } |
| 335 | + return -1; |
| 336 | + } |
| 337 | + |
| 338 | + // 删除index位置的元素 |
| 339 | + public E remove(int index) { |
| 340 | + if (index < 0 || index > size) { |
| 341 | + throw new IllegalArgumentException("index should be 0 to size"); |
| 342 | + } |
| 343 | + E remove_element = data[index]; |
| 344 | + for (int i = index + 1; i < size; i++) { |
| 345 | + data[i - 1] = data[i]; |
| 346 | + } |
| 347 | + size--; |
| 348 | + return remove_element; |
| 349 | + } |
| 350 | + |
| 351 | + // 删除末尾元素 |
| 352 | + // 注意:这是逻辑删除,但是size的大小已经做了相应的减少,所以从实际意义上我们外界并不能访问到末尾元素的值 |
| 353 | + public E removelast() { |
| 354 | + return remove(size - 1); |
| 355 | + } |
| 356 | + |
| 357 | + // 删除开头元素 |
| 358 | + public E removeFirst() { |
| 359 | + return remove(0); |
| 360 | + } |
| 361 | + |
| 362 | + // 将数组空间的容量变成newCapacity大小 |
| 363 | + private void resize(int newCapacity) { |
| 364 | + newCapacity = getCapacity()*2; |
| 365 | + E[] newData = (E[]) new Object[newCapacity]; |
| 366 | + for (int i = 0; i < size; i++) |
| 367 | + newData[i] = data[i]; |
| 368 | + data = newData; |
| 369 | + } |
| 370 | +} |
| 371 | +``` |
| 372 | + |
| 373 | +## 四、面试时关于 ArrayList 要说的事 |
| 374 | + |
| 375 | +如果有人问你 ArrayList 知多少,我觉得可以从这几个方面出发: |
| 376 | + |
| 377 | +ArrayList 的底层是基于数组进行的,进行随机位置的插入和删除、以及扩容时性能很差,但进行随机的读和取时速度却很快。 |
| 378 | + |
| 379 | +接着可以从源码的角度分析 add、remove、set、get、数组扩容拷贝的过程场景。 |
| 380 | + |
| 381 | +最后也是特别重要的一点,就是要积极掌握主动性,延伸出 LinkedList 的特点、源码、两者间的对比等。 |
| 382 | + |
| 383 | +注:当需要动态数组时我们通常使用 ArrayList 而不是使用类似的 vector,这里有一点说明一下,就是尽管 Vector 的方法都是线程安全的,但其在单线程下需要花费的时间更多,而 ArrayList 尽管不是线程安全的,但其花费的时间很少。 |
| 384 | + |
| 385 | +## 终:参考资料 |
| 386 | + |
| 387 | +1. JDK 集合框架 ArrayList 源码 |
| 388 | +2. 《Core.Java.Volume.I.Fundamentals.11th.Edition》 |
0 commit comments