From c288ded9354291c62b67ea7fc5eb768525932b3f Mon Sep 17 00:00:00 2001 From: Guide Date: Wed, 9 Aug 2023 12:08:20 +0800 Subject: [PATCH] [docs update]typo --- .../zhishixingqiu-two-years.md | 2 + docs/database/mysql/mysql-index.md | 2 +- .../redis-common-blocking-problems-summary.md | 2 +- docs/java/collection/arraylist-source-code.md | 405 +++++++++--------- 4 files changed, 203 insertions(+), 208 deletions(-) diff --git a/docs/about-the-author/zhishixingqiu-two-years.md b/docs/about-the-author/zhishixingqiu-two-years.md index ca929264..43586aa8 100644 --- a/docs/about-the-author/zhishixingqiu-two-years.md +++ b/docs/about-the-author/zhishixingqiu-two-years.md @@ -145,6 +145,8 @@ star: 2 ![个人微信](https://oss.javaguide.cn/xingqiu/weixin-guidege666.jpeg) + + **无任何套路,无任何潜在收费项。用心做内容,不割韭菜!** 不过, **一定要确定需要再进** 。并且, **三天之内觉得内容不满意可以全额退款** 。 diff --git a/docs/database/mysql/mysql-index.md b/docs/database/mysql/mysql-index.md index 0041c058..ad7b8b7d 100644 --- a/docs/database/mysql/mysql-index.md +++ b/docs/database/mysql/mysql-index.md @@ -56,7 +56,7 @@ index = hash % array_size 为了减少 Hash 冲突的发生,一个好的哈希函数应该“均匀地”将数据分布在整个可能的哈希值集合中。 -MySQL 的 InnoDB 存储引擎不直接支持常规的哈希索引,但是,InnoDB 存储引擎中存在一种特殊的“自适应哈希索引”(Adaptive Hash Index),自适应哈希索引并不是传统意义上的纯哈希索引,而是结合了 B+Tree 和哈希索引的特点,以便更好地适应实际应用中的数据访问模式和性能需求。自适应哈希索引的每个哈希桶实际上是一个小型的 B+Tree 结构。这个 B+Tree 结构可以存储多个键值对,而不仅仅是一个键。这有助于减少哈希冲突链的长度,提高了索引的效率。 +MySQL 的 InnoDB 存储引擎不直接支持常规的哈希索引,但是,InnoDB 存储引擎中存在一种特殊的“自适应哈希索引”(Adaptive Hash Index),自适应哈希索引并不是传统意义上的纯哈希索引,而是结合了 B+Tree 和哈希索引的特点,以便更好地适应实际应用中的数据访问模式和性能需求。自适应哈希索引的每个哈希桶实际上是一个小型的 B+Tree 结构。这个 B+Tree 结构可以存储多个键值对,而不仅仅是一个键。这有助于减少哈希冲突链的长度,提高了索引的效率。关于 Adaptive Hash Index 的详细介绍,可以查看 [MySQL各种“Buffer”之Adaptive Hash Index](https://mp.weixin.qq.com/s/ra4v1XR5pzSWc-qtGO-dBg) 这篇文章。 既然哈希表这么快,**为什么 MySQL 没有使用其作为索引的数据结构呢?** 主要是因为 Hash 索引不支持顺序和范围查询。假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。并且,每次 IO 只能取一个。 diff --git a/docs/database/redis/redis-common-blocking-problems-summary.md b/docs/database/redis/redis-common-blocking-problems-summary.md index f572c20c..938f084f 100644 --- a/docs/database/redis/redis-common-blocking-problems-summary.md +++ b/docs/database/redis/redis-common-blocking-problems-summary.md @@ -126,7 +126,7 @@ Redis 集群可以进行节点的动态扩容缩容,这一过程目前还处 **什么是 Swap?** Swap 直译过来是交换的意思,Linux 中的 Swap 常被称为内存交换或者交换分区。类似于 Windows 中的虚拟内存,就是当内存不足的时候,把一部分硬盘空间虚拟成内存使用,从而解决内存容量不足的情况。因此,Swap 分区的作用就是牺牲硬盘,增加内存,解决 VPS 内存不够用或者爆满的问题。 -Swap 对于 Redis 来说是非常致命的,Redis 保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把 Redis 使用的部分内存换出硬盘,由于内存与硬盘读写的速度并几个数量级,会导致发生交换后的 Redis 性能急剧下降。 +Swap 对于 Redis 来说是非常致命的,Redis 保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把 Redis 使用的部分内存换出硬盘,由于内存与硬盘的读写速度差几个数量级,会导致发生交换后的 Redis 性能急剧下降。 识别 Redis 发生 Swap 的检查方法如下: diff --git a/docs/java/collection/arraylist-source-code.md b/docs/java/collection/arraylist-source-code.md index 1b60ce53..f395c3e4 100644 --- a/docs/java/collection/arraylist-source-code.md +++ b/docs/java/collection/arraylist-source-code.md @@ -67,16 +67,8 @@ System.out.println(listOfStrings); 这里以 JDK1.8为例,分析一下 `ArrayList` 的底层源码。 ```java -package java.util; - -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.function.UnaryOperator; - - public class ArrayList extends AbstractList - implements List, RandomAccess, Cloneable, java.io.Serializable -{ + implements List, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** @@ -89,8 +81,8 @@ public class ArrayList extends AbstractList */ private static final Object[] EMPTY_ELEMENTDATA = {}; - //用于默认大小空实例的共享空数组实例。 - //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。 + //用于默认大小空实例的共享空数组实例。 + //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** @@ -115,14 +107,14 @@ public class ArrayList extends AbstractList this.elementData = EMPTY_ELEMENTDATA; } else { //其他情况,抛出异常 - throw new IllegalArgumentException("Illegal Capacity: "+ - initialCapacity); + throw new IllegalArgumentException("Illegal Capacity: " + + initialCapacity); } } /** - *默认无参构造函数 - *DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10 + * 默认无参构造函数 + * DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; @@ -153,44 +145,51 @@ public class ArrayList extends AbstractList modCount++; if (size < elementData.length) { elementData = (size == 0) - ? EMPTY_ELEMENTDATA - : Arrays.copyOf(elementData, size); + ? EMPTY_ELEMENTDATA + : Arrays.copyOf(elementData, size); } } //下面是ArrayList的扩容机制 //ArrayList的扩容机制提高了性能,如果每次只扩充一个, //那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。 + /** * 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量 - * @param minCapacity 所需的最小容量 + * + * @param minCapacity 所需的最小容量 */ public void ensureCapacity(int minCapacity) { //如果是true,minExpand的值为0,如果是false,minExpand的值为10 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; + // 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); } } - //1.得到最小扩容量 - //2.通过最小容量扩容 - private void ensureCapacityInternal(int minCapacity) { - if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { - // 获取“默认的容量”和“传入参数”两者之间的最大值 - minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); - } - ensureExplicitCapacity(minCapacity); + // 根据给定的最小容量和当前数组元素来计算所需容量。 + private static int calculateCapacity(Object[] elementData, int minCapacity) { + // 如果当前数组元素为空数组(初始情况),返回默认容量和最小容量中的较大值作为所需容量 + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + return Math.max(DEFAULT_CAPACITY, minCapacity); + } + // 否则直接返回最小容量 + return minCapacity; } - //判断是否需要扩容 + + // 确保内部容量达到指定的最小容量。 + private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); + } + + //判断是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { modCount++; - // overflow-conscious code if (minCapacity - elementData.length > 0) //调用grow方法进行扩容,调用此方法代表已经开始扩容了 @@ -222,17 +221,18 @@ public class ArrayList extends AbstractList // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } + //比较minCapacity和 MAX_ARRAY_SIZE private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? - Integer.MAX_VALUE : - MAX_ARRAY_SIZE; + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; } /** - *返回此列表中的元素数。 + * 返回此列表中的元素数。 */ public int size() { return size; @@ -255,12 +255,12 @@ public class ArrayList extends AbstractList } /** - *返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 + * 返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1 */ public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) - if (elementData[i]==null) + if (elementData[i] == null) return i; } else { for (int i = 0; i < size; i++) @@ -276,11 +276,11 @@ public class ArrayList extends AbstractList */ public int lastIndexOf(Object o) { if (o == null) { - for (int i = size-1; i >= 0; i--) - if (elementData[i]==null) + for (int i = size - 1; i >= 0; i--) + if (elementData[i] == null) return i; } else { - for (int i = size-1; i >= 0; i--) + for (int i = size - 1; i >= 0; i--) if (o.equals(elementData[i])) return i; } @@ -304,9 +304,9 @@ public class ArrayList extends AbstractList } /** - *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 - *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 - *因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。 + * 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 + * 返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 + * 因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的API之间的桥梁。 */ public Object[] toArray() { return Arrays.copyOf(elementData, size); @@ -314,17 +314,17 @@ public class ArrayList extends AbstractList /** * 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); - *返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。 - *否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。 - *如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。 - *(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。) + * 返回的数组的运行时类型是指定数组的运行时类型。 如果列表适合指定的数组,则返回其中。 + * 否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。 + * 如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null 。 + * (这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。) */ @SuppressWarnings("unchecked") public T[] toArray(T[] a) { if (a.length < size) // 新建一个运行时类型的数组,但是ArrayList数组的内容 return (T[]) Arrays.copyOf(elementData, size, a.getClass()); - //调用System提供的arraycopy()方法实现数组之间的复制 + //调用System提供的arraycopy()方法实现数组之间的复制 System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; @@ -372,8 +372,8 @@ public class ArrayList extends AbstractList /** * 在此列表中的指定位置插入指定的元素。 - *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; - *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 + * 先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; + * 再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 */ public void add(int index, E element) { rangeCheckForAdd(index); @@ -381,7 +381,7 @@ public class ArrayList extends AbstractList ensureCapacityInternal(size + 1); // Increments modCount!! //arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己 System.arraycopy(elementData, index, elementData, index + 1, - size - index); + size - index); elementData[index] = element; size++; } @@ -397,16 +397,16 @@ public class ArrayList extends AbstractList int numMoved = size - index - 1; if (numMoved > 0) - System.arraycopy(elementData, index+1, elementData, index, - numMoved); + System.arraycopy(elementData, index + 1, elementData, index, + numMoved); elementData[--size] = null; // clear to let GC do its work - //从列表中删除的元素 + //从列表中删除的元素 return oldValue; } /** * 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。 - *返回true,如果此列表包含指定的元素 + * 返回true,如果此列表包含指定的元素 */ public boolean remove(Object o) { if (o == null) { @@ -433,8 +433,8 @@ public class ArrayList extends AbstractList modCount++; int numMoved = size - index - 1; if (numMoved > 0) - System.arraycopy(elementData, index+1, elementData, index, - numMoved); + System.arraycopy(elementData, index + 1, elementData, index, + numMoved); elementData[--size] = null; // clear to let GC do its work } @@ -476,7 +476,7 @@ public class ArrayList extends AbstractList int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, - numMoved); + numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; @@ -485,16 +485,16 @@ public class ArrayList extends AbstractList /** * 从此列表中删除所有索引为fromIndex (含)和toIndex之间的元素。 - *将任何后续元素移动到左侧(减少其索引)。 + * 将任何后续元素移动到左侧(减少其索引)。 */ protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, - numMoved); + numMoved); // clear to let GC do its work - int newSize = size - (toIndex-fromIndex); + int newSize = size - (toIndex - fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } @@ -521,7 +521,7 @@ public class ArrayList extends AbstractList * 返回IndexOutOfBoundsException细节信息 */ private String outOfBoundsMsg(int index) { - return "Index: "+index+", Size: "+size; + return "Index: " + index + ", Size: " + size; } /** @@ -535,7 +535,7 @@ public class ArrayList extends AbstractList /** * 仅保留此列表中包含在指定集合中的元素。 - *换句话说,从此列表中删除其中不包含在指定集合中的所有元素。 + * 换句话说,从此列表中删除其中不包含在指定集合中的所有元素。 */ public boolean retainAll(Collection c) { Objects.requireNonNull(c); @@ -545,227 +545,220 @@ public class ArrayList extends AbstractList /** * 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。 - *指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。 - *返回的列表迭代器是fail-fast 。 + * 指定的索引表示初始调用将返回的第一个元素为next 。 初始调用previous将返回指定索引减1的元素。 + * 返回的列表迭代器是fail-fast 。 */ public ListIterator listIterator(int index) { if (index < 0 || index > size) - throw new IndexOutOfBoundsException("Index: "+index); + throw new IndexOutOfBoundsException("Index: " + index); return new ListItr(index); } /** - *返回列表中的列表迭代器(按适当的顺序)。 - *返回的列表迭代器是fail-fast 。 + * 返回列表中的列表迭代器(按适当的顺序)。 + * 返回的列表迭代器是fail-fast 。 */ public ListIterator listIterator() { return new ListItr(0); } /** - *以正确的顺序返回该列表中的元素的迭代器。 - *返回的迭代器是fail-fast 。 + * 以正确的顺序返回该列表中的元素的迭代器。 + * 返回的迭代器是fail-fast 。 */ public Iterator iterator() { return new Itr(); } - - ``` ## ArrayList 扩容机制分析 ### 先从 ArrayList 的构造函数说起 -**(JDK8)ArrayList 有三种方式来初始化,构造方法源码如下:** +ArrayList 有三种方式来初始化,构造方法源码如下(JDK8): ```java - /** - * 默认初始容量大小 - */ - private static final int DEFAULT_CAPACITY = 10; +/** + * 默认初始容量大小 + */ +private static final int DEFAULT_CAPACITY = 10; +private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; - private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; +/** + * 默认构造函数,使用初始容量10构造一个空列表(无参数构造) + */ +public ArrayList() { + this.elementData = 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); } +} - /** - * 带初始容量参数的构造函数。(用户自己指定容量) - */ - 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 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; } - - - /** - *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回 - *如果指定的集合为null,throws NullPointerException。 - */ - public ArrayList(Collection 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。** 下面在我们分析 ArrayList 扩容时会讲到这一点内容! +细心的同学一定会发现:**以无参数构造方法创建 `ArrayList` 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。** 下面在我们分析 `ArrayList` 扩容时会讲到这一点内容! -> 补充:JDK6 new 无参构造的 `ArrayList` 对象时,直接创建了长度是 10 的 `Object[]` 数组 elementData 。 +> 补充:JDK6 new 无参构造的 `ArrayList` 对象时,直接创建了长度是 10 的 `Object[]` 数组 `elementData` 。 ### 一步一步分析 ArrayList 扩容机制 -这里以无参构造函数创建的 ArrayList 为例分析 +这里以无参构造函数创建的 `ArrayList` 为例分析。 -#### 先来看 `add` 方法 +#### add 方法 ```java - /** - * 将指定的元素追加到此列表的末尾。 - */ - public boolean add(E e) { - //添加元素之前,先调用ensureCapacityInternal方法 - ensureCapacityInternal(size + 1); // Increments modCount!! - //这里看到ArrayList添加元素的实质就相当于为数组赋值 - elementData[size++] = e; - return true; - } +/** +* 将指定的元素追加到此列表的末尾。 +*/ +public boolean add(E e) { + // 加元素之前,先调用ensureCapacityInternal方法 + ensureCapacityInternal(size + 1); // Increments modCount!! + // 这里看到ArrayList添加元素的实质就相当于为数组赋值 + elementData[size++] = e; + return true; +} ``` -> **注意**:JDK11 移除了 `ensureCapacityInternal()` 和 `ensureExplicitCapacity()` 方法 +**注意**:JDK11 移除了 `ensureCapacityInternal()` 和 `ensureExplicitCapacity()` 方法 -#### 再来看看 `ensureCapacityInternal()` 方法 - -(JDK7)可以看到 `add` 方法 首先调用了`ensureCapacityInternal(size + 1)` +`ensureCapacityInternal` 方法的源码如下: ```java - //得到最小扩容量 - private void ensureCapacityInternal(int minCapacity) { - if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { - // 获取默认的容量和传入参数的较大值 - minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); - } - - ensureExplicitCapacity(minCapacity); +// 根据给定的最小容量和当前数组元素来计算所需容量。 +private static int calculateCapacity(Object[] elementData, int minCapacity) { + // 如果当前数组元素为空数组(初始情况),返回默认容量和最小容量中的较大值作为所需容量 + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + return Math.max(DEFAULT_CAPACITY, minCapacity); } + // 否则直接返回最小容量 + return minCapacity; +} + +// 确保内部容量达到指定的最小容量。 +private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); +} ``` -**当 要 add 进第 1 个元素时,minCapacity 为 1,在 Math.max()方法比较后,minCapacity 为 10。** - -> 此处和后续 JDK8 代码格式化略有不同,核心代码基本一样。 - -#### `ensureExplicitCapacity()` 方法 - -如果调用 `ensureCapacityInternal()` 方法就一定会进入(执行)这个方法,下面我们来研究一下这个方法的源码! +`ensureCapacityInternal` 方法非常简单,内部直接调用了 `ensureExplicitCapacity` 方法: ```java - //判断是否需要扩容 - private void ensureExplicitCapacity(int minCapacity) { - modCount++; - - // overflow-conscious code - if (minCapacity - elementData.length > 0) - //调用grow方法进行扩容,调用此方法代表已经开始扩容了 - grow(minCapacity); - } - +//判断是否需要扩容 +private void ensureExplicitCapacity(int minCapacity) { + modCount++; + //判断当前数组容量是否足以存储minCapacity个元素 + 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)` 方法。 +- 当我们要 `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 方法进行扩容。 +直到添加第 11 个元素,`minCapacity`(为 11)比 `elementData.length`(为 10)要大。进入 `grow` 方法进行扩容。 -#### `grow()` 方法 +#### grow 方法 ```java - /** - * 要分配的最大数组大小 - */ - private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; +/** + * 要分配的最大数组大小 + */ +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); - } +/** + * 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 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)!** 奇偶不同,比如:10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数. +**`int newCapacity = oldCapacity + (oldCapacity >> 1)`,所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)!** 奇偶不同,比如:10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数. > ">>"(移位运算符):>>1 右移一位相当于除 2,右移 n 位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了 1 位所以相当于 oldCapacity /2。对于大数据的 2 进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源 **我们再来通过例子探究一下`grow()` 方法:** -- 当 add 第 1 个元素时,oldCapacity 为 0,经比较后第一个 if 判断成立,newCapacity = minCapacity(为 10)。但是第二个 if 判断不会成立,即 newCapacity 不比 MAX_ARRAY_SIZE 大,则不会进入 `hugeCapacity` 方法。数组容量为 10,add 方法中 return true,size 增为 1。 -- 当 add 第 11 个元素进入 grow 方法时,newCapacity 为 15,比 minCapacity(为 11)大,第一个 if 判断不成立。新容量没有大于数组最大 size,不会进入 hugeCapacity 方法。数组容量扩为 15,add 方法中 return true,size 增为 11。 +- 当 `add` 第 1 个元素时,`oldCapacity` 为 0,经比较后第一个 if 判断成立,`newCapacity = minCapacity`(为 10)。但是第二个 if 判断不会成立,即 `newCapacity` 不比 `MAX_ARRAY_SIZE` 大,则不会进入 `hugeCapacity` 方法。数组容量为 10,`add` 方法中 return true,size 增为 1。 +- 当 `add` 第 11 个元素进入 `grow` 方法时,`newCapacity` 为 15,比 `minCapacity`(为 11)大,第一个 if 判断不成立。新容量没有大于数组最大 size,不会进入 huge`C`apacity 方法。数组容量扩为 15,add 方法中 return true,size 增为 11。 - 以此类推······ **这里补充一点比较重要,但是容易被忽视掉的知识点:** -- java 中的 `length`属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性. -- java 中的 `length()` 方法是针对字符串说的,如果想看这个字符串的长度则用到 `length()` 这个方法. -- java 中的 `size()` 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看! +- Java 中的 `length`属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性. +- Java 中的 `length()` 方法是针对字符串说的,如果想看这个字符串的长度则用到 `length()` 这个方法. +- Java 中的 `size()` 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看! -#### `hugeCapacity()` 方法。 +#### hugeCapacity() 方法 -从上面 `grow()` 方法源码我们知道:如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 +从上面 `grow()` 方法源码我们知道:如果新容量大于 `MAX_ARRAY_SIZE`,进入(执行) `hugeCapacity()` 方法来比较 `minCapacity` 和 `MAX_ARRAY_SIZE`,如果 `minCapacity` 大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 `MAX_ARRAY_SIZE` 即为 `Integer.MAX_VALUE - 8`。 ```java - 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; - } +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; +} ``` ### `System.arraycopy()` 和 `Arrays.copyOf()`方法 -阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及`add(int index, E element)`、`toArray()` 等方法中都用到了该方法! +阅读源码的话,我们就会发现 `ArrayList` 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及`add(int index, E element)`、`toArray()` 等方法中都用到了该方法! #### `System.arraycopy()` 方法