打开字符串

2022-09-04 03:59:10

我很好奇Java和Scala是如何在字符串上实现开关的:

class Java
{
    public static int java(String s)
    {
        switch (s)
        {
        case "foo": return 1;
        case "bar": return 2;
        case "baz": return 3;
        default: return 42;
        }
    }
}
object Scala {
  def scala(s: String): Int = {
    s match {
      case "foo" => 1
      case "bar" => 2
      case "baz" => 3
      case _ => 42
    }
  }
}

看起来Java打开了哈希码,然后进行了单个字符串比较:

 0: aload_0       
 1: dup           
 2: astore_1      
 3: invokevirtual #16    // Method java/lang/String.hashCode:()I
 6: lookupswitch  { // 3
           97299: 40
           97307: 52
          101574: 64
         default: 82
    }
40: aload_1       
41: ldc           #22    // String bar
43: invokevirtual #24    // Method java/lang/String.equals:(Ljava/lang/Object;)Z
46: ifne          78
49: goto          82
52: aload_1       
53: ldc           #28    // String baz
55: invokevirtual #24    // Method java/lang/String.equals:(Ljava/lang/Object;)Z
58: ifne          80
61: goto          82
64: aload_1       
65: ldc           #30    // String foo
67: invokevirtual #24    // Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifne          76
73: goto          82
76: iconst_1      
77: ireturn       
78: iconst_2      
79: ireturn       
80: iconst_3      
81: ireturn       
82: bipush        42
84: ireturn       

相比之下,Scala似乎与所有情况进行了比较:

 0: aload_1       
 1: astore_2      
 2: ldc           #16    // String foo
 4: aload_2       
 5: invokevirtual #20    // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
 8: ifeq          16
11: iconst_1      
12: istore_3      
13: goto          47
16: ldc           #22    // String bar
18: aload_2       
19: invokevirtual #20    // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
22: ifeq          30
25: iconst_2      
26: istore_3      
27: goto          47
30: ldc           #24    // String baz
32: aload_2       
33: invokevirtual #20    // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
36: ifeq          44
39: iconst_3      
40: istore_3      
41: goto          47
44: bipush        42
46: istore_3      
47: iload_3       
48: ireturn       

是否有可能说服Scala使用哈希码技巧?我宁愿更喜欢O(1)解而不是O(n)解。在我的实际代码中,我需要与33个可能的关键字进行比较。


答案 1

当然,这种情况似乎是Scala编译器缺乏优化。当然,这个结构比Java中的switch/case强大得多,而且优化它要困难得多,但它可以检测到这些特殊情况,在这些特殊情况下,简单的哈希比较将适用。match

另外,我不认为这种情况会在惯用的Scala中多次显示,因为你总是与除了具有不同值之外具有一些意义的案例类相匹配。


答案 2

我认为问题在于你正在从Java的角度考虑Scala(我认为你也过早地优化了,但是嘿)。

我认为你想要的解决方案是记住你的映射。你有一个从 String -> Int 映射的函数,对吧?所以这样做:

class Memoize1[-T, +R](f: T => R) extends (T => R) {
  import scala.collection.mutable
  private[this] val vals = mutable.Map.empty[T, R]

  def apply(x: T): R = {
    if (vals.contains(x)) {
      vals(x)
    }
    else {
      val y = f(x)
      vals += ((x, y))
      y
    }
  }
}

object Memoize1 {
  def apply[T, R](f: T => R) = new Memoize1(f)
}

(此记忆代码取自此处

然后,您可以像这样记住您的代码:

object Scala {
  def scala(s: String): Int = {
    s match {
      case "foo" => 1
      case "bar" => 2
      case "baz" => 3
      case _ => 42
    }
  }

  val memoed = Memoize1(Scala.scala)

  val n = memoed("foo")
}

多田!现在,您正在进行哈希值比较。虽然我要补充一点,大多数备忘录示例(包括这个)都是玩具,并且不会在大多数用例中幸存下来。现实世界的记忆应该包括你愿意缓存的量的上限,如果你的代码有少量可能的有效案例和大量的无效案例,我会考虑做一个通用类,预先构建地图,并有一个专门的查找,说: “在我的缓存中,你赢了,而不是在我的缓存中,默认”,这可以通过调整memoizer来非常轻松地完成,以获取输入来预缓存并更改“不在缓存中”代码以返回默认值。List