【问题标题】:Does Java optimize the creation of strings at runtime?Java 是否会在运行时优化字符串的创建?
【发布时间】:2011-06-11 09:20:52
【问题描述】:

假设我在运行时创建了以下两个字符串(例如来自用户输入):

public void someMethod(String input) {

   if ( input == null ) return;

   String a = input + input;
   String b = input;

   ...

}

Java(及其编译器)是否足够聪明,可以在运行时检测到b 包含在a 中,因此不需要为b 分配内存? b 可以只指向一半长度的a 吗?

也就是说,Java 是否实现了String.intern() 的动态版本?

编辑

考虑到到目前为止的答案,我的例子应该是:

public void someMethod(String input) {

   if ( input == null ) return;

   String a = input + input + input;
   String b = input + input;

   ...

}

【问题讨论】:

    标签: java string optimization runtime


    【解决方案1】:

    在您的示例中,您实际上并没有创建两个字符串:b 只是对input 的引用。因此,要按照您的要求进行操作,Java 将不得不在创建新字符串时以某种方式返回并更改旧字符串(例如通过说 input + input)。

    为了回答您更广泛的问题,AFAIK 两个字符串共享内存的唯一方法(此外,正如您提到的,是intern()'d)是使用substring() 创建一个或两个字符串。所以如果你真的想节省内存,你可以这样做:

    String a = input + input;
    String b = a.substring(input.length());
    

    (需要明确的是,这只会在 b 的值存储在某处但 input 被丢弃并最终被垃圾回收时节省内存。)

    编辑

    新问题和改进问题的新示例:

    String a = input + input + input;
    String b = a.substring(2 * input.length());
    

    (请注意,与问题中的第二个示例相比,这将始终节省内存,因为我们完全避免了分配。所以前面的警告不适用。)

    【讨论】:

      【解决方案2】:

      不,这不会发生,因为作为参数传入的字符串已经存在,并且字符串是不可变的。双倍长度的必须单独创建,因为原始数组没有空间容纳更多字符。

      但是,在相反的情况下——从长字符串开始,然后使用substring() 创建一个较短的字符串——这两个字符串实际上将共享同一个 char 数组。

      【讨论】:

      • 冒着迂腐的风险(还有什么用处?:-)),JVM 可能理论上会在运行时搞乱字符串的内部表示,所以只要它仍然执行不可变,但 AFAIK 没有人这样做。 (除了我认为hashCode() 的结果被缓存了,至少是由 Sun 缓存的。但这是对字符数据的实际存储的辅助。)
      • @Luke:这是绝对正确的,它可以,这样做会减少最终的内存使用。但它不会减少 peak 内存使用——在某些时候你仍然必须将两个数组都放在内存中——而且它不会减少分配的数量,因为什么都不会让您避免创建第二个数组。但是您可以安排事情,以便在最后,您可以 GC 较小的数组,如果您真的非常想这样做 - 当然,除非有一些 previous这种操作导致 那个 数组已经被共享...
      • 哦,当然,这就是疯狂所在……这就是为什么没有人这样做 :-) 除了难度之外,在我看来,编译器不太可能提供几乎与程序员可以在必要时明智地使用substring()intern()
      【解决方案3】:

      正如其他人指出的那样,b 只是指向与input 相同的字符串。

      至于a,我们可以使用javap来反汇编你的代码:

      public static void someMethod(java.lang.String);
        Code:
         0:   aload_0
         1:   ifnonnull   5
         4:   return
         5:   new #4; //class java/lang/StringBuilder
         8:   dup
         9:   invokespecial   #5; //Method java/lang/StringBuilder."<init>":()V
         12:  aload_0
         13:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
         16:  aload_0
         17:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
         20:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
         23:  astore_1
         24:  aload_0
         25:  astore_2
         26:  return
      

      编译器使用StringBuilder.append() 执行连接,这(至少在Sun/OpenJDK 中)只是将所有内容复制到一个大的char 数组中——这里没有intern()ing。同样适用于您的编辑 - 输入字符串总共附加了 5 次。

      我可以想象可以编写 String(Builder|Buffer) 的替代实现来维护数组链,这在某些情况下可以实现更好的重用和效率。

      【讨论】:

      • 如果您使用该参数,我们可以简单地证明“java”没有内联函数,没有 CSE 或其他任何东西。 javac 没有做任何有趣的优化吗?是的。这是否意味着 JIT 并没有优化很多东西——而且我认为它在运行时做了一些实习字符串(尽管不要把我钉在上面,上面的情况几乎不是最好的例子)——不。
      • 我所说的一切都不排除运行时优化。但是 Hotspot 最初会按原样解释字节码,直到它确定一个方法被足够频繁地调用在某些上下文中以至于内联、JIT 编译等是值得的 - 所以上面分析的代码 i> 至少在最初被执行。
      • 公平点,因为“java”是一个有点模糊的术语。也许我应该更清楚一点,编译器并不是 Java 优化的万能的,尽管它是我们唯一可以有信心推理的部分。
      • 是的,如果你以这种方式制定我会同意的。 Iirc Hotspot VM 在运行时进行了一些字符串内联,但我同意对 JIT 优化函数做出明确的陈述并不容易,并且可以从一个 VM 更改为另一个 VM(今天不再是真正的问题)甚至从一个版本更改为另一个版本(尽管我'希望他们不会在没有充分理由的情况下删除功能)。
      【解决方案4】:

      不,编译器足够智能,可以在编译时检测字符串连接,例如:

             String a = "hello, " + "world";
             //Will become
             String a = "hello, world";
      

      但是每次创建字符串时,子字符串匹配都需要做很多事情。 但是 Java 会尽量避免创建多个字符串(如果两个字符串相等),您应该检查 String.intern()

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-09
        • 2011-08-20
        • 1970-01-01
        • 2012-08-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多