【问题标题】:Is Java String really immutable?Java String 真的不可变吗?
【发布时间】:2010-10-25 03:25:02
【问题描述】:

在字符串源中,看起来哈希码值(私有 int hashCode)仅在方法 public int hashCode() 至少被调用一次时才被设置。这意味着不同的状态。但是下面的例子中会设置hashCode吗:

String s = "ABC"; ? 

String s = "ABC"; 
s.hashCode();

对后续比较性能有帮助吗?

【问题讨论】:

标签: java


【解决方案1】:

不可变意味着,从外部角度来看,对象的值不能改变。

如果hashCode被缓存,对象的内部状态在第一次hashCode调用后可能会有所不同。但该调用是只读的,您无法更改对象在外界看来的值。

换句话说,它仍然是同一个字符串。

【讨论】:

  • 来自维基百科:“不可变对象是一个对象,其状态在创建后无法修改”。 en.wikipedia.org/wiki/Immutable_object
  • 叹息。来自同一个 Wikipedia 页面:“在某些情况下,即使某些内部使用的属性发生了变化,但对象的状态从外部角度来看似乎是不变的,但对象被认为是不可变的。例如,使用记忆的对象缓存昂贵计算的结果仍然可以被视为不可变对象。” hash 值的缓存是经典的记忆。
  • 叹息。从外部角度可以观察到性能。
  • @Alex - SIGH ...但这不是对象状态的可观察到的变化,
  • 叹息。如果它像一个不可变对象一样行走,并且像一个不可变对象一样嘎嘎作响,那么它就是一个不可变对象。性能与不变性无关。
【解决方案2】:

技术上字符串已更改。第一次调用 hashCode() 时,某些内部字段已更改。

但出于所有实际原因,字符串是不可变的。哈希码的值不会因为调用hashCode() 而改变,它只是在第一次调用之前不会计算。对于字符串的任何使用者,该字符串是不可变的。这才是最重要的。

【讨论】:

    【解决方案3】:

    正如 Robert Harvey 和 Greg 所说,出于所有实际目的,String 对象是不可变的(可以通过反射来更改内容,但这是一种 hack)。

    在构造完成后立即调用hashCode()可能有助于提高理论性能。但是,出于所有实际目的,这是过早的优化

    【讨论】:

    • 事实上,在构造之后调用 hashCode 更有可能对性能产生负面影响。第一次调用 .hashCode() 时,无论如何都会填充私有字段;并且您实际上可能会受益于根本不必计算 hashCode。
    • 事实上,在这种特定情况下,优化远非为时过早。那是一个巨大的 12 年历史的应用程序。不幸的是,工作速度不够快。一个相同的字符串被比较了数百万次(我自己会使用整数,但这就是生活)。第二个比较字符串来自遥远的交易,你敢打赌它里面没有hasCode。
    • 现在还为时过早 - 如果比较这个字符串数百万次,那么第一次比较是否比其他所有比较慢一点(数百万次 - 1)无关紧要。第一次比较有点慢的事实可以忽略不计。
    • 您假设在第一次比较之后设置了hashCode?不是这样。
    • “比较”是指调用 hashCode(),而不是调用 equals()。 docjar.com/html/api/java/lang/String.java.html#1041
    【解决方案4】:

    但是下面的例子中会设置hashCode吗?

    没有。

    [调用 hashCode] 对后续比较性能有帮助吗?

    假设您的意思是后续调用String.equals(Object),答案是“否”。 equals 方法不使用 String 的 hash 值,无论之前是否计算过。

    如果您指的是对String.hashCode() 的调用,答案是“可能不是”。最多你会得到一个一次性的计算,而不是迟早发生。 hashCode 方法仍然需要在每次调用时测试 hash 是否为零。

    编辑

    我认为很明显不同的 JVM 供应商实现 String.equals 的方式不同。例如,@Alex 引用的 IBM 版本使用了缓存的 hashcode,但 Sun 的 JDK 1.6 中的版本没有。

    由此我们得出结论,任何调用String.hashCode() 以“优化”String.equals 的尝试都会产生依赖于JVM 的结果。此外,对于 @Alex 正在使用的特定 IBM JVM,它看起来可能是有益的......前提是您已经为两个字符串都做了。

    但我仍然认为这是一个坏主意......除非你从分析中得到明确的证据表明String.equals() 是一个重要的瓶颈。

    【讨论】:

    • Java 标准可能未定义此行为。但是,如果两个字符串都设置了哈希值,则我使用的 String 的实现会使用它。
    • @Alex - OpenJDK 1.6 中 String 的实现没有。你用的是什么版本?
    • 我使用的是 RAD 的 IBM 版本
    • /* * 许可材料 - IBM 的财产,* IBM Corp. 1998, 2007 版权所有 */
    • public boolean equals (Object object) { if (object == this) return true; if (object instanceof String) { String s = (String)object; if (count != s.count || (hashCode != s.hashCode && hashCode != 0 && s.hashCode != 0)) 返回假;返回 regionMatches(0, s, 0, count); } 返回假; }
    【解决方案5】:
    1. 没有。没有人调用它,所以它没有被调用。

    2. 没有。多调用一个方法怎么会更快?

    【讨论】:

    • 提问者可能意味着“在随后的通话中更快,但以牺牲设置速度为代价”。
    • 确实是这个意思。但他仍在放慢他的整体计划。那么有什么区别呢?
    • @EJP 你假设他的程序是线性的。如果后续调用有显着差异(一个很大的“if”),那么当其他东西成为瓶颈时,可以首先进行方法调用,例如在等待磁盘访问时。然后,如果稍后调用 hashCode,在 CPU 密集型代码区域中,可以获得整体改进。理论上。
    • @Grundlefleck 我不明白。两次调用一个方法不会比调用一次更快。
    • @EJP 不,它不能,但是在您的程序中的某一点进行较慢的调用以使其在另一点更快可能会缩短 net 时间。这是相当迟钝的,但如果你在内存中有一百万个对象,这些对象都将在循环中调用它们的 hashCode,在你的程序等待文件加载时预先填充它们的 hashCode,可以加快循环。不太可能,但可能:)
    【解决方案6】:

    这是实际的 String.hashCode() 实现:

    public int hashCode() {
    int h = hash;    // hash is a field in String
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;
    
            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }
    

    所以:

    1. 字符串是不可变的,你不能改变它的值。您只能创建一个新字符串。
    2. 字符串哈希码在首次使用 String.hashCode() 时计算。稍后的调用返回预先计算的值:
    3. 是的,它会更快。如果您调用 .hashCode(),那么后续比较会在一次性计算哈希码的一小段时间内更快。问题是这是否重要。做你自己的基准测试。

    【讨论】:

    • 看我的回答。额外的时间调用一个方法怎么可能更快?
    • 总时间显然是相同的,但随后的比较会稍微快一些(就像我写的那样,一次计算哈希码)。这就是原始海报所要求的。
    猜你喜欢
    • 2011-05-06
    • 1970-01-01
    • 2011-03-24
    • 1970-01-01
    • 2015-07-10
    • 1970-01-01
    • 2019-08-20
    • 1970-01-01
    • 2019-04-06
    相关资源
    最近更新 更多