web-dev-qa-db-ja.com

StringのhashCode()が0をキャッシュしないのはなぜですか?

Java 6のStringのソースコードで、hashCodeが0以外の値しかキャッシュしないことに気付きました。パフォーマンスの違いは、次のスニペットによって示されています。

public class Main{
   static void test(String s) {
      long start = System.currentTimeMillis();
      for (int i = 0; i < 10000000; i++) {
         s.hashCode();
      }
      System.out.format("Took %d ms.%n", System.currentTimeMillis() - start);
   }
   public static void main(String[] args) {
      String z = "Allocator redistricts; strict allocator redistricts strictly.";
      test(z);
      test(z.toUpperCase());
   }
}

ideone.comでこれを実行する は、次の出力を提供します。

Took 1470 ms.
Took 58 ms.

だから私の質問は:

  • StringのhashCode()が0をキャッシュしないのはなぜですか?
  • Java文字列が0にハッシュされる確率はどれくらいですか?
  • 0にハッシュする文字列について、毎回ハッシュ値を再計算するというパフォーマンスのペナルティを回避する最良の方法は何ですか?
  • これは値をキャッシュする最良の方法ですか? (つまり、1つを除くすべてをキャッシュしますか?)

あなたの娯楽のために、ここの各行は0にハッシュする文字列です:

pollinating sandboxes
amusement & hemophilias
schoolworks = perversive
electrolysissweeteners.net
constitutionalunstableness.net
grinnerslaphappier.org
BLEACHINGFEMININELY.NET
WWW.BUMRACEGOERS.ORG
WWW.RACCOONPRUDENTIALS.NET
Microcomputers: the unredeemed Lollipop...
Incentively, my dear, I don't tessellate a derangement.
A person who never yodelled an apology, never preened vocalizing transsexuals.
75

あなたは何も心配していません。この問題について考える方法は次のとおりです。

あなたが一年中文字列のハッシュを囲んでいるだけのアプリケーションがあるとします。 1000の文字列をすべてメモリ内で受け取り、それらに対してhashCode()をラウンドロビン方式で100万回繰り返し呼び出し、その後、さらに1000の新しい文字列を取得して、それを再度実行するとします。

また、文字列のハッシュコードがゼロになる可能性が、実際には1/2 ^ 32よりはるかに大きいと仮定します。それはsomewhatが1/2 ^ 32よりも大きいと確信していますが、1/2 ^ 16のようにそれよりもはるかに悪いとしましょう(平方根!今ではそれはずっと悪いです!).

この状況では、Oracleのエンジニアがこれらの文字列のハッシュコードをキャッシュする方法を改善することで、他の誰よりも多くのメリットを得られます。だからあなたは彼らに手紙を書いて、それを修正するように頼みますそして、s.hashCode()がゼロのときは常にが瞬時にを返すように魔法をかけます(初めてでも100%改善されます!)。そして、他のケースではパフォーマンスをまったく低下させることなくこれを行うとしましょう。

やったー!これであなたのアプリは...見てみましょう... 0.0015%速くなりました!

以前は丸一日かかっていたものが、今では23時間57分48秒しかかかりません。

そして、多くの場合滑稽な程度で、疑いのすべての可能な利益を与えるようにシナリオを設定しました。

これはあなたにとって価値があるように見えますか?

編集:これを数時間前に投稿してから、プロセッサの1つでワイルドコードを実行し、ハッシュコードが0の2ワードのフレーズを探しました。これまでに考え出されたのは、ベゾトルゾリージョ、クロノグラムミックシュトフ、破壊的な回廊のようなもの、クリーシャックオーガンジン、ドラムウッドボールダーヘッド、電気分析のエクササイズ可能、そして非常に複雑なものです。これは約2 ^ 35の可能性から外れているので、完全な分布で​​は8しか表示されないと予想されます。さらに重要なことは、いくつかの興味深いバンド名/アルバム名を思いついたことです!公正な盗難はありません!

55

「ハッシュコードをまだ計算していない」ことを示すために0を使用します。別の方法としては、別のブール値フラグを使用して、より多くのメモリを必要とします。 (もちろん、ハッシュコードをまったくキャッシュしないようにするためです。)

多数文字列は0にハッシュされるとは思いません。おそらく、ハッシングルーチンが意図的に0を回避することは理にかなっています(たとえば、0のハッシュを1に変換し、それをキャッシュします)。それは衝突を増やしますが、再ハッシュを避けます。ただし、String hashCodeアルゴリズムが明示的に文書化されているため、これを行うのは遅すぎます。

これが一般的に良いアイデアであるかどうかについては、確かに効率的なキャッシングメカニズムであり、最終的にハッシュ値が0になる値の再ハッシュを回避するために、変更によりmight(編集を参照)の方がさらに優れています。個人的には、Sunが最初にこれを行う価値があると考えたデータに興味があります。これは、これまでに作成されたすべての文字列に対して追加の4バイトを消費しますが、ハッシュされることが多いため、文字列の場合のみの利点がありますハッシュ化されます2回以上

編集:KevinBが他の場所でコメントで指摘しているように、上記の「0を避ける」の提案は、非常にまれなの場合に役立ちますが、追加が必要なため、正味コストを含む可能性がありますeveryハッシュ計算の比較。

24
Jon Skeet

これは セキュリティの脆弱性 に関連する良い質問であることが判明しました。

"文字列をハッシュするとき、Javaはハッシュ属性のハッシュ値もキャッシュしますが、結果がゼロとは異なる場合のみです。したがって、ターゲット値ゼロは、攻撃者にとって特に興味深いものです。キャッシングを防ぎ、再ハッシュを強制します。」

5
cdunn2001

さて皆さん、長さがゼロなら結局ゼロになるので、それはゼロを維持します。

そして、lenがゼロであること、そしてハッシュコードがゼロであることを理解するのに時間がかかりません。

だから、あなたのコードレビューのために! Java 8栄光です:

 public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

ご覧のとおり、文字列が空の場合は常にクイックゼロが返されます。

  if (h == 0 && value.length > 0) ...
0
The Coordinator

「0を回避」の提案は、書き込み前のブランチ操作のわずかなコストで、本当の問題(攻撃者が提供する可能性のある構築可能なケースでの予期しないパフォーマンスの低下)に役立つため、ベストプラクティスとして推奨するのが適切と思われます。設定されたものだけが特別な調整値にハッシュされる場合に実行できる「予期しないパフォーマンス低下」がいくつか残っています。しかし、これは最悪の場合、無制限ではなく2倍の低下です。

もちろん、Stringの実装は変更できませんが、問題を永続させる必要はありません。

0
Mike Liddell
  • StringのhashCode()が0をキャッシュしないのはなぜですか?

値ゼロは、「ハッシュコードはキャッシュされない」という意味として予約されています。

  • Java文字列が0にハッシュされる確率はどれくらいですか?

Javadocによると、文字列のハッシュコードの式は次のとおりです。

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

int算術を使用して、ここでs[i]は文字列のi番目の文字で、nは文字列の長さです。 (空の文字列のハッシュは、特別な場合としてゼロになるように定義されています。)

私の直感は、上記のハッシュコード関数がint値の範囲全体で文字列ハッシュ値の均一な広がりを与えることです。ランダムに生成された文字列がゼロにハッシュされる確率が2 ^ 32の1であることを意味する均一な広がり。

  • 0にハッシュする文字列について、毎回ハッシュ値を再計算するというパフォーマンスのペナルティを回避する最良の方法は何ですか?

最善の戦略は、問題を無視することです。同じString値を繰り返しハッシュしている場合は、アルゴリズムにかなり奇妙な点があります。

  • これは値をキャッシュする最良の方法ですか? (つまり、1つを除くすべてをキャッシュしますか?)

これは、スペースと時間のトレードオフです。 AFAIK、代替案は次のとおりです。

  • 各Stringオブジェクトにcachedフラグを追加して、すべてのJava Stringが余分なWordを受け取るようにします。

  • hashメンバーの最上位ビットをキャッシュフラグとして使用します。これにより、すべてのハッシュ値をキャッシュできますが、可能な文字列ハッシュ値は半分しかありません。

  • 文字列のハッシュコードをキャッシュしないでください。

Javaデザイナは文字列を正しく呼び出していると思います。そして、彼らが彼らの決定の健全性を確認する広範なプロファイリングを行ったと私は確信しています。しかし、それはしないこれは、alwaysがキャッシュを処理する最良の方法であることを示します。

(ゼロにハッシュする2つの「一般的な」文字列値、空の文字列、およびNUL文字のみで構成される文字列があることに注意してください。ただし、これらの値のハッシュコードを計算するコストは、典型的な文字列値のハッシュコード。)

0
Stephen C