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行會輸出什麼?
結果肯定不是true
,true
。
好了,大都數情況下結果可能爲true, false
。
如果你關閉網絡再運行得到的結果就可能爲:true
,true
了。
造成這種現象的原因就是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/