Java魔法之java.net.URL

Java魔法之java.net.URL【譯】

最近發現一個很有意思的代碼段:

HashSet set = new HashSet();
set.add(new URL("http://google.com"));
System.out.println(set.contains(new URL("http://google.com")));
Thread.sleep(60000);
System.out.println(set.contains(new URL("http://google.com")));

你猜想下,第3、5行會輸出什麼?

結果肯定不是truetrue

好了,大都數情況下結果可能爲true, false

如果你關閉網絡再運行得到的結果就可能爲:truetrue了。

造成這種現象的原因就是java.net.URL 類的 hashCode()equals()方法的具體實現導致的。

我們來看看它的hashCode方法:

public synchronized int hashCode() {
    if (hashCode != -1)
        return hashCode;

    hashCode = handler.hashCode(this);
    return hashCode;
}


private int hashCode = -1;

我們可以看到hashCode是一個成員變量,只計算一次。

注意,java.net.URL 類是不可變的。

那麼handler是啥玩意呢?是URLStreamHandler的一個子類,依賴於協議(file、http、ftp…)。

感興趣的話可以查看下: java.net.URLStreamHandler#hashCode的邏輯。

我們看下URL.hashCode()的javadoc說明:

基於網址比較,是一個阻塞操作!

OMG!! 是一個阻塞操作呢!!

另一個刺激的是,handler會解析主機ip地址來計算哈希值。如果搞不定,則基於域名http://google.com計算哈希值。

如果ip是動態的,或者存在請求負載均衡,則主機的ip也是動態變化的——所以我們可能得到不同步的哈希值,這樣在HashSet就是2各不同的實例的。

這樣一點也不好,順便說下,hashCode 和 equals 的性能很糟糕——因爲URLStreamHandler會打開URLConnection。

那麼如何避免這種情況發生呢?

  • 使用java.net.URI替換java.net.URL; 這不是最好的選擇,但是也有了確定的哈希實現。
  • 不要在集合中使用java.net.URL,如果真要這麼做,建議使用代表URL的String對象放到集合中。
  • 在你計算哈希值的時候,斷網!!!——開玩笑啦。。。
  • 編寫自己的URLStreamHandler子類實現合適的hashCode方法。

附錄

參考:http://mishadoff.com/blog/java-magic-part-1-java-dot-net-dot-url/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章