为什么arrays不可扩展?

当我们创建一个数组时,我们无法改变它的大小; 它是固定的。 好吧,看起来不错,我们可以创建一个新的更大的数组并逐个复制值,这有点慢。 它的技术背景是什么?

这个问题没有提到一种语言,所以我将为我的答案选择基于’C’的数组。

数组被分配为单个内存块。 增长arrays是有问题的,因为正确地做这件事的唯一方法是在最后增长它。 对于大小为N的增长,在下一个分配的地址之前,数组末尾必须至少有N个空闲字节。

支持这种类型的分配需要将分配分布在虚拟地址空间中。 这既消除了使内存分配彼此更接近的好处,又有助于增加碎片。 这在大多数内存管理器面前都是如此,这些内存管理器试图将内存打包在一起并减少碎片。

在内存中具有足够空间的位置分配新arrays并复制arrays根本不是一种通用解决方案。 原因是消费者可以通过指针看到数组的先前位置。

 int* array = malloc(int*someSize); int* pointer1 = &(arr[2]); growArray(&array, 12); // Can't move because pointer1 knows the address of the array 

根部的数组是一个连续的“数组”内存。 其他数据可以占用此内存区域之前和之后的数据,因此如果不分配适合新的更大尺寸的新的不同内存区域,则无法动态resize。

取决于您的语言,但通常数组在内存中排列为一系列顺序空格。 这样您就不必为数组中的每个点存储内存位置,只需存储一个内存位置(数组的开头),然后添加一个偏移量(偏移量将是每个条目的大小乘以索引你想要找出特定条目在内存中的位置。

这也是数组通常只包含一种类型的原因,否则您无法进行如此简单的计算。 允许您存储多种类型的语言实际上是创建一个普通数组并将指针放到数组中的每个条目 – 所有指针通常都是相同的大小。 这种间接成本水平,这就是为什么“更容易”的语言往往会慢一点。

无论如何,当你分配更多的内存时,你想把新的内存放在数组的末尾 – 否则你会把你的内存分成一个洞 – 你为什么要这样做呢?

所以你不能只是在没有物理移动的情况下扩展数组。

计算机多年来一直这样做,所以大多数语言都有一些方法可以分配一大块内存然后告诉CPU将所有条目块复制到新块并更改指针以反映这一点,但通常(C, Java,…)他们把这个留给程序员用特定的命令复制数组而不是为你做(可能只是为了让你知道扩展数组不是“免费的”

可以在数组的末尾添加一个指针,以跳转到要添加到数组末尾的新内存块,但现在您的数组查找速度已经相当缓慢。

许多语言只是将数组包装为允许此类function的集合。 例如,Java Vector / ArrayList将自动为您重新分配内存。 链表实际上每次只用一个指向下一个元素的指针分配一个元素。 添加元素非常快,但转到元素5000的速度很慢(您必须读取每个元素,而使用数组读取元素1的速度与元素5000一样快)

这取决于语言。

在C(以及像Java这样的类似语言)中,当你声明一个像int ary[10]这样的数组时,系统会留出足够的内存来背对背地保存十个整数。 扩展它并不容易,因为系统没有留出任何额外的空间(因为它不知道你是想要扩展它还是想要扩展它)以及在arrays可能被使用之后出现的内存通过别的东西。 因此,获得更大数组的唯一方法是留出一个新的内存块来保存扩展的数组,然后复制旧内容并添加新项。

你是对的,这可能很慢。 解决这个问题的一种方法是声明您的arrays比您需要的更大,以便为您提供增长空间。 特别是在较旧的计算机上,这可能会导致程序占用大量内存从未使用过。

另一种方法是使用具有可扩展数组的更高级语言。 例如,Ruby允许您将更多项添加到数组中,而无需声明内存或复制数组内容。

一般来说,编程语言在某处可以分配固定的内存部分 。 然后,在这种抽象中,可以创建其他抽象,这可以通过移动/复制数据来隐藏内存管理的复杂性。

大多数情况下, array是固定的 – 一种(某种程度上)低级抽象 – listscollections 构建在数组之上,并且知道如何动态调整自身大小。

拥有这样的低级抽象以便有时能够实现有效的算法/优化是很方便的。 但是在大多数代码中,您可以使用列表和集合,而不必过多担心性能。

是否可以更改数组的大小取决于您使用的语言。 在那些无法增加数组大小的语言中,原因是数组在内存中的连续位置布局,编译器无法保证数组末尾后的位置可以添加到数组中。 许多编程语言都支持可扩展的数组类型,但这些只是为您重新分配和复制底层内存。

例如,在Curl编程语言中,有一个FastArray类型,它具有size和max-size。 max-size指定数组的最大大小,并确定将为arrays分配多少内存。 有一种更通用的Array类型,它使用FastArray作为其底层实现,如果需要扩展数组超出底层FastArray的max-size,它将替换FastArray实例。

回到汇编语言,人们不得不声明变量所需的内存空间。 这是数据段(DS)注册表中的保留内存。

所以,大致看起来像这样(Borland Turbo Assembler):

 .DATA myStringVariable DB "Hello world!", 13, 10 myArrayVariable DW " " 'Reserving 20 bytes in memory (in a row) .CODE MOV AX, @DATA MOV DS, AX ' ... 

然后,一旦.DATA段被分隔,它就无法改变,因为.CODE段(CS)从少量字节开始。

因此,如果一个数组是可扩展的,就像集合在.NET中那样,数据会覆盖代码,导致程序崩溃等。

C / C ++(3.0),Pascal(7.0),QBasic,PowerBasic和COM调试程序都基于这种架构,并且可以比Assembler允许的更好。

今天,通过更灵活的技术,我们现在能够根据需要动态分配内存地址,并且只使用一个变量来保持对它们的引用,因此数组已经可以通过集合进行扩展。 但是在某些情况下,您需要精确的字节数,例如网络数据包等,例如,数组仍然有用。 另一个例子是将图像存储在数据库中。 你完全知道mow大字节是一个图像,所以你可以将它存储在一个字节数组(Byte [])。

也许我在这里缺少一些准确性,我写的是我记得我最喜欢的编程语言。 也许一些人可以提出一些更详细的东西。

希望这可以帮助! =)