Java 关于数组时间复杂度的问题“;“调整大小”;在爪哇
注意:正如标题已经暗示的,这个问题不是关于基于数组的列表的特定Java 关于数组时间复杂度的问题“;“调整大小”;在爪哇,java,arrays,memory,time-complexity,Java,Arrays,Memory,Time Complexity,注意:正如标题已经暗示的,这个问题不是关于基于数组的列表的特定java.util.ArrayList实现,而是关于原始数组本身,以及它们在“纯”(即完全未优化的)基于数组的列表实现中的行为。我之所以选择提及java.util.ArrayList,是因为它是java中最突出的基于数组的列表示例,尽管它在技术上并不“纯粹”,因为它利用预分配来减少add()的操作时间。如果您想知道为什么我问这个特定的问题而对java.util.ArrayList()preallocation优化不感兴趣,我在下面添加
java.util.ArrayList
实现,而是关于原始数组本身,以及它们在“纯”(即完全未优化的)基于数组的列表实现中的行为。我之所以选择提及java.util.ArrayList
,是因为它是java中最突出的基于数组的列表示例,尽管它在技术上并不“纯粹”,因为它利用预分配来减少add()
的操作时间。如果您想知道为什么我问这个特定的问题而对java.util.ArrayList()
preallocation优化不感兴趣,我在下面添加了一点对我的用例的解释
众所周知,您可以访问基于数组的列表中的元素(如Java的ArrayList
),时间复杂度为O(1)
,而向该列表中添加元素需要O(n)
。对于链表,情况正好相反(对于双链表,您可以将访问优化到执行时间的一半)
将元素添加到基于数组的列表需要O(n)
的原因是数组不能简单地调整大小,必须重新分配和填充。最简单的方法是:
String arr[] = new String[n];
//...
String newElem = "foo";
String[] newArr = new String[n + 1];
int i = 0;
for (String elem : arr) {
newArr[i] = arr[i++];
}
newArr[i] = newElem;
arr = newArr;
由于for循环,时间复杂度O(n)
清晰可见。但是在Java中还有其他复制数组的方法,例如System.arraycopy()
按照常规的for循环解决方案,即使缩小数组也需要O(n)
,因为数组的大小是固定的,为了“缩小”它,您必须将要保留的所有元素复制到一个新的更小的数组中
下面是我关于这些数组操作及其时间复杂性的问题:
O(n)
,但是如果内存中有足够的空间来展开数组,那么System.arraycopy()
是否可以优化“add”操作,这意味着它会将原始数组保留在原来的位置,只在其末尾添加新元素O(1)
执行,因此System.arraycopy()
是否总是将此操作优化为O(1)
System.arraycopy()
无法使用这些优化,那么Java中是否有其他方法可以实际使用这些理论上可行的优化,或者无论在何种情况下,数组“调整大小”总是需要O(n)
O(n)
的情况
其他信息:
我使用的是openJDK11(最新版本),但是如果答案是依赖于JVM的,那么我想知道与之相比,其他JVM的表现如何
为了好奇的人
谁想知道我想对这些信息做什么:
我正在研究一个新的java.util.List
实现,即一个混合列表,它可以将数据存储在数组和链接缓冲区中。在某些情况下,缓冲区将被刷新到数组中,这当然需要调整现有数组的大小。但是除了这个想法之外,我还想在阵列部分使用尽可能多的其他优化。为了避免数组通常的大小调整,我尝试了让数组保持恒定大小的想法,但是用一些其他字段来管理它的“有效”范围。这意味着,如果要弹出数组的最后一个元素,它不会缩小数组,而是缩小有效元素的范围。然后,在数组部分插入新元素时,可以使用以前的无效部分将值转移到中,基本上重用了现在已删除的元素以前使用的空间。如果插入操作超过了实际数组大小,元素仍然可以传输到链接缓冲区以避免调整大小。为了进一步优化这一点,我选择在删除某些元素时使用数组的中间部分作为轴心。现在,有效范围可能不再从数组的开头开始。基本上,这意味着如果删除轴左侧的元素,则有效范围起点和删除元素之间的所有元素都将移向轴右侧。移除枢轴右侧的图元会相应地起作用。因此,经过一些删除后,阵列可能会如下所示:
[null null|elem0 elem1 elem2||elem3 elem4 elem5|null null null]
(其中开始和结束处的|标记有效范围,|标记轴)
那么,这与我的问题有什么关系呢
所有这些优化都建立在阵列大小调整在时间上非常昂贵的说法之上,即O(n)
。因此,尽可能避免调整阵列大小。这些优化听起来很整洁,但实现它们的代码可能会变得非常混乱,尤其是在实现批处理操作时(addAll()
,removeAll()
,retainal()
)。因此,如果事实证明数组大小调整操作本身在某些情况下(特别是收缩)的成本较低,我将减少许多优化,这些优化随后变得无用,从而使代码在过程中变得更容易
因此,在坚持我的优化想法和实验之前,我想知道是否需要它们。这些问题似乎非常特定于JVM供应商。至少可以说Javadoc(甚至在SE 14中)没有提到这一点。感谢这些信息,我正在使用openjdk 11,但我会将这些信息添加到我的问题
系统中。arraycopy使原始数组保持工作状态,因此无法在O(1)中实现除非使用奇怪的内存映射技术,否则肯定会产生意外的性能结果