为什么我们不能在将元素添加到第 (n-1) 个索引之前将元素添加到第 n 个索引,即使提供了初始容量
Why we can't add element into a List to nth index before adding element to (n-1)th index even if initial capacity is provided
假设我声明了一个大小为 10 的 int
数组,我可以将一个元素添加到它的第 4 个索引,我可以毫无例外地 运行 代码。
int[] ar = new int[10];
ar[4] = 8;
System.out.println(Arrays.toString(ar)); //works fine
那是因为当我说大小为 10 时,将为该数组分配那么多内存 space,每个索引中都保留其类型的初始值。
但List
的情况不一样。假设我声明了一个初始容量为 10 的列表,然后我尝试将一个元素添加到它给出的第 4 个索引
java.lang.IndexOutOfBoundsException: Index: 4, Size: 0
List<Integer> list = new ArrayList<Integer>(10);
list.add(4, 8); //exception
当然,即使给定了初始容量,列表的大小也会 return 0
。为什么它不像Array,我认为没有为列表的10个元素分配内存?
我猜有什么方法可以在给定容量后用默认值填充列表,就像数组一样。
这就是 ArrayList 的 JavaDoc 关于 add(int index, E element)
的说法:
throws IndexOutOfBoundsException - if the index is out of range (index < 0 || index > size())
size是当前存储的元素个数,不是当前capacity。
事实上,您的汽车 "capacity" 可以以 100 英里/小时的速度行驶,但这并不意味着您可以在 1 秒内神奇地从 0 英里/小时加速到 90 英里/小时 ;-)
换句话说:答案是大小和容量不一样。容量仅表示:"that is the size this list can grow to before the underlying array needs to grow".
现在应该清楚了,构造函数上的初始容量只是对初始内部数组的一点内存管理。没有任何语义。
当实际size()
溢出数组时,重新分配数组。
不存在带有初始元素的批量分配这样的东西。然而有:
List<Integer> list = Collections.nCopies(10, Integer.valueOf(0));
并且新的 Stream
提供了动态生成列表的方法。
你可以这样做:
public <T> void add(List<T> list, int i, T obj) {
while (list.size() < i) {
list.add(null);
}
list.add(i, obj);
}
但很明显,您将引入 nulls,这是不安全且丑陋的,
需要空值检查。
正如其他人在他们的回答中提到的,在 List<Integer> list = new ArrayList<Integer>(10)
中,10
指定初始 容量 。
指定初始容量只是一件可选的事情。仅当您使用接受初始容量作为参数的特定构造函数时,您才行使该选项。当您使用其他构造函数时,您无法控制初始容量。
如果您希望第一个 n
添加到列表中尽可能高效,则指定 n
作为初始容量 -- 否则,每个人的添加都有可能列表中的项目会导致一些代价高昂的内部重新调整大小并重新复制到重新调整大小的内部区域。
上面没有回答为什么不允许在位置8添加项目,而位置7没有项目的问题。
正如一些人回答的那样,那是因为 the API doc says so.
这是一种回答方式。但是为什么 API 文档这么说呢?为什么东西是这样设计的?
事情是这样设计的,因为:
- 在位置 8 添加一个项目,当位置 7 没有项目时,会产生一个间隙(在位置 8 之前)。
- 作为一名程序员,您将必须跟踪项目的位置,在所有可能的位置中(全部容量)。目前,作为一名程序员,您只需要跟踪项目的位置,在所有添加的项目中。现在,那岂不是 编程噩梦?
假设我声明了一个大小为 10 的 int
数组,我可以将一个元素添加到它的第 4 个索引,我可以毫无例外地 运行 代码。
int[] ar = new int[10];
ar[4] = 8;
System.out.println(Arrays.toString(ar)); //works fine
那是因为当我说大小为 10 时,将为该数组分配那么多内存 space,每个索引中都保留其类型的初始值。
但List
的情况不一样。假设我声明了一个初始容量为 10 的列表,然后我尝试将一个元素添加到它给出的第 4 个索引
java.lang.IndexOutOfBoundsException: Index: 4, Size: 0
List<Integer> list = new ArrayList<Integer>(10);
list.add(4, 8); //exception
当然,即使给定了初始容量,列表的大小也会 return 0
。为什么它不像Array,我认为没有为列表的10个元素分配内存?
我猜有什么方法可以在给定容量后用默认值填充列表,就像数组一样。
这就是 ArrayList 的 JavaDoc 关于 add(int index, E element)
的说法:
throws IndexOutOfBoundsException - if the index is out of range (index < 0 || index > size())
size是当前存储的元素个数,不是当前capacity。
事实上,您的汽车 "capacity" 可以以 100 英里/小时的速度行驶,但这并不意味着您可以在 1 秒内神奇地从 0 英里/小时加速到 90 英里/小时 ;-)
换句话说:答案是大小和容量不一样。容量仅表示:"that is the size this list can grow to before the underlying array needs to grow".
现在应该清楚了,构造函数上的初始容量只是对初始内部数组的一点内存管理。没有任何语义。
当实际size()
溢出数组时,重新分配数组。
不存在带有初始元素的批量分配这样的东西。然而有:
List<Integer> list = Collections.nCopies(10, Integer.valueOf(0));
并且新的 Stream
提供了动态生成列表的方法。
你可以这样做:
public <T> void add(List<T> list, int i, T obj) {
while (list.size() < i) {
list.add(null);
}
list.add(i, obj);
}
但很明显,您将引入 nulls,这是不安全且丑陋的, 需要空值检查。
正如其他人在他们的回答中提到的,在 List<Integer> list = new ArrayList<Integer>(10)
中,10
指定初始 容量 。
指定初始容量只是一件可选的事情。仅当您使用接受初始容量作为参数的特定构造函数时,您才行使该选项。当您使用其他构造函数时,您无法控制初始容量。
如果您希望第一个 n
添加到列表中尽可能高效,则指定 n
作为初始容量 -- 否则,每个人的添加都有可能列表中的项目会导致一些代价高昂的内部重新调整大小并重新复制到重新调整大小的内部区域。
上面没有回答为什么不允许在位置8添加项目,而位置7没有项目的问题。
正如一些人回答的那样,那是因为 the API doc says so.
这是一种回答方式。但是为什么 API 文档这么说呢?为什么东西是这样设计的?
事情是这样设计的,因为:
- 在位置 8 添加一个项目,当位置 7 没有项目时,会产生一个间隙(在位置 8 之前)。
- 作为一名程序员,您将必须跟踪项目的位置,在所有可能的位置中(全部容量)。目前,作为一名程序员,您只需要跟踪项目的位置,在所有添加的项目中。现在,那岂不是 编程噩梦?