Java 为什么使用数组比使用映射的记忆速度更快?
我在leetcode(#377)上求解组合和IV,其内容如下: 给定一个包含所有正数且无重复数的整数数组,请查找可能的组合数,这些组合加起来就是一个正整数目标 我在Java中使用自顶向下的递归方法和记忆数组解决了这个问题:Java 为什么使用数组比使用映射的记忆速度更快?,java,memoization,Java,Memoization,我在leetcode(#377)上求解组合和IV,其内容如下: 给定一个包含所有正数且无重复数的整数数组,请查找可能的组合数,这些组合加起来就是一个正整数目标 我在Java中使用自顶向下的递归方法和记忆数组解决了这个问题: public int combinationSum4(int[] nums, int target){ int[] memo = new int[target+1]; for(int i = 1; i < target+1; i++) {
public int combinationSum4(int[] nums, int target){
int[] memo = new int[target+1];
for(int i = 1; i < target+1; i++) {
memo[i] = -1;
}
memo[0] = 1;
return topDownCalc(nums, target, memo);
}
public static int topDownCalc(int[] nums, int target, int[] memo) {
if (memo[target] >= 0) {
return memo[target];
}
int tot = 0;
for(int num : nums) {
if(target - num >= 0) {
tot += topDownCalc(nums, target - num, memo);
}
}
memo[target] = tot;
return tot;
}
public-int-combinationSum4(int[]nums,int-target){
int[]备忘录=新int[目标+1];
对于(int i=1;i=0){
返回备忘录[目标];
}
int-tot=0;
for(int-num:nums){
如果(目标-数量>=0){
tot+=topDownCalc(nums,target-num,memo);
}
}
备忘录[目标]=总目标;
返回tot;
}
然后我发现初始化整个memo数组是在浪费时间,可以只使用一个Map(这也可以节省空间/内存)。因此,我将代码改写如下:
public int combinationSum4(int[] nums, int target) {
Map<Integer, Integer> memo = new HashMap<Integer, Integer>();
memo.put(0, 1);
return topDownMapCalc(nums, target, memo);
}
public static int topDownMapCalc(int[] nums, int target, Map<Integer, Integer> memo) {
if (memo.containsKey(target)) {
return memo.get(target);
}
int tot = 0;
for(int num : nums) {
if(target - num >= 0) {
tot += topDownMapCalc(nums, target - num, memo);
}
}
memo.put(target, tot);
return tot;
}
public-int-combinationSum4(int[]nums,int-target){
Map memo=newhashmap();
备忘录.付诸表决(0,1);
返回topDownMapCalc(nums、target、memo);
}
公共静态int-topDownMapCalc(int[]nums,int-target,Map-memo){
if(备忘录容器(目标)){
返回备忘录。获取(目标);
}
int-tot=0;
for(int-num:nums){
如果(目标-数量>=0){
tot+=topDownMapCalc(nums,target-num,memo);
}
}
备忘录.投入(目标,总目标);
返回tot;
}
但我还是感到困惑,因为在提交了我的代码的第二个版本后,Leetcode说它比第一个代码更慢,占用了更多的空间。HashMap如何比数组使用更多的空间并运行得更慢?谁的值都必须初始化,谁的长度大于HashMaps的大小?我首先想到的是:
int[]
会产生一个连续的、直接大小的表示数据的内存块。换句话说,一个int[]x=new int[1000]
将声明一个1000*4=4000字节的内存块(因为int是4字节,您为1000个em预留了空间)。可以放在一页内。因此,当您编写循环以将值初始化为-1时,这要求CPU循环通过单个缓存页并向其中写入一些数据。CPU有管道和其他加速因素;这可能需要250个周期
与获取缓存页的成本形成对比:CPU将摆弄它的拇指(这很好;它可以冷却一些,在现代硬件上,CPU通常不受其原始速度能力的限制,而是受系统吸走运行时产生的热影响的能力的限制!-它还可以在其他线程/进程上花费时间)同时,它将把一些内存块提取到缓存页中的任务转移到内存控制器。尽管如此,拇指旋转的数量级为500个周期或更多。CPU在运行期间能够冷却或专注于其他事情是很好的,但在紧循环中写入4000个连续字节的速度仍然比单个缓存未命中要快
因此,“用-1填充1000个大整数数组”是一种非常便宜的操作
包装器对象
映射操作对象,而不是整数,这就是为什么必须编写Integer
而不是int
。一个整数
,至少在内存中,是一个非常非常大的内存负载。它是一个完整的对象,包含一个int字段。然后,您的变量(或映射)持有指向它的指针
因此,一个int[]x=new int[1000]
需要4000字节,加上对对象头的一些更改(可能全部添加12个字节),以及一个引用(取决于VM,但假设为64位),总共4020字节
相比之下
Integer[] x = new Integer[1000];
for (int i = 0; i < 1000; i++) x[i] = i;`
Integer[]x=新整数[1000];
对于(inti=0;i<1000;i++)x[i]=i`
要大得多。它有1000个指针(每个指针可以大到8字节,或者小到4.s)