【Java原理系列】 Java中URL原理用法示例中文源码分析

Java中URL原理用法示例中文源码分析

含义

类{@code URL}表示统一资源定位符(URL),它是指向万维网上的“资源”的指针
资源可以是一个简单的文件或目录,也可以是对更复杂对象的引用,例如对数据库或搜索引擎的查询
有关URL类型及其格式的更多信息,请参阅:URL类型

通常情况下,URL可以被分成几个部分。考虑以下示例:

  http://www.example.com/docs/resource1.html

上面的URL表示要使用的协议是{@code http}(超文本传输协议),信息驻留在名为{@code www.example.com}的主机上。
该主机上的信息被命名为{@code /docs/resource1.html}。此名称在主机上的确切含义取决于协议和主机。
信息通常驻留在文件中,但也可能是动态生成的。URL的这个组成部分称为路径组件。

URL还可以选择性地指定一个“端口”,即远程主机上要进行TCP连接的端口号
如果未指定端口,则默认使用该协议的默认端口。
例如,{@code http}的默认端口是{@code 80}。
可以指定一个替代端口,如下所示:

  http://www.example.com:1080/docs/resource1.html

{@code URL}的语法由RFC 2396:统一资源标识符(URI):通用语法定义,
并由RFC 2732:URL中文字面IPv6地址的格式修订。
文字IPv6地址格式也支持scope_id。有关scope_id的语法和用法,请参见这里

URL可能会附加一个“片段”,也称为“引用”或“引用”。片段由井号字符“#”后跟更多字符来表示。
例如,

  http://java.sun.com/index.html#chapter1

此片段在技术上不是URL的一部分。它表示在检索指定的资源之后,应用程序特别关注带有标签{@code chapter1}的文档的那一部分。
标签的含义与资源相关。

应用程序还可以指定“相对URL”,它只包含足够的信息以相对于另一个URL访问资源。
相对URL通常在HTML页面中使用。例如,如果URL的内容为:

  http://java.sun.com/index.html

其中包含相对URL:

  FAQ.html

它将是以下缩写形式:

  http://java.sun.com/FAQ.html

相对URL不需要指定URL的所有组件。如果协议、主机名或端口号丢失,则该值将继承自完全指定的URL。
必须指定文件组件。可选的片段不会被继承。

URL类本身不会根据RFC2396中定义的转义机制对任何URL组件进行编码或解码。
调用者有责任在调用URL之前对需要进行转义的字段进行编码,并对返回的已转义字段进行解码。
此外,因为URL对URL转义没有了解,它不会识别相同URL的编码或解码形式之间的等价性。
例如,两个URL:
http://foo.com/hello world/

http://foo.com/hello%20world
将被认为不相等。

注意,{@link java.net.URI}类在某些情况下会对其组件字段执行转义。
管理URL的编码和解码的推荐方式使用{@link java.net.URI},并使用{@link #toURI()}和{@link URI#toURL()}之间进行转换

{@link URLEncoder}和{@link URLDecoder}类也可以用于HTML表单编码,
但这与RFC2396中定义的编码方案不同。

原理

Java中的URL类是用于处理统一资源定位符(URL)的实用工具。它提供了一种方便的方式来解析、构建和操作URL。

URL类的实现基于以下原理:

  1. URL表示:

    • URL由协议、主机、端口、路径、查询参数和片段标识符等组成。
    • URL可以通过字符串形式进行表示,例如:“https://www.example.com/index.html?param=value#section”。
    • URL类使用字符串构造函数或各个部分的setter方法来创建URL对象。
  2. 解析URL:

    • URL类使用URLStreamHandler来处理不同协议的URL。每个支持的协议都有对应的URLStreamHandler实现。
    • URLStreamHandlerFactory接口允许应用程序设置自定义的URLStreamHandlerFactory,以便支持自定义协议。
  3. 打开URL连接:

    • URL.openConnection()方法打开与URL的连接,并返回一个URLConnection对象。
    • URLConnection提供了与URL相关的IO操作,如读取和写入数据。
  4. 读取URL内容:

    • URL.openStream()方法返回一个输入流,用于读取URL的内容。
    • URL.getContent()方法获取URL的内容对象,类型可能根据URL的内容类型而异。
  5. URL编码和解码:

    • URL类提供了静态的encode()和decode()方法,用于URL的编码和解码。
    • URL编码用于将特殊字符转换为URL安全的形式,以便在URL中传递参数和数据。
  6. 其他功能:

    • URL类提供了许多其他方法,如获取URL的各个部分、比较URL对象、获取URL的响应状态码等。
    • URL类还支持设置连接超时时间、代理服务器等功能。

总体而言,URL类通过封装URL的表示和操作,提供了一种方便的方式来处理URL。它基于URLStreamHandler和URLConnection实现与不同协议相关的功能,并提供了丰富的方法来处理URL的各个部分、读取内容、编码解码等操作。

用法

Java中的URL类提供了许多方法来处理URL。以下是URL类的一些常用方法:

构造函数:

  • URL(String spec):根据指定的URL字符串创建URL对象。

  • URL(String protocol, String host, int port, String file):根据给定的协议、主机、端口和文件路径创建URL对象。

  • URL(URL context, String spec):使用相对URL和基础URL创建URL对象。

获取URL的各个部分:

  • String getProtocol():获取URL的协议部分。

  • String getHost():获取URL的主机部分。

  • int getPort():获取URL的端口号。

  • String getFile():获取URL的文件路径部分。

  • String getQuery():获取URL的查询参数部分。

  • String getRef():获取URL的片段标识符部分。

连接管理:

  • URLConnection openConnection():打开与URL的连接并返回一个URLConnection对象,可以用于读取和写入数据。

  • URLConnection openConnection(Proxy proxy):使用指定的代理服务器打开与URL的连接。

读取URL内容:

  • InputStream openStream():打开URL连接并返回一个输入流,用于读取URL的内容。

  • Object getContent():获取URL的内容对象,返回的对象类型可能是不同的,具体取决于URL的内容类型。

URL编码和解码:

  • static String encode(String s):对指定的字符串进行URL编码。

  • static String decode(String s):对指定的URL编码字符串进行解码。

其他:

  • boolean equals(Object obj):比较URL对象与另一个对象是否相等。
  • int hashCode():返回URL对象的哈希码值。
  • String toString():将URL对象转换为字符串表示形式。

以上仅列举了URL类的一些常用方法,还有其他更多方法可以根据具体需求使用。

示例

以下是使用URL类的一些代码示例:

1.使用构造函数创建URL对象:

public class UrlTest {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://www.baidu.com");
            System.out.println("Protocol: " + url.getProtocol());
            System.out.println("Host: " + url.getHost());
            System.out.println("Port: " + url.getPort());
            System.out.println("File: " + url.getFile());          
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//            Protocol: https
//            Host: www.baidu.com
//            Port: -1
//            File:

2.打开URL连接并读取内容:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class UrlTest {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://www.baidu.com");     
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            InputStream inputStream = connection.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
            connection.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.对URL进行编码和解码:

public class UrlTest {
    public static void main(String[] args) {
        try {
            String originalUrl = "https://www.example.com/search?q=java programming";
            String encodedUrl = URLEncoder.encode(originalUrl, "UTF-8");
            System.out.println("Encoded URL: " + encodedUrl);

            String decodedUrl = URLDecoder.decode(encodedUrl, "UTF-8");
            System.out.println("Decoded URL: " + decodedUrl);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}
//Encoded URL: https%3A%2F%2Fwww.example.com%2Fsearch%3Fq%3Djava+programming
//Decoded URL: https://www.example.com/search?q=java programming

4.使用相对URL和基础URL创建URL对象:

class UrlTest2 {
    public static void main(String[] args) {
        try {
            URL baseURL = new URL("https://www.example.com");
            URL relativeURL = new URL(baseURL, "/about.html");

            System.out.println("Base URL: " + baseURL.toString());
            System.out.println("Relative URL: " + relativeURL.toString());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
}
//Base URL: https://www.example.com
//Relative URL: https://www.example.com/about.html

5.获取URL的查询参数:

class UrlTest3 {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://www.example.com/search?q=java+programming&lang=en");
            String query = url.getQuery();
            if (query != null) {
                String[] params = query.split("&");
                for (String param : params) {
                    String[] keyValue = param.split("=");
                    String key = URLDecoder.decode(keyValue[0], "UTF-8");
                    String value = URLDecoder.decode(keyValue[1], "UTF-8");
                    System.out.println(key + ": " + value);
                }
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}
//q: java programming
//lang: en

6.比较URL对象:

class UrlTest4 {
    public static void main(String[] args) {
        try {
            URL url1 = new URL("https://www.example.com");
            URL url2 = new URL("https://www.example.com");

            boolean isEqual = url1.equals(url2);
            System.out.println("URLs are equal: " + isEqual);

            int hashCode1 = url1.hashCode();
            int hashCode2 = url2.hashCode();
            System.out.println("HashCode 1: " + hashCode1);
            System.out.println("HashCode 2: " + hashCode2);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
}
//URLs are equal: true
//HashCode 1: 1672012488
//HashCode 2: 1672012488

7.设置URL连接的超时时间:

try {
    URL url = new URL("https://www.example.com");
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setConnectTimeout(5000); // 设置连接超时时间为5秒
    connection.setReadTimeout(10000); // 设置读取超时时间为10秒
    
    // 进行其他操作,如读取数据
    
    connection.disconnect();
} catch (IOException e) {
    e.printStackTrace();
}

8.获取URL的响应头信息:

class UrlTest7 {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://www.baidu.com");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            Map<String, List<String>> headers = connection.getHeaderFields();
            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
                String key = entry.getKey();
                List<String> values = entry.getValue();

                System.out.println(key + ": " + values);
            }

            connection.disconnect();
            connection.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
//null: [HTTP/1.1 200 OK]
//Server: [bfe]
//Content-Length: [2443]
//Date: [Sun, 15 Oct 2023 15:56:20 GMT]
//Content-Type: [text/html]

9.使用代理服务器打开URL连接:

try {
    URL url = new URL("https://www.example.com");
    Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy.example.com", 8080));
    HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
    
    // 进行其他操作,如读取数据
    
    connection.disconnect();
} catch (IOException e) {
    e.printStackTrace();
}

10.获取URL的输入流,并将其保存到文件中:

class UrlTest9 {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://pic35.photophoto.cn/20150511/0034034892281415_b.jpg");
            InputStream inputStream = url.openStream();
            FileOutputStream outputStream = new FileOutputStream("image.jpg");

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }

            inputStream.close();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
}}

11.获取URL的响应状态码:

class UrlTest8 {
    public static void main(String[] args) {
        try {
            URL url = new URL("https://www.baidu.com");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            int statusCode = connection.getResponseCode();
            System.out.println("Status Code: " + statusCode);
            connection.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//    Status Code: 200

中文源码

/**
 * 类{@code URL}表示统一资源定位符(URL),它是指向万维网上的“资源”的指针。
 * 资源可以是一个简单的文件或目录,也可以是对更复杂对象的引用,例如对数据库或搜索引擎的查询。
 * 有关URL类型及其格式的更多信息,请参阅:<a href="http://web.archive.org/web/20051219043731/http://archive.ncsa.uiuc.edu/SDG/Software/Mosaic/Demo/url-primer.html"><i>URL类型</i></a>
 *
 * 通常情况下,URL可以被分成几个部分。考虑以下示例:
 *
 *     http://www.example.com/docs/resource1.html
 *
 * 上面的URL表示要使用的协议是{@code http}(超文本传输协议),信息驻留在名为{@code www.example.com}的主机上。
 * 该主机上的信息被命名为{@code /docs/resource1.html}。此名称在主机上的确切含义取决于协议和主机。
 * 信息通常驻留在文件中,但也可能是动态生成的。URL的这个组成部分称为<i>路径</i>组件。
 *
 * URL还可以选择性地指定一个“端口”,即远程主机上要进行TCP连接的端口号。
 * 如果未指定端口,则默认使用该协议的默认端口。
 * 例如,{@code http}的默认端口是{@code 80}。
 * 可以指定一个替代端口,如下所示:
 *
 *     http://www.example.com:1080/docs/resource1.html
 *
 * {@code URL}的语法由<a href="http://www.ietf.org/rfc/rfc2396.txt"><i>RFC&nbsp;2396:统一资源标识符(URI):通用语法</i></a>定义,
 * 并由<a href="http://www.ietf.org/rfc/rfc2732.txt"><i>RFC&nbsp;2732:URL中文字面IPv6地址的格式</i></a>修订。
 * 文字IPv6地址格式也支持scope_id。有关scope_id的语法和用法,请参见<a href="Inet6Address.html#scoped">这里</a>。
 *
 * URL可能会附加一个“片段”,也称为“引用”或“引用”。片段由井号字符“#”后跟更多字符来表示。
 * 例如,
 *
 *     http://java.sun.com/index.html#chapter1
 *
 * 此片段在技术上不是URL的一部分。它表示在检索指定的资源之后,应用程序特别关注带有标签{@code chapter1}的文档的那一部分。
 * 标签的含义与资源相关。
 *
 * 应用程序还可以指定“相对URL”,它只包含足够的信息以相对于另一个URL访问资源。
 * 相对URL通常在HTML页面中使用。例如,如果URL的内容为:
 *
 *     http://java.sun.com/index.html
 *
 * 其中包含相对URL:
 *
 *     FAQ.html
 *
 * 它将是以下缩写形式:
 *
 *     http://java.sun.com/FAQ.html
 *
 * 相对URL不需要指定URL的所有组件。如果协议、主机名或端口号丢失,则该值将继承自完全指定的URL。
 * 必须指定文件组件。可选的片段不会被继承。
 *
 * URL类本身不会根据RFC2396中定义的转义机制对任何URL组件进行编码或解码。
 * 调用者有责任在调用URL之前对需要进行转义的字段进行编码,并对返回的已转义字段进行解码。
 * 此外,因为URL对URL转义没有了解,它不会识别相同URL的编码或解码形式之间的等价性。
 * 例如,两个URL:
 *     http://foo.com/hello world/
 * 和
 *     http://foo.com/hello%20world
 * 将被认为不相等。
 *
 * 注意,{@link java.net.URI}类在某些情况下会对其组件字段执行转义。
 * 管理URL的编码和解码的推荐方式是使用{@link java.net.URI},并使用{@link #toURI()}和{@link URI#toURL()}之间进行转换。
 *
 * {@link URLEncoder}和{@link URLDecoder}类也可以用于HTML表单编码,
 * 但这与RFC2396中定义的编码方案不同。
 *
 * @author  James Gosling
 * @since   JDK1.0
 */
public final class URL implements java.io.Serializable {

    static final String BUILTIN_HANDLERS_PREFIX = "sun.net.www.protocol";
    static final long serialVersionUID = -7627629688361524110L;

    /**
     * 指定要扫描的协议处理程序的包前缀列表的属性。该属性的值(如果有)应为以竖线分隔的包名称列表,
     * 用于搜索要加载的协议处理程序。
     * 该类的策略是所有协议处理程序都将位于名为<protocolname>.Handler的类中,
     * 并且会依次检查列表中的每个包是否存在匹配的处理程序。
     * 如果找不到(或未指定属性),则使用默认的包前缀sun.net.www.protocol。
     * 搜索从列表中的第一个包开始,到最后一个包结束,并在找到匹配项时停止。
     */
    private static final String protocolPathProp = "java.protocol.handler.pkgs";

    /**
     * 要使用的协议(ftp、http、nntp等)。
     * @serial
     */
    private String protocol;

    /**
     * 要连接的主机名。
     * @serial
     */
    private String host;

    /**
     * 要连接的协议端口。
     * @serial
     */
    private int port = -1;

    /**
     * 主机上指定的文件名。{@code file}被定义为{@code path[?query]}。
     * @serial
     */
    private String file;

    /**
     * 此URL的查询部分。
     */
    private transient String query;

    /**
     * 此URL的授权部分。
     * @serial
     */
    private String authority;

    /**
     * 此URL的路径部分。
     */
    private transient String path;

    /**
     * 此URL的用户信息部分。
     */
    private transient String userInfo;

    /**
     * 引用。
     * @serial
     */
    private String ref;

    /**
     * 主机的IP地址,用于equals和hashCode。根据需要计算。未初始化或未知的hostAddress为null。
     */
    transient InetAddress hostAddress;

    /**
     * 此URL的URLStreamHandler。
     */
    transient URLStreamHandler handler;

    /* 我们的哈希码。
     * @serial
     */
    private int hashCode = -1;

    private transient UrlDeserializedState tempState;

/**
 * 从指定的 protocol、host、port 号和 file 创建一个 URL 对象。
 *
 * host 可以表示为主机名或字面 IP 地址。如果使用 IPv6 字面地址,则应将其括在方括号([ 和 ])中,如 RFC 2732 所指定;但是,也接受 RFC 2373:IP Version 6 Addressing Architecture 中定义的字面 IPv6 地址格式。
 *
 * 指定 port 号为 -1 表示 URL 应使用协议的默认端口。
 *
 * 如果这是使用指定协议创建的第一个 URL 对象,则会为该协议创建一个流协议处理程序对象,即 URLStreamHandler 类的实例:
 * - 如果应用程序先前设置了 URLStreamHandlerFactory 的实例作为流处理程序工厂,则调用该实例的 createURLStreamHandler 方法,参数为协议字符串,以创建流协议处理程序。
 * - 如果尚未设置 URLStreamHandlerFactory,或者如果工厂的 createURLStreamHandler 方法返回 null,则构造函数找到系统属性的值 java.protocol.handler.pkgs。
 *   如果该系统属性的值不为 null,则将其解释为由竖线字符 | 分隔的包列表。构造函数尝试加载名为 <package>.<protocol>.Handler 的类,其中 <package> 被包的名称替换,而 <protocol> 被协议的名称替换。
 *   如果此类不存在,或者如果该类存在但不是 URLStreamHandler 的子类,则尝试下一个包。
 * - 如果前面的步骤无法找到协议处理程序,则构造函数尝试从系统默认包中加载 <system default package>.<protocol>.Handler。
 *   如果此类不存在,或者如果该类存在但不是 URLStreamHandler 的子类,则抛出 MalformedURLException。
 *
 * 以下协议的协议处理程序保证存在于搜索路径上:
 * - http
 * - https
 * - file
 * - jar
 * 还可以提供其他协议的协议处理程序。
 *
 * 此构造函数不执行输入的任何验证。
 *
 * @param protocol 要使用的协议的名称。
 * @param host 主机的名称。
 * @param port 主机上的端口号。
 * @param file 主机上的文件。
 * @exception MalformedURLException 如果指定了未知协议。
 * @see java.lang.System#getProperty(java.lang.String)
 * @see java.net.URL#setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory)
 * @see java.net.URLStreamHandler
 * @see java.net.URLStreamHandlerFactory#createURLStreamHandler(java.lang.String)
 */
public URL(String protocol, String host, int port, String file) throws MalformedURLException {
    this(protocol, host, port, file, null);
}

/**
 * 通过指定的协议名称、主机名和文件名创建一个 URL 对象。将使用指定协议的默认端口。
 *
 * 此方法相当于使用参数 protocol、host、-1 和 file 调用四个参数的构造函数。
 *
 * 此构造函数不执行输入的任何验证。
 *
 * @param protocol 协议名称。
 * @param host 主机名。
 * @param file 文件名。
 * @exception MalformedURLException 如果未指定协议、找到未知协议或 spec 为 null。
 * @see java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
 */
public URL(String protocol, String host, String file) throws MalformedURLException {
    this(protocol, host, -1, file);
}

/**
 * 通过指定的协议、主机、端口号、文件和处理程序创建一个 URL 对象。指定端口号为 -1 表示该 URL 应使用协议的默认端口。指定处理程序为 null 表示 URL 应使用协议的默认流处理程序。
 *
 * 如果处理程序不为 null 并且存在安全管理器,则会使用安全管理器的 checkPermission 方法检查 NetPermission("specifyStreamHandler") 权限。这可能导致 SecurityException。
 *
 * 此构造函数不执行输入的任何验证。
 *
 * @param protocol 协议名称。
 * @param host 主机名。
 * @param port 主机上的端口号。
 * @param file 主机上的文件。
 * @param handler URL 的流处理程序。
 * @exception MalformedURLException 如果未指定协议、找到未知协议或 spec 为 null。
 * @exception SecurityException 如果存在安全管理器且其 checkPermission 方法不允许显式指定处理程序。
 * @see java.lang.System#getProperty(java.lang.String)
 * @see java.net.URL#setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory)
 * @see java.net.URLStreamHandler
 * @see java.net.URLStreamHandlerFactory#createURLStreamHandler(java.lang.String)
 * @see SecurityManager#checkPermission
 * @see java.net.NetPermission
 */
public URL(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException {
    if (handler != null) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkSpecifyHandler(sm);
        }
    }

    protocol = protocol.toLowerCase();
    this.protocol = protocol;
    if (host != null) {
        if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
            host = "[" + host + "]";
        }
        this.host = host;

        if (port < -1) {
            throw new MalformedURLException("Invalid port number: " + port);
        }
        this.port = port;
        authority = (port == -1) ? host : host + ":" + port;
    }

    Parts parts = new Parts(file);
    path = parts.getPath();
    query = parts.getQuery();

    if (query != null) {
        this.file = path + "?" + query;
    } else {
        this.file = path;
    }
    ref = parts.getRef();

    if (handler == null && (handler = getURLStreamHandler(protocol)) == null) {
        throw new MalformedURLException("Unknown protocol: " + protocol);
    }
    this.handler = handler;
    if (host != null && isBuiltinStreamHandler(handler)) {
        String s = IPAddressUtil.checkExternalForm(this);
        if (s != null) {
            throw new MalformedURLException(s);
        }
    }
    if ("jar".equalsIgnoreCase(protocol)) {
        if (handler instanceof sun.net.www.protocol.jar.Handler) {
            String s = ((sun.net.www.protocol.jar.Handler) handler).checkNestedProtocol(file);
            if (s != null) {
                throw new MalformedURLException(s);
            }
        }
    }
}

/**
 * 通过解析给定的字符串表示形式创建一个 URL 对象。
 *
 * 此构造函数相当于调用两个参数的构造函数,第一个参数为 null。
 *
 * @param spec 要解析为 URL 的字符串。
 * @exception MalformedURLException 如果未指定协议、找到未知协议或 spec 为 null。
 * @see java.net.URL#URL(java.net.URL, java.lang.String)
 */
public URL(String spec) throws MalformedURLException {
    this(null, spec);
}

/**
 * 通过在指定上下文中解析给定的规范创建一个 URL。
 *
 * 新的 URL 是根据给定的上下文 URL 和规范参数创建的,如 RFC2396“Uniform Resource Identifiers : Generic Syntax”中所述:
 * <scheme>://<authority><path>?<query>#<fragment>
 * 引用将被解析为方案、权限、路径、查询和片段部分。如果路径组件为空且未定义方案、权限和查询组件,则新的 URL 是对当前文档的引用。否则,在新 URL 中使用规范中的片段和查询部分。
 *
 * 如果规范中定义了方案组件并且与上下文的方案不匹配,则新 URL 将作为仅基于规范的绝对 URL 创建。否则,方案组件将继承自上下文 URL。
 *
 * 如果规范中存在权限组件,则规范将被视为绝对的,并且规范权限和路径将替换上下文权限和路径。如果规范中不存在权限组件,则新 URL 的权限将继承自上下文。
 *
 * 如果规范的路径组件以斜杠字符“/”开头,则该路径被视为绝对路径,并且规范路径将替换上下文路径。
 *
 * 否则,路径被视为相对路径,并追加到上下文路径中,如 RFC2396 中所述。此外,在这种情况下,通过删除出现的“..”和“.”来对路径进行规范化。
 *
 * 有关 URL 解析的更详细描述,请参见 RFC2396。
 *
 * @param context 要解析规范的上下文。
 * @param spec 要解析为 URL 的字符串。
 * @exception MalformedURLException 如果未指定协议、找到未知协议或 spec 为 null。
 * @see java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
 * @see java.net.URLStreamHandler
 * @see java.net.URLStreamHandler#parseURL(java.net.URL, java.lang.String, int, int)
 */
public URL(URL context, String spec) throws MalformedURLException {
    this(context, spec, null);
}

/**
 * 通过在指定上下文中使用指定处理程序解析给定的规范创建 URL。如果处理程序为 null,则与两个参数的构造函数一样进行解析。
 *
 * @param context 要解析规范的上下文。
 * @param spec 要解析为 URL 的字符串。
 * @param handler URL 的流处理程序。
 * @exception MalformedURLException 如果未指定协议、找到未知协议或 spec 为 null。
 * @exception SecurityException 如果存在安全管理器且其 checkPermission 方法不允许指定流处理程序。
 * @see java.net.URL#URL(java.lang.String, java.lang.String, int, java.lang.String)
 * @see java.net.URLStreamHandler
 * @see java.net.URLStreamHandler#parseURL(java.net.URL, java.lang.String, int, int)
 */
public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
    String original = spec;
    int i, limit, c;
    int start = 0;
    String newProtocol = null;
    boolean aRef = false;
    boolean isRelative = false;

    if (handler != null) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkSpecifyHandler(sm);
        }
    }

    try {
        limit = spec.length();
        while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
            limit--;
        }
        while ((start < limit) && (spec.charAt(start) <= ' ')) {
            start++;
        }

        if (spec.regionMatches(true, start, "url:", 0, 4)) {
            start += 4;
        }
        if (start < spec.length() && spec.charAt(start) == '#') {
            aRef = true;
        }
        for (i = start ; !aRef && (i < limit) && ((c = spec.charAt(i)) != '/') ; i++) {
            if (c == ':') {
                String s = spec.substring(start, i).toLowerCase();
                if (isValidProtocol(s)) {
                    newProtocol = s;
                    start = i + 1;
                }
                break;
            }
        }

        protocol = newProtocol;
        if ((context != null) && ((newProtocol == null) || newProtocol.equalsIgnoreCase(context.protocol))) {
            if (handler == null) {
                handler = context.handler;
            }

            if (context.path != null && context.path.startsWith("/")) {
                newProtocol = null;
            }

            if (newProtocol == null) {
                protocol = context.protocol;
                authority = context.authority;
                userInfo = context.userInfo;
                host = context.host;
                port = context.port;
                file = context.file;
                path = context.path;
                isRelative = true;
            }
        }

        if (protocol == null) {
            throw new MalformedURLException("No protocol specified: " + original);
        }

        if (handler == null && (handler = getURLStreamHandler(protocol)) == null) {
            throw new MalformedURLException("Unknown protocol: " + protocol);
        }

        this.handler = handler;

        i = spec.indexOf('#', start);
        if (i >= 0) {
            ref = spec.substring(i + 1, limit);
            limit = i;
        }

        if (isRelative && start == limit) {
            query = context.query;
            if (ref == null) {
                ref = context.ref;
            }
        }

        handler.parseURL(this, spec, start, limit);
    } catch (MalformedURLException e) {
        throw e;
    } catch (Exception e) {
        MalformedURLException exception = new MalformedURLException(e.getMessage());
        exception.initCause(e);
        throw exception;
    }
}
 
    /**
     * 判断指定的字符串是否是有效的协议名称。
     */
    private boolean isValidProtocol(String protocol) {
        int len = protocol.length();
        if (len < 1)
            return false;
        char c = protocol.charAt(0);
        if (!Character.isLetter(c))
            return false;
        for (int i = 1; i < len; i++) {
            c = protocol.charAt(i);
            if (!Character.isLetterOrDigit(c) && c != '.' && c != '+' &&
                c != '-') {
                return false;
            }
        }
        return true;
    }

    /**
     * 检查是否有权限指定流处理程序。
     */
    private void checkSpecifyHandler(SecurityManager sm) {
        sm.checkPermission(SecurityConstants.SPECIFY_HANDLER_PERMISSION);
    }

    /**
     * 设置URL的字段。这不是一个公共方法,以便只有URLStreamHandlers可以修改URL字段。URL通常是不可变的。
     *
     * @param protocol 使用的协议的名称
     * @param host 主机名
     * @param port 主机上的端口号
     * @param file 主机上的文件
     * @param ref URL中的内部引用
     */
    void set(String protocol, String host, int port,
             String file, String ref) {
        synchronized (this) {
            this.protocol = protocol;
            this.host = host;
            authority = port == -1 ? host : host + ":" + port;
            this.port = port;
            this.file = file;
            this.ref = ref;
            /* 这非常重要。在URL被改变后我们必须重新计算hashCode。 */
            hashCode = -1;
            hostAddress = null;
            int q = file.lastIndexOf('?');
            if (q != -1) {
                query = file.substring(q+1);
                path = file.substring(0, q);
            } else
                path = file;
        }
    }

    /**
     * 设置指定的8个URL字段。这不是一个公共方法,以便只有URLStreamHandlers可以修改URL字段。URL通常是不可变的。
     *
     * @param protocol 使用的协议的名称
     * @param host 主机名
     * @param port 主机上的端口号
     * @param authority URL的授权部分
     * @param userInfo 用户名和密码
     * @param path 主机上的文件
     * @param ref URL中的内部引用
     * @param query 这个URL的查询部分
     * @since 1.3
     */
    void set(String protocol, String host, int port,
             String authority, String userInfo, String path,
             String query, String ref) {
        synchronized (this) {
            this.protocol = protocol;
            this.host = host;
            this.port = port;
            this.file = query == null ? path : path + "?" + query;
            this.userInfo = userInfo;
            this.path = path;
            this.ref = ref;
            /* 这非常重要。在URL被改变后我们必须重新计算hashCode。 */
            hashCode = -1;
            hostAddress = null;
            this.query = query;
            this.authority = authority;
        }
    }

    /**
     * 获取此URL的查询部分。
     *
     * @return 此URL的查询部分,如果不存在则返回null
     * @since 1.3
     */
    public String getQuery() {
        return query;
    }

    /**
     * 获取此URL的路径部分。
     *
     * @return 此URL的路径部分,如果不存在则返回空字符串
     * @since 1.3
     */
    public String getPath() {
        return path;
    }

    /**
     * 获取此URL的userInfo部分。
     *
     * @return 此URL的userInfo部分,如果不存在则返回null
     * @since 1.3
     */
    public String getUserInfo() {
        return userInfo;
    }

    /**
     * 获取此URL的authority部分。
     *
     * @return 此URL的authority部分
     * @since 1.3
     */
    public String getAuthority() {
        return authority;
    }

    /**
     * 获取此URL的端口号。
     *
     * @return 端口号,如果未设置端口则返回-1
     */
    public int getPort() {
        return port;
    }

    /**
     * 获取与此URL关联的协议的默认端口号。如果URL方案或URLStreamHandler未定义默认端口号,则返回-1。
     *
     * @return 端口号
     * @since 1.4
     */
    public int getDefaultPort() {
        return handler.getDefaultPort();
    }

    /**
     * 获取此URL的协议名称。
     *
     * @return 此URL的协议
     */
    public String getProtocol() {
        return protocol;
    }

    /**
     * 获取此URL的主机名(如果适用)。主机名的格式符合RFC 2732,即对于字面IPv6地址,此方法将返回用方括号('['和']')括起来的IPv6地址。
     *
     * @return 此URL的主机名
     */
    public String getHost() {
        return host;
    }

    /**
     * 获取此URL的文件名。返回的文件部分将与getPath()相同,加上如果有的话getQuery()的值。如果没有查询部分,这个方法和getPath()将返回相同的结果。
     *
     * @return 此URL的文件名,如果不存在则返回空字符串
     */
    public String getFile() {
        return file;
    }

    /**
     * 获取此URL的锚点(也称为“引用”)。
     *
     * @return 此URL的锚点,如果不存在则返回null
     */
    public String getRef() {
        return ref;
    }

    /**
     * 将此URL与另一个对象进行比较,判断是否相等。
     *
     * 如果给定的对象不是URL,则该方法立即返回false。
     *
     * 两个URL对象相等的条件是:它们具有相同的协议、引用等效的主机、在主机上具有相同的端口号以及相同的文件和文件片段。
     *
     * 如果两个主机名称可以解析为相同的IP地址,则认为两个主机是等效的;否则,如果任一主机名称无法解析,主机名称必须相等,而不区分大小写;或者两个主机名称都为null。
     *
     * 由于主机比较需要进行名称解析,这个操作是一个阻塞操作。
     *
     * 注意:已知{@code equals}的定义行为与HTTP中的虚拟主机不一致。
     *
     * @param obj 要比较的URL。
     * @return 如果对象相同则返回true;否则返回false。
     */
    public boolean equals(Object obj) {
        if (!(obj instanceof URL))
            return false;
        URL u2 = (URL)obj;

        return handler.equals(this, u2);
    }

    /**
     * 创建适合哈希表索引的整数。
     *
     * 哈希码基于所有与URL比较相关的URL组件。因此,这个操作是一个阻塞操作。
     *
     * @return 这个URL的哈希码。
     */
    public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

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

    /**
     * 比较两个URL,不包括片段部分。
     *
     * 如果此URL和{@code other}参数在不考虑片段部分的情况下相等,则返回{@code true}。
     *
     * @param   other   要比较的URL。
     * @return  如果它们引用相同的远程对象,则返回{@code true};否则返回{@code false}。
     */
    public boolean sameFile(URL other) {
        return handler.sameFile(this, other);
    }

    /**
     * 构造此URL的字符串表示形式。通过调用此对象的流协议处理程序的{@code toExternalForm}方法创建字符串。
     *
     * @return 此对象的字符串表示形式。
     * @see     java.net.URL#URL(java.lang.String, java.lang.String, int,
     *                  java.lang.String)
     * @see     java.net.URLStreamHandler#toExternalForm(java.net.URL)
     */
    public String toString() {
        return toExternalForm();
    }

    /**
     * 构造此URL的字符串表示形式。通过调用此对象的流协议处理程序的{@code toExternalForm}方法创建字符串。
     *
     * @return 此对象的字符串表示形式。
     * @see     java.net.URL#URL(java.lang.String, java.lang.String,
     *                  int, java.lang.String)
     * @see     java.net.URLStreamHandler#toExternalForm(java.net.URL)
     */
    public String toExternalForm() {
        return handler.toExternalForm(this);
    }

    /**
     * 返回与此URL等效的{@link java.net.URI}。此方法的功能方式与{@code new URI (this.toString())}相同。
     * 注意,任何符合RFC 2396的URL实例都可以转换为URI。然而,某些不严格符合规范的URL无法转换为URI。
     *
     * @exception URISyntaxException 如果此URL的格式不严格符合RFC2396,并且无法转换为URI。
     *
     * @return 与此URL等效的URI实例。
     * @since 1.5
     */
    public URI toURI() throws URISyntaxException {
        URI uri = new URI(toString());
        if (authority != null && isBuiltinStreamHandler(handler)) {
            String s = IPAddressUtil.checkAuthority(this);
            if (s != null) throw new URISyntaxException(authority, s);
        }
        return uri;
    }

    /**
     * 返回表示到{@code URL}所引用的远程对象的连接的{@link java.net.URLConnection}实例。
     *
     * 每次调用此URL的协议处理程序的{@linkplain java.net.URLStreamHandler#openConnection(URL)URLStreamHandler.openConnection(URL)}方法时,
     * 都会创建一个新的{@linkplain java.net.URLConnection URLConnection}实例。</P>
     *
     * 需要注意的是,URLConnection实例在创建时不会建立实际的网络连接。只有在调用{@linkplain java.net.URLConnection#connect() URLConnection.connect()}时才会发生连接。</P>
     *
     * 如果URL的协议(如HTTP或JAR)存在于以下包或其子包中的公共专用URLConnection子类:java.lang、java.io、java.util、java.net,则返回的连接将是该子类。
     * 例如,对于HTTP,将返回一个HttpURLConnection;对于JAR,将返回一个JarURLConnection。</P>
     *
     * @return 一个链接到URL的{@link java.net.URLConnection}。
     * @exception  IOException  如果发生I/O异常。
     * @see        java.net.URL#URL(java.lang.String, java.lang.String,
     *             int, java.lang.String)
     */
    public URLConnection openConnection() throws java.io.IOException {
        return handler.openConnection(this);
    }

    /**
     * 与{@link #openConnection()}相同,只是连接将通过指定的代理进行。
     * 不支持代理的协议处理程序将忽略proxy参数并进行正常连接。
     *
     * 调用此方法会优先于系统的默认ProxySelector设置。
     *
     * @param      proxy 将通过该代理进行连接。如果要直接连接,则应指定Proxy.NO_PROXY。
     * @return     链接到URL的{@code URLConnection}。
     * @exception  IOException  如果发生I/O异常。
     * @exception  SecurityException 如果存在安全管理器且调用者没有连接到代理的权限。
     * @exception  IllegalArgumentException 如果proxy为null,或proxy类型不正确
     * @exception  UnsupportedOperationException 如果实现协议处理程序的子类不支持此方法。
     * @see        java.net.URL#URL(java.lang.String, java.lang.String,
     *             int, java.lang.String)
     * @see        java.net.URLConnection
     * @see        java.net.URLStreamHandler#openConnection(java.net.URL,
     *             java.net.Proxy)
     * @since      1.5
     */
    public URLConnection openConnection(Proxy proxy)
        throws java.io.IOException {
        if (proxy == null) {
            throw new IllegalArgumentException("proxy can not be null");
        }

        // 创建代理的副本作为一种安全措施
        Proxy p = proxy == Proxy.NO_PROXY ? Proxy.NO_PROXY : sun.net.ApplicationProxy.create(proxy);
        SecurityManager sm = System.getSecurityManager();
        if (p.type() != Proxy.Type.DIRECT && sm != null) {
            InetSocketAddress epoint = (InetSocketAddress) p.address();
            if (epoint.isUnresolved())
                sm.checkConnect(epoint.getHostName(), epoint.getPort());
            else
                sm.checkConnect(epoint.getAddress().getHostAddress(),
                                epoint.getPort());
        }
        return handler.openConnection(this, p);
    }

    /**
     * 打开与此URL的连接并返回一个用于从该连接读取的{@code InputStream}。
     * 这个方法是以下代码的简写形式:
     * <blockquote><pre>
     *     openConnection().getInputStream()
     * </pre></blockquote>
     *
     * @return     用于从URL连接读取的输入流。
     * @exception  IOException  如果发生I/O异常。
     * @see        java.net.URL#openConnection()
     * @see        java.net.URLConnection#getInputStream()
     */
    public final InputStream openStream() throws java.io.IOException {
        return openConnection().getInputStream();
    }

/**
 * 获取此URL的内容。此方法是以下方法的简写形式:
 * <blockquote><pre>
 *     openConnection().getContent()
 * </pre></blockquote>
 *
 * @return     此URL的内容。
 * @exception  IOException  如果发生I/O异常。
 * @see        java.net.URLConnection#getContent()
 */
public final Object getContent() throws java.io.IOException {
    return openConnection().getContent();
}

/**
 * 获取此URL的内容。此方法是以下方法的简写形式:
 * <blockquote><pre>
 *     openConnection().getContent(Class[])
 * </pre></blockquote>
 *
 * @param classes Java类型数组
 * @return     此URL的内容对象,即与classes数组中指定类型匹配的第一个对象。
 *               如果没有请求的类型被支持,则返回null。
 * @exception  IOException  如果发生I/O异常。
 * @see        java.net.URLConnection#getContent(Class[])
 * @since 1.3
 */
public final Object getContent(Class[] classes)
throws java.io.IOException {
    return openConnection().getContent(classes);
}

/**
 * URLStreamHandler工厂。
 */
static URLStreamHandlerFactory factory;

/**
 * 设置应用程序的{@code URLStreamHandlerFactory}。
 * 在给定的Java虚拟机中,此方法最多可以调用一次。
 *
 * {@code URLStreamHandlerFactory}实例用于
 *从协议名称构造流协议处理程序。
 *
 *  如果存在安全管理器,此方法首先调用
 * 安全管理器的{@code checkSetFactory}方法,
 * 以确保允许该操作。
 * 这可能导致SecurityException。
 *
 * @param      fac   所需的工厂。
 * @exception  Error  如果应用程序已经设置了工厂。
 * @exception  SecurityException  如果存在安全管理器,并且
 *             其{@code checkSetFactory}方法不允许该操作。
 * @see        java.net.URL#URL(java.lang.String, java.lang.String,
 *             int, java.lang.String)
 * @see        java.net.URLStreamHandlerFactory
 * @see        SecurityManager#checkSetFactory
 */
public static void setURLStreamHandlerFactory(URLStreamHandlerFactory fac) {
    synchronized (streamHandlerLock) {
        if (factory != null) {
            throw new Error("factory already defined");
        }
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkSetFactory();
        }
        handlers.clear();
        factory = fac;
    }
}

/**
 * 协议处理程序的表。
 */
static Hashtable<String,URLStreamHandler> handlers = new Hashtable<>();
private static Object streamHandlerLock = new Object();

/**
 * 返回流处理程序。
 * @param protocol 要使用的协议
 */
static URLStreamHandler getURLStreamHandler(String protocol) {

    URLStreamHandler handler = handlers.get(protocol);
    if (handler == null) {

        boolean checkedWithFactory = false;

        // 使用工厂(如果有)
        if (factory != null) {
            handler = factory.createURLStreamHandler(protocol);
            checkedWithFactory = true;
        }

        // 尝试java协议处理程序
        if (handler == null) {
            String packagePrefixList = null;

            packagePrefixList
                = java.security.AccessController.doPrivileged(
                new sun.security.action.GetPropertyAction(
                    protocolPathProp,""));
            if (packagePrefixList != "") {
                packagePrefixList += "|";
            }

            // REMIND: 决定是否允许“null”类前缀
            // 或不允许。
            packagePrefixList += "sun.net.www.protocol";

            StringTokenizer packagePrefixIter =
                new StringTokenizer(packagePrefixList, "|");

            while (handler == null &&
                   packagePrefixIter.hasMoreTokens()) {

                String packagePrefix =
                  packagePrefixIter.nextToken().trim();
                try {
                    String clsName = packagePrefix + "." + protocol +
                      ".Handler";
                    Class<?> cls = null;
                    try {
                        cls = Class.forName(clsName);
                    } catch (ClassNotFoundException e) {
                        ClassLoader cl = ClassLoader.getSystemClassLoader();
                        if (cl != null) {
                            cls = cl.loadClass(clsName);
                        }
                    }
                    if (cls != null) {
                        handler  =
                          (URLStreamHandler)cls.newInstance();
                    }
                } catch (Exception e) {
                    // 可能会抛出多个异常
                }
            }
        }

        synchronized (streamHandlerLock) {

            URLStreamHandler handler2 = null;

            // 再次检查哈希表,以防另一个线程自上次检查以来创建了处理程序
            handler2 = handlers.get(protocol);

            if (handler2 != null) {
                return handler2;
            }

            // 如果另一个线程自我们上次检查以来设置了工厂,请与工厂检查
            if (!checkedWithFactory && factory != null) {
                handler2 = factory.createURLStreamHandler(protocol);
            }

            if (handler2 != null) {
                // 工厂的处理程序必须更重要。放弃
                // 此线程创建的默认处理程序。
                handler = handler2;
            }

            // 将此处理程序插入哈希表
            if (handler != null) {
                handlers.put(protocol, handler);
            }

        }
    }

    return handler;

}

/**
 * @serialField    protocol String
 *
 * @serialField    host String
 *
 * @serialField    port int
 *
 * @serialField    authority String
 *
 * @serialField    file String
 *
 * @serialField    ref String
 *
 * @serialField    hashCode int
 *
 */
private static final ObjectStreamField[] serialPersistentFields = {
    new ObjectStreamField("protocol", String.class),
    new ObjectStreamField("host", String.class),
    new ObjectStreamField("port", int.class),
    new ObjectStreamField("authority", String.class),
    new ObjectStreamField("file", String.class),
    new ObjectStreamField("ref", String.class),
    new ObjectStreamField("hashCode", int.class), };

/**
 * WriteObject被调用以将URL的状态保存到ObjectOutputStream中。处理程序不保存,因为它特定于该系统。
 *
 * @serialData 默认写对象值。在读取时,
 * 读者必须确保使用协议变量调用getURLStreamHandler返回一个有效的URLStreamHandler,
 * 如果不是,则抛出IOException。
 */
private synchronized void writeObject(java.io.ObjectOutputStream s)
    throws IOException
{
    s.defaultWriteObject(); // 写入字段
}

/**
 * readObject被调用以从流中恢复URL的状态。它读取URL的组件并查找本地流处理程序。
 */
private synchronized void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
    GetField gf = s.readFields();
    String protocol = (String)gf.get("protocol", null);
    if (getURLStreamHandler(protocol) == null) {
        throw new IOException("unknown protocol: " + protocol);
    }
    String host = (String)gf.get("host", null);
    int port = gf.get("port", -1);
    String authority = (String)gf.get("authority", null);
    String file = (String)gf.get("file", null);
    String ref = (String)gf.get("ref", null);
    int hashCode = gf.get("hashCode", -1);
    if (authority == null
            && ((host != null && host.length() > 0) || port != -1)) {
        if (host == null)
            host = "";
        authority = (port == -1) ? host : host + ":" + port;
    }
    tempState = new UrlDeserializedState(protocol, host, port, authority,
           file, ref, hashCode);
}

/**
 * 使用URL对象替换反序列化的对象。
 *
 * @return 从反序列化状态创建的新对象。
 *
 * @throws ObjectStreamException 如果无法创建代替此对象的新对象
 */
private Object readResolve() throws ObjectStreamException {

    URLStreamHandler handler = null;
    // 已在readObject中检查过
    handler = getURLStreamHandler(tempState.getProtocol());

    URL replacementURL = null;
    if (isBuiltinStreamHandler(handler.getClass().getName())) {
        replacementURL = fabricateNewURL();
    } else {
        replacementURL = setDeserializedFields(handler);
    }
    return replacementURL;
}

private URL setDeserializedFields(URLStreamHandler handler) {
    URL replacementURL;
    String userInfo = null;
    String protocol = tempState.getProtocol();
    String host = tempState.getHost();
    int port = tempState.getPort();
    String authority = tempState.getAuthority();
    String file = tempState.getFile();
    String ref = tempState.getRef();
    int hashCode = tempState.getHashCode();


    // 构造授权部分
    if (authority == null
        && ((host != null && host.length() > 0) || port != -1)) {
        if (host == null)
            host = "";
        authority = (port == -1) ? host : host + ":" + port;

        // 处理具有userInfo的主机
        int at = host.lastIndexOf('@');
        if (at != -1) {
            userInfo = host.substring(0, at);
            host = host.substring(at+1);
        }
    } else if (authority != null) {
        // 构造用户信息部分
        int ind = authority.indexOf('@');
        if (ind != -1)
            userInfo = authority.substring(0, ind);
    }

    // 构造路径和查询部分
    String path = null;
    String query = null;
    if (file != null) {
        // 修复:仅在层次结构时才这样做?
        int q = file.lastIndexOf('?');
        if (q != -1) {
            query = file.substring(q+1);
            path = file.substring(0, q);
        } else
            path = file;
    }

    // 设置对象字段。
    this.protocol = protocol;
    this.host = host;
    this.port = port;
    this.file = file;
    this.authority = authority;
    this.ref = ref;
    this.hashCode = hashCode;
    this.handler = handler;
    this.query = query;
    this.path = path;
    this.userInfo = userInfo;
    replacementURL = this;
    return replacementURL;
}

private URL fabricateNewURL()
            throws InvalidObjectException {
    // 从反序列化的对象创建URL字符串
    URL replacementURL = null;
    String urlString = tempState.reconstituteUrlString();

    try {
        replacementURL = new URL(urlString);
    } catch (MalformedURLException mEx) {
        resetState();
        InvalidObjectException invoEx = new InvalidObjectException(
                "Malformed URL: " + urlString);
        invoEx.initCause(mEx);
        throw invoEx;
    }
    replacementURL.setSerializedHashCode(tempState.getHashCode());
    resetState();
    return replacementURL;
}

boolean isBuiltinStreamHandler(URLStreamHandler handler) {
   Class<?> handlerClass = handler.getClass();
   return isBuiltinStreamHandler(handlerClass.getName())
             || (handlerClass.getClassLoader() == null);
}

private boolean isBuiltinStreamHandler(String handlerClassName) {
    return (handlerClassName.startsWith(BUILTIN_HANDLERS_PREFIX));
}

private void resetState() {
    this.protocol = null;
    this.host = null;
    this.port = -1;
    this.file = null;
    this.authority = null;
    this.ref = null;
    this.hashCode = -1;
    this.handler = null;
    this.query = null;
    this.path = null;
    this.userInfo = null;
    this.tempState = null;
}

private void setSerializedHashCode(int hc) {
    this.hashCode = hc;
}
}

class Parts {
String path, query, ref;

Parts(String file) {
    int ind = file.indexOf('#');
    ref = ind < 0 ? null: file.substring(ind + 1);
    file = ind < 0 ? file: file.substring(0, ind);
    int q = file.lastIndexOf('?');
    if (q != -1) {
        query = file.substring(q+1);
        path = file.substring(0, q);
    } else {
        path = file;
    }
}

String getPath() {
    return path;
}

String getQuery() {
    return query;
}

String getRef() {
    return ref;
}
}

final class UrlDeserializedState {
private final String protocol;
private final String host;
private final int port;
private final String authority;
private final String file;
private final String ref;
private final int hashCode;

public UrlDeserializedState(String protocol,
                            String host, int port,
                            String authority, String file,
                            String ref, int hashCode) {
    this.protocol = protocol;
    this.host = host;
    this.port = port;
    this.authority = authority;
    this.file = file;
    this.ref = ref;
    this.hashCode = hashCode;
}

String getProtocol() {
    return protocol;
}

String getHost() {
    return host;
}

String getAuthority () {
    return authority;
}

int getPort() {
    return port;
}

String getFile () {
    return file;
}

String getRef () {
    return ref;
}

int getHashCode () {
    return hashCode;
}

String reconstituteUrlString() {

    // 预先计算StringBuilder的长度
    int len = protocol.length() + 1;
    if (authority != null && authority.length() > 0)
        len += 2 + authority.length();
    if (file != null) {
        len += file.length();
    }
    if (ref != null)
        len += 1 + ref.length();
    StringBuilder result = new StringBuilder(len);
    result.append(protocol);
    result.append(":");
    if (authority != null && authority.length() > 0) {
        result.append("//");
        result.append(authority);
    }
    if (file != null) {
        result.append(file);
    }
    if (ref != null) {
        result.append("#");
        result.append(ref);
    }
    return result.toString();
}
}
ate final String protocol;
private final String host;
private final int port;
private final String authority;
private final String file;
private final String ref;
private final int hashCode;

public UrlDeserializedState(String protocol,
                            String host, int port,
                            String authority, String file,
                            String ref, int hashCode) {
    this.protocol = protocol;
    this.host = host;
    this.port = port;
    this.authority = authority;
    this.file = file;
    this.ref = ref;
    this.hashCode = hashCode;
}

String getProtocol() {
    return protocol;
}

String getHost() {
    return host;
}

String getAuthority () {
    return authority;
}

int getPort() {
    return port;
}

String getFile () {
    return file;
}

String getRef () {
    return ref;
}

int getHashCode () {
    return hashCode;
}

String reconstituteUrlString() {

    // 预先计算StringBuilder的长度
    int len = protocol.length() + 1;
    if (authority != null && authority.length() > 0)
        len += 2 + authority.length();
    if (file != null) {
        len += file.length();
    }
    if (ref != null)
        len += 1 + ref.length();
    StringBuilder result = new StringBuilder(len);
    result.append(protocol);
    result.append(":");
    if (authority != null && authority.length() > 0) {
        result.append("//");
        result.append(authority);
    }
    if (file != null) {
        result.append(file);
    }
    if (ref != null) {
        result.append("#");
        result.append(ref);
    }
    return result.toString();
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值