Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/384.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_Memoization - Fatal编程技术网

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++) {

我在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++) {
        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的大小?

我首先想到的是:

  • HashMap顾名思义就是一个基于哈希的映射。所以,无论你把什么东西放进去或从中得到什么东西,它都必须散列密钥,然后根据散列找到目标
  • put()操作也不仅仅是在公园里散步,您可以通过检查了解它的功能。肯定比数组赋值更重要
  • 在java中,它不适用于原语,因此对于每个值,必须将整数转换为整数,反之亦然。(正如其他人所指出的,有int专用映射选项可用,但不在标准库中)
  • aa由于您没有初始化它,它可能需要在运行期间在内部调整几次大小-hashmap的默认大小只有16-这肯定比您使用array进行的一次性初始化更昂贵
  • 它还可以处理每个内部条目所需的条目对象,所有这些对象也占用了一些空间,远远不止一个整数数组
  • 所以我不认为hashmap可以节省空间和时间。为什么会这样

    然后我觉得初始化整个备忘录数组是在浪费时间

    您可以存储'answer+1',这样默认值(0)现在可以作为'not computed yet'的占位符,并保存该初始化。并不是说它很贵。让我们深入研究缓存页

    缓存页 CPU是复杂的野兽。它们不直接对内存进行操作;不再是了。他们实际上无法做到这一点;芯片的计算部分根本没有连接。完全取而代之的是,CPU有缓存,缓存的大小是固定的(例如,64k—您不能让一个缓存节点的容量大于或小于64k,整个64k被认为是主内存中某个64k段的缓存副本)。一个这样的节点称为缓存页

    CPU只能在缓存页上运行

    在java中,
    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)