数组列表容量与数组大小之间的区别

我阅读了下面的核心Java我书中的片段。

将数组列表分配为新的ArrayList <'Employee>(100)//容量为100

新的Employee [100] // size是100分配一个新的数组是不一样的

数组列表的容量和数组的大小之间有一个重要的区别。 如果您分配一个有100个条目的arrays,则该arrays有100个插槽,可供使用。 一个容量为100个元素的数组列表可能包含100个元素(事实上,超过100个元素是以额外的重新分配为代价的); 但是一开始,即使在初始build设之后,数组列表根本就没有任何元素。

当我看到源代码数组列表时,构造函数创build一个给定容量的Object数组,它准备好容纳给定容量的元素(下面是代码段)。

public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } 

我无法弄清楚上面提到的作者的实际区别。

如果你用arr = new Employee[100]分配一个新的数组,那么这个数组的大小( arr.length )就是100.它有100个元素。 所有的元素最初都是空的(因为这是一个对象引用的数组),但是仍然有100个元素。

如果你做了像list = new ArrayList <Employee>(100) ,并试着检查list.size() ,你会得到0.列表中没有元素。

在内部, ArrayList分配了足够的空间来放置100个项目,然后才需要扩展其容量,但是这是一个内部实现细节,并且该列表将其内容呈现为“没有项目存储”。 只有当你真的做list.add(something) ,你会有列表中的项目。

因此,虽然列表提前分配存储空间,但与程序通信的API却告诉您没有任何项目。 其内部数组中的空项目不可用 – 您不能检索它们或更改它们。

ArrayList只是表示抽象列表的一种方法,ArrayList的容量是系统如何实现逻辑列表的实现细节。

一个ArrayList通过使用一个实际的数组来存储一个列表的元素。 计算机内存中数组的实际实现在分配时具有一定的大小, 这个大小是ArrayList的容量。 ArrayList通过存储除了固定长度数组之外的列表的逻辑长度来模拟可变大小的列表。 因此,如果您有一个包含4个逻辑元素的容量为10的ArrayList,则ArrayList可以表示为一个长度和一个数组

(4)| e1 | e2 | e3 | e4 | __ | __ | __ | __ | __ | __ |

其中(4)是列表的逻辑长度,“__”表示因为不属于逻辑列表而被忽略的数据。 如果你试图访问这个ArrayList的第5个元素,它会抛出一个exception,因为它知道第五个元素还没有被初始化。 如果我们附加一个额外的元素e5到列表中,ArrayList就变成了

(5)| e1 | e2 | e3 | e4 | e5 | __ | __ | __ | __ | __ |

请注意,由于底层数组仍然能够处理逻辑列表中的所有数据,因此容量没有发生变化,而逻辑长度却有所变化。

如果您设法向此列表中添加十个以上的元素,ArrayList将不会中断。 ArrayList是一个与所有数组操作兼容的抽象。 相反,当ArrayList的逻辑长度超过其原始容量时,ArrayList将更改其容量。 如果我们将元素(a1,a2,…,a7)添加到上面的列表中,那么得到的ArrayList可能看起来像

(12)| e1 | e2 | e3 | e4 | e5 | a1 | a2 | a3 | a4 | a5 | a6 | a7 | __ | __ | __ | __ | __ | __ | __ | __ |

容量为20。

一旦你创build了一个ArrayList,你可以忽略后面所有编程的容量; 逻辑不受影响。 但是,系统在某种操作下的性能会受到影响。 例如,增加容量可能涉及分配更大的数组,将第一个数组复制到第二个数组中,然后执行操作。 与例如链接列表上的相同操作相比,这可能相当慢。 因此,selectArrayList的容量大于或至less与实际运行时环境中预期的实际元素数相当是明智的。

如果你创build一个新的数组myArray = new Object[100]那么你可以读写myArray[0]myArray[99] (你会发现它充满了null )。

如果你创build一个ArrayList myList = new ArrayList(100)那么你尝试getset任何元素,你将得到一个IndexOutOfBoundsException ,因为List是空的,直到你add一些东西。

总之,大小为100的数组最初将保存100个null ,但是List将是空的。

这似乎措辞不佳,可能不正确,如果我不正确的理解。

我相信它试图说的是,ArrayList的初始容量和ArrayList的初始大小之间是有区别的。

 List<Employee> employees = new ArrayList<>(100); int size = employes.size(); 

在初始容量为100时, 大小将为0。

你对如何阅读源代码是正确的。

区别在于一个固定大小的容器(数据结构)和一个可变大小的容器。

一个数组是一个固定大小的容器,它所保存的元素的数量是在数组创build时build立的,并且永远不会改变。 (创build数组时,所有这些元素都会有一些默认值,例如,对于引用types为null,对于整数,则为0,但是它们将全部在数组中:您可以对每个元素进行索引。

一个列表是一个可变大小的容器,其中的元素数量可以改变,范围从0到任意数量(受实现限制)。 创build之后,元素的数量可以增长或缩小。 在任何时候,你都可以通过索引检索任何元素。

但是Java概念List实际上是一个接口,它可以用许多不同的方式来实现。 因此, ArrayListLinkedList等在列表后面有一个数据结构来实际保存元素。 而且这个数据结构本身可能是固定的大小或者可变的大小,并且在任何给定的时间都可能具有列表中元素数目的确切大小,或者可能具有一些extra “缓冲”空间。

例如LinkedList总是在其基础数据结构中具有与它所表示的列表中相同数量的“元素的位置”。 但ArrayList使用固定长度的数组作为其后备存储。

对于ArrayList ,在任何给定时间,列表中元素的数量可能不同于其后面的数组元素的数量。 这些元素的“额外”位置只包含空值或0或其他值,但是ArrayList永远不会让您访问这些位置。 在向ArrayList添加元素时,它们会占用底层数组中的更多位置,直到底层数组已满。 添加到ArrayList下一个元素会导致一个全新的固定大小的数组(比“当前”数组大一些)被分配,并将所有列表元素复制到它(原始数组将被丢弃)。 为了防止这种昂贵的操作(分配和复制)过于频繁地发生,新数组比当前数组大(通过某种因素),因此具有当时不会保存列表元素的元素 – 它们是空的或0)。

因此,由于所表示的列表中的元素的数量与实现的数据结构可以容纳的元素的数量(可能)之间存在差异,所以存在两个概念。

列表的大小是其中的元素的数量。 列表容量是此时支持数据结构可以容纳的元素的数量。 大小将随着元素被添加到列表或从列表中移除而改变。 当您使用的列表的实施需要它时,容量将会改变。 (当然,规模永远不会超过容量。)

(顺便说一句,对于固定大小的容器, 大小通常被称为长度 ,因此数组具有属性长度 ,string具有方法长度() 。不同的语言 – 有时甚至是相同的语言 – 不一致地使用“大小”和“长度”总是意味着大小 ,“容量”一词总是用于底层数据结构的大小/长度。)

让我们用一个真实的例子。 考虑一个十八座巴士,容量是十八个乘客。 在任何时间旅客的大小可以less于十八,但不超过。 当乘客人数为十八人时,另一名乘客不能入住。

在一个ArrayList中,容量与我们的总线有一些共同之处,那就是它定义了可以容纳的元素的数量。然而,与我们的总线不同,容量扩展到容纳元素的数量,直到Integer.MAX_VALUE。

大小也一样,就像我们的公交车一样,列表中的元素的大小不能超过容量。 想象一下,当50名乘客乘坐18座巴士时! 你肯定不想坐在那辆公交车上。