函数ArrayList.add()
的运行速度非常快。我检查了源代码,发现工具是Arrays.copyOf()
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
但是当我在代码中使用方法Arrays.copyOf()
时,它变得非常慢。您只需运行以下代码即可查看它:
public class TestArrayCopy {
public static void main(String[] args) {
long t = System.currentTimeMillis();
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
list.add(i, i);
}
System.out.println(System.currentTimeMillis() - t);
t = System.currentTimeMillis();
Integer[] array = new Integer[0];
for (int i = 0; i < 100000; i++) {
array = Arrays.copyOf(array, array.length +1);
array[i] = i;
}
System.out.println(System.currentTimeMillis() - t);
}
}
有什么想法吗?
答案 0 :(得分:3)
如果您要问两个循环为什么运行时间不同(第二个循环慢得多),则原因是大多数对list.add(i, i)
的调用都不需要重新创建后备数组。
仅当后备阵列已满时,才会创建更大的阵列。而且较大的数组比原始数组大50%,因此它可以在add
变满之前处理许多后续对import serial
from tkinter import *
import tkinter as ttk
from tkinter import *
top = Tk()
top.title("Tk dropdown example")
# Add a grid
mainframe = Frame(top)
mainframe.grid(column=10,row=10, sticky=(N,W,E,S) )
mainframe.columnconfigure(0, weight = 1)
mainframe.rowconfigure(0, weight = 1)
mainframe.pack(pady = 100, padx = 100)
# Create a Tkinter variable
tkvar = StringVar(top)
# Dictionary with options
choices = { '9600','19200','34800'}
tkvar.set('9600') # set the default option
popupMenu = OptionMenu(mainframe, tkvar, *choices)
Label(mainframe, text="Baudrate").grid(row = 1, column = 1)
popupMenu.grid(row = 2, column =1)
ard = serial.Serial('COM4', timeout=1)
ard.baudrate = drop
k = ard.readline().decode('ascii')
print(k)
# on change dropdown value
def change_dropdown(*args):
global drop;
drop = int(tkvar.get());
#ard.baudrate = drop;
print( drop )
return drop
# link function to change dropdown
tkvar.trace('w', change_dropdown)
top.mainloop()
的调用。
答案 1 :(得分:3)
您的代码每次添加新元素时都会调用copyOf()
:
因此,对于添加的每个元素,您都必须花更多的精力来复制以前的元素。因此,如果要添加n
个元素,则必须对单个元素执行总共1 + 2 + 3 + ... + (n - 1) = n * (n - 1) / 2 = n^2 / 2 - n / 2
个复制。因此,您的运行时与所添加元素数量的 square 成正比。
将此与适当的方法进行对比,即拥有比您所需的数组更大的数组(这使您有足够的空间添加更多元素而无需始终复制),并将大小乘以 每次需要扩展时固定的因子。这要求您分别跟踪已添加的元素数,并向用户撒谎以了解您的真实尺寸。该因子通常小于2(Java代码使用1.5:int newCapacity = oldCapacity + (oldCapacity >> 1);
),但是如果使用2:
即使不评估复制的总和,我们也可以看到,每批 n 个新元素都已经被以前的 n 的复制“支付”了元素,因此复制工作是 linear 而不是二次的。实际上,4 + 4 + 8 + 16 + ... + n / 2 + n = 2 * n
(如果n
是2的幂)。
答案 2 :(得分:0)
因为添加新元素时并非总是调用grow()
。 ArrayList
总是增加50%。
相关行如下:
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
(oldCapacity >> 1)
向右移一点,因此除以2。这意味着newCapacity
是oldCapacity
的+ 50%。仅在超出此限制时,才会下一次调用grow()
。
答案 3 :(得分:0)
Arrays.copyOf
与预期的一样慢。尽管List.add()
的速度与预期的一样快,但是由于Arrays.copyOf
方法中的grow
不会在每次调用add
方法时发生。 grow
仅在ArrayList
的功能不足时才会发生。
答案 4 :(得分:0)
区别是amortized runtime。每次创建新副本时,您的实现都会复制整个阵列。每个复制操作都是O(n)
,因此要花费整个操作的费用
1 + 2 + ... + n = n * (n + 1) / 2 = O(n^2)
另一方面, grow
以不同的方式增加大小。在此行中计算出数组的新大小:
int newCapacity = oldCapacity + (oldCapacity >> 1);
本质上可以归结为
int newCapacity = (int) (oldCapacity * 1.5)
所以一旦数组被填满,数组就会增长1.5倍
相比之下,这需要插入n
和初始大小为m
m + 1.5 * m + 1.5 ^ 2 * m + ... + n =
m + 1.5 * m + 1.5 ^ 2 * m + ... + 1.5 ^ log_1.5(n / m) * m =
m * (1 - 1.5 ^ (log_1.5(n / m) + 1) / (1 - 1.5) =
m * 2 * (n / m * 1.5 - 1) =
3 * n - 2 * m
摊销的运行时间可以计算为n
插入的总成本除以n
:
(3 * n - 2 * m) / n = 3 - 2 * m / n
由于在您的情况下m < n
的第二部分变得可以忽略不计,所以我们最终将O(1)
作为摊销运行时以及所有O(n)
插入的运行时与O(n^2)
不同之处在于每次插入后都复制整个数组。