【问题标题】:Does filterKeys cause a stack overflow?filterKeys 会导致堆栈溢出吗?
【发布时间】:2012-01-31 14:18:02
【问题描述】:

据我了解,MapLike 的方法 filterKeys 在原始地图上创建了一个 包装器。所以,如果我执行下面的代码m 将是一个 10K 包装器链和原始映射。

var m = Map(1 -> "一", 2 -> "二")
for(1 

现在我认为m.contains 的调用导致堆栈溢出,但它没有发生。你能解释一下在这种情况下发生了什么吗?

【问题讨论】:

    标签: scala collections stack-overflow


    【解决方案1】:

    我无法重现堆栈溢出,但发生了以下情况:

    override def filterKeys(p: A => Boolean): Map[A, B] = new DefaultMap[A, B] {
      override def foreach[C](f: ((A, B)) => C): Unit = for (kv <- self) if (p(kv._1)) f(kv)
      def iterator = self.iterator.filter(kv => p(kv._1))
      override def contains(key: A) = self.contains(key) && p(key)
      def get(key: A) = if (!p(key)) None else self.get(key)
    }
    

    请注意,没有值的复制:新类只是向四个方法添加规则,这些方法将改变它们的工作方式以反映您添加的过滤器。由于您反复申请filterKeys(10000 次),这意味着您有 10000 个类,每个类指向前一个,第一个指向您的原始地图。

    因此,在最终类上调用上述方法之一(直接或间接)将调用 10000 个嵌套方法,如果您的堆栈足够小,这肯定会产生堆栈溢出。

    【讨论】:

      【解决方案2】:

      我能够像这样 (Scala 2.9.1) 导致 Stack Overflow

      scala> var m = Map(1 -> "one", 2 -> "two")
      scala> for (_ <- 0 until 1000000) { m = m.filterKeys(_ % 2 == 0) }
      scala> m.contains(1)
      huge stack trace
      

      当然,您可以通过强制filterKeys 在每一步实际执行其工作来避免堆栈跟踪:

      scala> var m = Map(1 -> "one", 2 -> "two")
      scala> for (_ <- 0 until 1000000) { m = Map() ++ m.filterKeys(_ % 2 == 0) }
      scala> m.contains(1)
      res1: Boolean = false
      

      【讨论】:

        【解决方案3】:

        如果我逐字复制,您的循环只会执行 1 次。正因为如此,您只创建了一个包装器,所以原本打算成为 10000 个包装器的链只是一个 1 的链。这可能是一个错字,但是循环,

        for(1 <- 0 until 10000) {m = m.filterKeys(_%2 == 0)}
        

        应该是

        for(i <- 0 until 10000) {m = m.filterKeys(_%2 == 0)}
        

        除此之外,丹尼尔是对的; fitlerKeys 确实产生了本质上是包装器的东西。我花了超过 10k 次迭代,但我确实设法创建了 StackOverflowError。

        【讨论】:

        • 谢谢。我修正了您注意到的错字并运行循环更多迭代。现在堆栈溢出确实发生了。嗯,filterKeys 真的很危险……
        • @Michael:filterKeys 仅在您计划执行数千次且中间没有任何操作时才危险。在大多数情况下,它的惰性行为实际上是可取的。
        • 是的,这凸显了滥用函数式编程的可能性。在这种情况下,确保 m 始终是它自己的集合可能是个好主意,可能类似于m = Map() ++ m.filterKeys(_%2 == 0)
        猜你喜欢
        • 2011-02-08
        • 1970-01-01
        • 1970-01-01
        • 2016-02-09
        • 2015-05-21
        • 2014-02-14
        • 1970-01-01
        • 1970-01-01
        • 2015-12-08
        相关资源
        最近更新 更多