我正在与另一名学生竞赛,以制作最快的家庭作业版本,并且由于性能原因我没有使用ArrayList(我自己将基准时间从56秒调整为4),但是我想知道每次需要时我应该调整数组的大小。具体来说,我的代码的相关部分是这样的:
private Node[] list;
private int size; // The number of items in the list
private static final int N; // How much to resize the list by every time
public MyClass(){
list = new Node[N];
}
public void add(Node newNode){
if(size == list.length){
list = Arrays.copyOf(list, size + N);
}
list[size] = newNode;
size++;
}
TL; DR:我该怎么做N
?
答案 0 :(得分:6)
建议在调整大小时将数组的大小加倍。将尺寸加倍会导致线性时间成本摊销。
天真的想法是,调整大小值有两个成本:
如果要通过一次添加一个元素来重新调整数组大小,则内存开销为零,但复制成本变为二次方。如果要分配太多的插槽,复制成本将是线性的,但内存开销太大。
加倍导致线性摊销成本(即很长一段时间,复制成本与阵列大小成线性关系),并且保证不会浪费超过阵列的一半。
更新:顺便说一句,显然Java的ArrayList
扩展了(3/2)。这使得内存保守一点,但在复制方面要花费更多。使用基准测试不会受到影响。
MINOR Correction:加倍会使成本调整大小线性摊销,但会确保您有一个摊销的常数时间插入。检查CMU's lecture on Amortized Analysis。
答案 1 :(得分:5)
3/2很可能被选为“干净利落但小于phi”的东西。 2003年11月回归an epic thread on comp.lang.c++.moderated
,探讨 phi 如何在重新分配首次匹配的分配器时重新使用先前分配的存储。
首次提及 phi 对此问题的应用,请参阅post #7 from Andrew Koenig。
答案 2 :(得分:2)
如果你大致知道将要有多少项,那么将数组或ArrayList预分配到该大小,你将永远不必扩展。无与伦比的表现!
如果做不到这一点,实现良好摊销成本的合理方法是保持一定比例的抗冻性。 100%或50%是常见的。
答案 3 :(得分:2)
您应该将列表的大小调整为以前大小的倍数,而不是每次都添加常量。
例如:
newSize = oldSize * 2;
不
newSize = oldSize + N;
答案 4 :(得分:2)
每次需要调整大小时,大小加倍,除非您知道更多或更少是最好的。
如果内存不是问题,那么首先从一个大数组开始。
答案 5 :(得分:2)
您的代码似乎与ArrayList完全相同 - 如果您知道将使用大型列表,则可以在创建列表时将其传递给初始大小,并且完全避免调整大小。这个过程假设您需要原始速度和内存消耗不是问题。
答案 6 :(得分:1)
根据其中一个答案的评论:
问题在于记忆不是 问题,但我正在任意阅读 大文件。
试试这个:
new ArrayList<Node>((int)file.length());
您也可以使用您的阵列。然后在任何一种情况下都不应该调整大小,因为数组将是文件的大小(假设文件不长于int ...)。
答案 7 :(得分:0)
为了获得最佳性能,您需要尽可能少地调整大小。将初始大小设置为您通常需要的大小,而不是从N个元素开始。在这种情况下,您为N选择的值将更少。
如果您要创建大量不同大小的列表对象,那么您将需要使用基于池的分配器,而不是在退出之前释放内存。
要完全消除复制操作,可以使用数组列表
答案 8 :(得分:0)
这里有一个类比,很久很久以前,当我以前在大型机上工作时,我们使用了一个名为VSAM的文件系统,它要求你指定初始文件大小和所需的可用空间量。
每当自由空间的数量降到所需的阈值以下时,在程序继续处理时,所需的可用空间量将在后台分配。
看看是否可以在java中使用单独的线程来分配额外的空间并在主线程继续处理时将其“附加”到数组的末尾,这将会很有趣。