【问题标题】:String s1 == String s2 (true) but FieldOffset is differentString s1 == String s2 (true) 但 FieldOffset 不同
【发布时间】:2013-05-27 10:24:10
【问题描述】:

在学习 java 时,我了解到比较 2 个字符串的正确方法是使用 equals 而不是“==”。这一行

静态字符串 s1 = "a"; 静态字符串 s2 = "a"; System.out.println(s1 == s2);

将输出 true,因为 jvm 似乎已经优化了此代码,以便它们实际上指向相同的地址。我试图用我在这里找到的一篇很棒的帖子来证明这一点

http://javapapers.com/core-java/address-of-a-java-object/

但地址似乎不一样。我错过了什么?

导入 sun.misc.Unsafe; 导入 java.lang.reflect.Field; 公共类 SomeClass { 静态字符串 s1 = "a"; 静态字符串 s2 = "a"; 公共静态 void main (String args[]) 抛出异常 { System.out.println(s1 == s2); //真的 不安全不安全 = getUnsafeInstance(); 字段 s1Field = SomeClass.class.getDeclaredField("s1"); System.out.println(unsafe.staticFieldOffset(s1Field)); //600 字段 s2Field = SomeClass.class.getDeclaredField("s2"); System.out.println(unsafe.staticFieldOffset(s2Field)); //604 } private static Unsafe getUnsafeInstance() 抛出 SecurityException, NoSuchFieldException,IllegalArgumentException,IllegalAccessException { 字段 theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafeInstance.setAccessible(true); return (Unsafe) theUnsafeInstance.get(Unsafe.class); } }

【问题讨论】:

标签: java


【解决方案1】:

你没有错过任何东西。 Unsafe 库正在报告实际发生的情况。

字节码:

static {};
  Code:
   0:   ldc #11; //String a
   2:   putstatic   #13; //Field s1:Ljava/lang/String;
   5:   ldc #11; //String a
   7:   putstatic   #15; //Field s2:Ljava/lang/String;
   10:  return

请注意,两个字符串都放在内存中的不同位置,13 和 15。

变量在内存中的存储位置(需要单独的地址)与是否将新对象放入堆中是有区别的。在这种情况下,它为两个变量分配了两个单独的地址,但它不需要创建一个新的字符串对象,因为它可以识别相同的字符串字面量。所以此时两个变量都引用了同一个字符串。

如果你想得到地址,你可以使用在这个问题中找到的答案,How can I get the memory location of a object in java?。确保您在使用前阅读了注意事项,但我做了一个快速测试,它似乎有效。

【讨论】:

  • 但是如果他们在不同的位置,为什么==返回true?
  • @aardvarkk 因为 staticFieldOffset 为您提供了s1s2SomeClass 中的位置。这些是不同的引用,它们甚至有不同的名称,s1s2。然而,这两个引用都指向同一个字符串对象,这就是== 将评估的事实。
  • @greedybuddha 您写道“但是这两个引用都指向同一个字符串对象”。我知道这是准确的,但是有没有办法通过输出一些地址来证明它(不查看字节码)。
  • 查看我的更新答案,我将答案与代码链接以获取地址
【解决方案2】:

在上面的代码中,您不是比较字符串的地址,而是比较它们的“存储分配中给定字段的位置”,即持有对(相同)字符串的引用的变量的位置。

【讨论】:

    【解决方案3】:

    我认为您对 staticFieldOffset 返回的内容感到困惑。它将 pointer 的偏移量返回到 String 实例,而不是 String 本身的地址。因为有两个字段,所以它们有不同的偏移量:即两个指针,它们恰好具有相同的值。

    仔细阅读Unsafe javadoc 可以看出:

    报告给定字段在其存储分配中的位置 班级。不要期望对此执行任何类型的算术运算 抵消;它只是一个传递给不安全堆内存的 cookie 访问器。

    换句话说,如果您知道实际的 Class 实例在内存中的位置,那么您可以将此方法返回的偏移量添加到该基地址,结果将是您可以在内存中找到的位置指向String 的指针的值。

    【讨论】:

    • @AndrewW 至少在你玩 Unsafe 之前!
    • @AndrewW 了解 C 确实非常重要,因为大多数当前的编程语言都从 C 派生了许多语义和机制。但那是 C,而不是 C++。像那样混合它们不是很准确。相反,C++“教”了太多 C++ 特有的东西。
    • @Theodoros 我不是指语义。 (尽管我同意它对此很有用。)我的意思是学习指针是如何工作的,而 C 和 C++ 都适用于此。
    • @AndrewW 哦,是的,为了理解偏移是如何工作的,我绝对同意。无论语言如何,每个人都应该了解指针运算及其应用(即使它是“隐式”完成的)。
    【解决方案4】:

    Java 代码中声明的字符串会自动interned

    因此结果与您手动调用String.intern() 相同。

        String a = "aa";
        String b = new String(a);
        System.out.println("aa" == "aa");
        System.out.println(a == b);
        System.out.println(a.equals(b));
        System.out.println(a.intern() == b.intern());
    

    输出:

    是的

    是的

    是的

    【讨论】:

    • 问题是关于 FieldOffset
    猜你喜欢
    • 2019-11-29
    • 1970-01-01
    • 1970-01-01
    • 2013-10-20
    • 1970-01-01
    • 2013-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多