原文 http://mishadoff.com/blog/java-magic-part-1-java-dot-net-dot-url/
序
這篇文章比較老了拍皮,也有很多人翻譯格了,但是好像都是google翻譯的,沒有體現(xiàn)出作者的風(fēng)趣。感覺作者還是很皮的晴埂,他的這系列文章都很有意思痕囱。
最近, 我在reddit上發(fā)現(xiàn)了一個(gè)非常有趣的Java代碼片段(有一點(diǎn)修改)
HashSet set = new HashSet();
set.add(new URL("http://google.com"));
set.contains(new URL("http://google.com"));
Thread.sleep(60000);
set.contains(new URL("http://google.com"));
你認(rèn)為第三代碼和第五行代碼的結(jié)果會是什么?
既然問了這個(gè)問題舟奠,那很明顯不是true, true竭缝。先思考兩分鐘。
好了沼瘫,在大多數(shù)時(shí)候結(jié)果是true, false 這是因?yàn)槟氵B接了互聯(lián)網(wǎng)(否則你怎么能看到這篇文章呢抬纸?)關(guān)閉你的網(wǎng)絡(luò)連接或者WiFi你將會得到true, true。
問題的原因在于該類方法hashCode() 和 equals()的實(shí)現(xiàn)邏輯耿戚。
讓我們看下它是如何計(jì)算hashCode的:
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
我們可以看到hashCode是一個(gè)實(shí)例變量并且只計(jì)算一次湿故。這是有意義的,因?yàn)閁RL是不可變的膜蛔。handler是什么坛猪?它是URLStreamHandler子類的實(shí)例,具體依賴于不同的協(xié)議類型(file,http,ftp)皂股∈裕看下java.net.URL的Java文檔說明:
The hash code is based upon all the URL components relevant for URL comparison. As such, this operation is a blocking operation.
等一下! 阻塞式操作?呜呐!
對不起我昨天沒有收新郵件就斤,因?yàn)閔ashCode計(jì)算阻塞了
或者更好的例子:
不是的,媽媽蘑辑,我不能看X片战转。你知道的,在做hashCode計(jì)算呢(這個(gè)實(shí)在是太皮了)
好的就當(dāng)他是阻塞操作吧以躯。另一個(gè)奇葩的地方槐秧,當(dāng)計(jì)算hashCode的時(shí)候這個(gè)handler竟然會解析ip地址啄踊。更準(zhǔn)確的說法是會嘗試去解析ip地址,如果無法解析ip地址的話刁标,會根據(jù)host地址去計(jì)算hashCode颠通。我們拿google.com舉個(gè)例子。當(dāng)host的ip是動態(tài)的時(shí)候膀懈,或者說有一個(gè)域名解析的負(fù)載均衡的時(shí)候顿锰,不好的事情就發(fā)生了。在這種情況下同一個(gè)域名會得到不同的hashCode值启搂,如果用在HashSet就會有兩個(gè)(或者多個(gè))實(shí)例在集合列表里硼控。這一點(diǎn)也不好。順便說一下胳赌,hashCode 和 equals 的性能也是很不好的牢撼,因?yàn)閁RLStreamHandler會開啟一個(gè)URLConnection,不過這是另外一個(gè)話題了疑苫。
附Java里URLStreamHandler代碼實(shí)現(xiàn)
protected int hashCode(URL u) {
int h = 0;
// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
// Generate the host part.
InetAddress addr = getHostAddress(u);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}
// Generate the file part.
String file = u.getFile();
if (file != null)
h += file.hashCode();
// Generate the port part.
if (u.getPort() == -1)
h += getDefaultPort();
else
h += u.getPort();
// Generate the ref part.
String ref = u.getRef();
if (ref != null)
h += ref.hashCode();
return h;
}
好消息是Android源碼里對此作了更改
protected int hashCode(URL u) {
// Android-changed: Avoid network I/O
// Hash on the same set of fields that we compare in equals().
return Objects.hash(
u.getRef(),
u.getQuery(),
u.getProtocol(),
u.getFile(),
u.getHost(),
u.getPort());
}