Scalaでダイジェストを生成する
インターネットから取得した画像をキャッシュとしてファイルで保存しておきたいなー、と思いました。キャッシュなのでURLからファイル名が特定できなければいけない。最初はURLのString#hashCodeを使えばいいのかな、と思ったのですが、hashCodeは衝突する可能性があるらしい。そうかー、どんなアルゴリズムであれ衝突する可能性はあるよなー、と思ってMap[String, String]にURLとファイル名の対応を保持しておき、プログラム終了時にxmlで保存するようにしてたのですが。
ちょうどその頃、たまたま「入門Git」を読んでいて、 その中に、Gitではオブジェクトを管理するのに、SHA-1ハッシュ値を利用しているとの記述がありました。どうもこれを利用すればいいらしいなーという気がします。
なにしろ、SHA-1ハッシュ値が重複してしまう可能性というのはとてつもなく低いのです。「Pro Git」から引用すると、
しかし、そんなことはまず起こりえないということを知っておくべきでしょう。SHA-1 ダイジェストの大きさは 20 バイト (160 ビット) です。ランダムなハッシュ値がつけられた中で、たった一つの衝突が 50% の確率で発生するために必要なオブジェクトの数は約 2^80 となります (衝突の可能性の計算式は p = (n(n-1)/2) * (1/2^160) です)。2^80 は、ほぼ 1.2 x 10^24 、つまり一兆二千億のそのまた一兆倍です。これは、地球上にあるすべての砂粒の数の千二百倍にあたります。
http://progit.org/book/ja/ch6-1.html/
で、Scalaで、というかJavaでSHA-1ハッシュ関数を利用してダイジェストを生成するのはとても簡単、@IT:Java TIPS -- Javaでダイジェストを生成するにサンプルのコードが記載されていました。
これを元に、ScalaでStringからSHA-1で生成したダイジェストの文字列を返すクラスは以下のように定義できました。
import java.security.MessageDigest class Sha1Digest(str: String) { val digestString: String = { val md = MessageDigest.getInstance("SHA-1") md.update(str.getBytes) md.digest.foldLeft("") { (s, b) => s + "%02x".format(if(b < 0) b + 256 else b) } } }
ダイジェストを取得する際、Stringのメソッドであるかのように使いたいので、implicitすると、以下のようになります。
object App { def main(args: Array[String]) = List("全部", "違う", "値になります").foreach { str => println(str.digestString) } implicit def String2Sha1Digest(s: String): Sha1Digest = new Sha1Digest(s) }
自分のプロジェクトでは、MyPreDefのようなObjectを定義してimplicitを並べてimportしており、その中に紛れ込ませて使っています。