Java 魔法Part 1:java.net.URL

原文 http://mishadoff.com/blog/java-magic-part-1-java-dot-net-dot-url/

这篇文章比较老了,也有很多人翻译,但是好像都是google翻译的,没有体现出作者的风趣。感觉作者还是很皮的,他的这系列文章都很有意思。


最近, 我在reddit上发现了一个非常有趣的Java代码片段(有一点修改)

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"));

你认为第三代码和第五行代码的结果会是什么?
既然问了这个问题,那很明显不是true, true。先思考两分钟。

好了,在大多数时候结果是true, false 这是因为你连接了互联网(否则你怎么能看到这篇文章呢?)关闭你的网络连接或者WiFi你将会得到true, true
问题的原因在于该类方法hashCode() 和 equals()的实现逻辑。
让我们看下它是如何计算hashCode的:

public synchronized int hashCode() {
  if (hashCode != -1)
    return hashCode;
  hashCode = handler.hashCode(this);
  return hashCode;
}

我们可以看到hashCode是一个实例变量并且只计算一次。这是有意义的,因为URL是不可变的。handler是什么?它是URLStreamHandler子类的实例,具体依赖于不同的协议类型(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.

等一下! 阻塞式操作?!

对不起我昨天没有收新邮件,因为hashCode计算阻塞了

或者更好的例子:

不是的,妈妈,我不能看X片。你知道的,在做hashCode计算呢(这个实在是太皮了)

好的就当他是阻塞操作吧。另一个奇葩的地方,当计算hashCode的时候这个handler竟然会解析ip地址。更准确的说法是会尝试去解析ip地址,如果无法解析ip地址的话,会根据host地址去计算hashCode。我们拿google.com举个例子。当host的ip是动态的时候,或者说有一个域名解析的负载均衡的时候,不好的事情就发生了。在这种情况下同一个域名会得到不同的hashCode值,如果用在HashSet就会有两个(或者多个)实例在集合列表里。这一点也不好。顺便说一下,hashCode 和 equals 的性能也是很不好的,因为URLStreamHandler会开启一个URLConnection,不过这是另外一个话题了。


附Java里URLStreamHandler代码实现

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());
    }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,380评论 11 349
  • Java继承关系初始化顺序 父类的静态变量-->父类的静态代码块-->子类的静态变量-->子类的静态代码快-->父...
    第六象限阅读 2,174评论 0 9
  • BAT 常问的 Java基础39道常见面试题 1.八种基本数据类型的大小,以及他们的封装类 2.引用数据类型 3....
    AI乔治阅读 2,474评论 0 75
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,145评论 0 62
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,581评论 16 22