Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/309.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么这么慢?_Java - Fatal编程技术网

Java 为什么这么慢?

Java 为什么这么慢?,java,Java,函数ArrayList.add()运行速度非常快。我检查了源代码,发现实现是Arrays.copyOf() 有什么想法吗?如果您问为什么这两个循环的运行时间不同(第二个循环的运行时间要慢得多),原因是对列表的大多数调用。add(i,i)不需要重新创建备份数组 只有当备份阵列已满时,才会创建更大的阵列。而且较大的数组比原始数组大50%,因此它可以在其满之前处理对add的许多后续调用。如果您要问为什么这两个循环的运行时间不同(第二个循环要慢得多),原因是对list.add的大多数调用(i,i)无需重

函数
ArrayList.add()
运行速度非常快。我检查了源代码,发现实现是
Arrays.copyOf()


有什么想法吗?

如果您问为什么这两个循环的运行时间不同(第二个循环的运行时间要慢得多),原因是对
列表的大多数调用。add(i,i)
不需要重新创建备份数组


只有当备份阵列已满时,才会创建更大的阵列。而且较大的数组比原始数组大50%,因此它可以在其满之前处理对
add
的许多后续调用。

如果您要问为什么这两个循环的运行时间不同(第二个循环要慢得多),原因是对
list.add的大多数调用(i,i)
无需重新创建备份阵列


只有当备份阵列已满时,才会创建更大的阵列。而且较大的数组比原始数组大50%,因此它可以在其满之前处理对
add
的许多后续调用。

每次添加新元素时,您的代码都在调用
copyOf()

  • 第一次,没有元素需要复制,因为原始数组的长度为0
  • 第二次,必须复制一个元素
  • 第三次,两个要素
  • 第四次,三个要素
  • ……等等
因此,对于添加的每个元素,必须进行越来越多的工作才能复制以前的元素。因此,如果要添加
n
元素,则必须总共执行
1+2+3+…+(n-1)=n*(n-1)/2=n^2/2-n/2
单个元素的复制。因此,您的运行时与您添加的元素数的平方成比例

与此相对应的是,适当的方法是使用一个比您需要的更大的数组(这给了您添加更多元素的空间,而无需一直复制),并在每次需要扩展时将大小乘以一个固定因子。这要求您单独跟踪添加了多少元素,并向用户谎报您的真实大小。该系数通常小于2(Java代码使用1.5:
int newCapacity=oldCapacity+(oldCapacity>>1);
),但如果使用2,则数学更简单:

  • 初始数组大小是一些较小但非零的数字,例如4,因此您可以免费添加4个元素(分配和初始化数组的成本是4)
  • 对于第五个元素,将大小加倍为8,并复制4个旧元素;您现在可以再添加4个
  • 对于第九个元素,将大小加倍为16,并复制8个旧元素;您现在可以再添加8个
  • 以此类推:对于第n+1个元素,您将大小加倍为2*n,然后复制旧的n个元素,这为您提供了多容纳n个元素的空间

即使不评估复制的总和,我们也可以看到,每批n个新元素都已经通过复制之前的n个元素来“支付”,因此复制工作是线性的,而不是二次的。事实上,
4+4+8+16+…+n/2+n=2*n
(如果
n
是2的幂)。

每次添加新元素时,您的代码都在调用
copyOf()

  • 第一次,没有元素需要复制,因为原始数组的长度为0
  • 第二次,必须复制一个元素
  • 第三次,两个要素
  • 第四次,三个要素
  • ……等等
因此,对于添加的每个元素,必须进行越来越多的工作才能复制以前的元素。因此,如果要添加
n
元素,则必须总共执行
1+2+3+…+(n-1)=n*(n-1)/2=n^2/2-n/2
单个元素的复制。因此,您的运行时与您添加的元素数的平方成比例

与此相对应的是,适当的方法是使用一个比您需要的更大的数组(这给了您添加更多元素的空间,而无需一直复制),并在每次需要扩展时将大小乘以一个固定因子。这要求您单独跟踪添加了多少元素,并向用户谎报您的真实大小。该系数通常小于2(Java代码使用1.5:
int newCapacity=oldCapacity+(oldCapacity>>1);
),但如果使用2,则数学更简单:

  • 初始数组大小是一些较小但非零的数字,例如4,因此您可以免费添加4个元素(分配和初始化数组的成本是4)
  • 对于第五个元素,将大小加倍为8,并复制4个旧元素;您现在可以再添加4个
  • 对于第九个元素,将大小加倍为16,并复制8个旧元素;您现在可以再添加8个
  • 以此类推:对于第n+1个元素,您将大小加倍为2*n,然后复制旧的n个元素,这为您提供了多容纳n个元素的空间
即使不评估复制的总和,我们也可以看到,每批n个新元素都已经通过复制之前的n个元素来“支付”,因此复制工作是线性的,而不是二次的。事实上,
4+4+8+16+…+n/2+n=2*n
(如果
n
是2的幂)。

因为添加新元素时并不总是调用
grow()
ArrayList
始终增加50%

相关行为:

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
(oldCapacity>>1)
正在向右移位,因此除以2。这意味着
newCapacity
oldCapacity
的+50%。仅当超过此值时,才会进行下一次对
grow()
的调用。

因为添加新元素时并不总是调用
grow()
ArrayList
始终增加50%

相关行为:

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
(旧容量>>1)int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
1 + 2 + ... + n = n * (n + 1) / 2 = O(n^2)
int newCapacity = oldCapacity + (oldCapacity >> 1);
int newCapacity = (int) (oldCapacity * 1.5)
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
(3 * n - 2 * m) / n = 3 - 2 * m / n