URI类与URL类概述与实例

来源:互联网 发布:会计核算软件 编辑:程序博客网 时间:2024/05/20 13:08

绝大部分知识与实例来自O’REILLY的《Java网络编程》(Java Network Programming,Fourth Edition,by Elliotte Rusty Harold(O’REILLY))。

URI与URL

URI全称为Uniform Resource Identifier,统一资源标识符,作用是标识某一互联网资源。
URL全称为Uniform Resource Locator,统一资源定位符,用于表示资源的地点。
URI与URL的联系是它们都能够唯一地确定一个资源,区别是URL是通过资源的地点来确定资源的。因此,可以说URL是URI的子集。

URI格式

URI没有非常具体的格式,一般由模式和模式特定部分组成,模式特定部分的语法取决于所用的格式,如下:

模式:模式特定部分[#片段标识符]

模式类型如下:

  • data:Base64编码数据
  • file:本地磁盘上的文件
  • ftp:FTP服务器
  • http:使用HTTP的国际互联网服务器
  • mailto:电子邮件地址
  • magnet:可通过对等网络(BT等)下载的资源
  • telnet:与基于Telnet的服务的连接
  • urn:统一资源名(Uniform Resource Name,URN)

除了这些标准模式,Java还支持rmi,jar,jndi,doc等非标准的定制模式,用于实现不同用途。
模式特定部分没有特定的语法,一般会采用一种层次结构形式,如下:

//authority/path?query

其中authority为授权机构,负责解析URI其余部分,path为路径,query为查询字符串。

URL格式

相比于URI,URL的格式具体的多。绝对URL格式如下:

协议方案名://[登录信息@]主机[:端口号]/带层次的文件路径[?查询字符串][#片段标识符]protocol://[userInfo@]host[:port]/path[?query][#fragment]

下面为字段简介:

  • 协议方案名(protocol):是URI中模式(scheme)的另一种叫法,可以是file,http,ftp,https等。
  • 主机(host):指代提供资源的服务器。可以是主机名(www.oreilly.com)也可以是IP地址。
  • 用户信息(userInfo):服务器的登录信息。
  • 端口(port):可选,如果服务在默认端口进行(如HTTP使用端口80)就不需要。
  • 路径(path):指向指定服务器上的一个特定目录。
  • 查询(query):向服务器提供附加参数,一般只在HTTP URL中使用,其中包含表单数据,作为输入提供给在这个服务器上运行的程序。
  • 片段(fragment):指向远程资源的一个特定部分。

URL还支持一种相对格式。若在XXX/doc/A.html中单击地址为B.html的超链接,浏览器会自动进入XXX/doc/B.html。若点击/help/C.html(注意开头有一个/,表示该相对URL相对于根目录),则会自动进入XXX/help/C.html。

编码与解码

为了解决跨平台的兼容问题,URI和URL规定只能使用ASCII的一个固定子集,其他的字符必须编码。直接可用的字符集如下:

  • 大写字母A-Z;
  • 小写字母a-z;
  • 数字0-9;
  • 标点符号字符- _ . ! ~ * ‘(以及,)。

除此之外,/ & ? @ # ; $ + = %这些也可以使用,不过有特定的用途(分隔符)。如果在这些用途之外使用,同样需要编码。
编码方式为:除了上面的字符集之外,所有其他字符都要用UTF-8编码成十六进制码,并在前面加上%表示转义。各个分隔符在用作分隔符时无需编码,而用在其他部分中时则需要编码。空格是一个例外,可以编码为加号+(加号本身会被编为%2B)。
补充关于分隔符部分:比如https://www.baidu.com,由于它是按http URI模式来组织的,因此里面的“/”和“:”为分隔符,因此都不用编码。而如果它们出现在其他部分(比如路径:/s/Java I/O)里面,就需要和其他特殊字符一样接受编码(类似于转义字符出现在字符串中的处理方式)。

URL类简介

java.net.URL类是Java对URL的抽象,是一个final类,不能被继承。实际上,URL类使用了策略设计模式,通过不同的协议处理器来扩展功能。
URL类是不可变的,对象构建完成后,字段就无法改变,因此能够保证线程安全。
URL类提供了多种构造器,用于在不同情况下创建对象。

从字符串构建URL对象

public URL(string url) throws MalformedURLException

根据一个字符串形式的绝对URL构建URl对象。如果构造成功,说明URL的协议得到了支持。失败则会抛出MalformedURLException异常。

实例1:检查虚拟机支持的协议类型

public static void testProtocol(String url){    try {        URL u = new URL(url);        System.out.println(u.getProtocol() + " is supported.");    } catch (MalformedURLException e) {        String protocol = url.substring(0,url.indexOf(":"));        System.out.println(protocol + " is supported.");    } }public static void main(String[] args) {    testProtocol("http://www.baidu.com");}

由组成部分构建URL对象

public URL(String protocol, String host, String file)throws MalformedURLExceptionpublic URL(String protocol, String host, int port, String file) throws MalformedURLException

两个方法都是利用URL的组成部分来构建URL对象,区别在于有无端口号。如果不设置端口号,构造器会将端口设置为-1,即使用协议的默认端口。
此处的protocol和host只要正常填写内容即可,如“http”、“www.csdn.net”,而file需要在开头加上/。file包括路径、文件名和可选的片段标识符。

根据相对URL构建URL对象

public URL(URL context, String spec) throws MalformedURLException

这个构造器根据基础URL和相对URL构建URL对象。简单来讲,就是用spec(相对URL)中的信息替换掉context(基础URL)中的对应部分,替换规则和。简单示例如下:

URL url = new URL("https", "osu.ppy.sh", "/s/557145");URL url2 = new URL(url, "625822");URL url3 = new URL(url,"/p/beatmaplist");System.out.println(url);System.out.println(url2);System.out.println(url3);输出:https://osu.ppy.sh/s/557145https://osu.ppy.sh/s/625822https://osu.ppy.sh/p/beatmaplist

URLEncoder与URLDecoder

前面提到过,**URL需要对特殊字符进行编码,而这个工作在构建它们的对象时不会自动完成。**Java提供了URLEncoder与URLDecoder,用于进行编码和解码的工作。先看看URLEncoder:

public static String encode(String s, String enc) throws UnsupportedEncodingException

提供了这样一个方法进行编码,第一个参数是需要编码的原始字符串,第二个是字符集,一般都用UTF-8。
这个方法的问题在于,它会进行过度编码,示例如下:

String str = URLEncoder.encode("http://www.baidu.com","UTF-8");System.out.println(str);输出:http%3A%2F%2Fwww.baidu.com

可以看到,它把本不需要编码的“/”和“:”都进行了编码。这个问题没有通解,只能通过手工编程将字符串各个部分分别编码,之后再组合起来。

URLDecoder提供了下面的方法用于解码:

public static String decode(String s, String enc) throws UnsupportedEncodingException

参数意义和URLEncoder相同,第一个参数是需要编码的原始字符串,第二个是字符集,一般都用UTF-8。使用这个方法的时候不需要拆分,只需将整个字符串即可,这是因为它不会对非转义字符做任何处理,只会把+号替换为空格,以及将%XX转换为对应的字符。

从URL对象获取数据

URL对象提供了一系列方法用于获取数据:

public final InputStream openStream() throws java.io.IOExceptionpublic URLConnection openConnection() throws java.io.IOExceptionpublic URLConnection openConnection(Proxy proxy) throws java.io.IOExceptionpublic final Object getContent() throws java.io.IOExceptionpublic final Object getContent(Class[] classes) throws java.io.IOException

openStream()方法是最直接的,它能够打开到指定URL的连接,并返回一个InputStream,从这个InputStream获得的数据是URL引用的原始内容,因此可能是ASCII文本、HTML、二进制图片数据等。

实例2:下载Web页面

public static void showWebSourceCode(String url){    try {        URL u = new URL(url);        //java 7        try(BufferedReader reader =                 new BufferedReader(                new InputStreamReader(                        new BufferedInputStream(u.openStream()),"UTF-8"))){            String line;            while((line = reader.readLine()) != null){                System.out.println(line);            }        } catch (IOException e) {            e.printStackTrace();        }    } catch (MalformedURLException e) {        System.out.println("Fail to connect to the url.");    }}

openConnection方法为指定的URL打开一个Socket,并返回一个URLConnection对象。一个URLConnection对象表示一个网络资源的打开的连接。如果需要和服务器通信,这个方法是最好的。重载版本可以指定一个代理服务器。
getContent()方法获取由URL引用的数据,尝试由它建立某种类型的对象。返回的对象可能是InputStream、HttpURLConnection、sun.awt.image.URLImageSource等等。
getContent(Class[] classes)接受一个class数组,用于选择最合适的返回类型。它会从数组的第一个开始判断,如果能够返回该类型则返回该类型,否则判断下一个,以此类推。注意返回值依然是Object,需要用instanceof判断实际类型。

分解URL

已经知道,URL分成5部分:模式(协议)、授权机构(主机名)、路径、查询字符串、片段标识符(ref)。URL类提供了获取这些部分的只读访问的方法:

  • getProtocol():协议;
  • getHost():主机名;
  • getPort():构建对象时指定的端口,默认-1;
  • getDefaultPort():协议对应的默认端口;
  • getFile():路径,从主机名后第一个“/”(包括)到片段标识符前的“#”(不包括)
  • getPath():路径,从主机名后第一个“/”(包括)到查询字符串前的“?”(不包括)
  • getRef():片段标识符;
  • getQuery():查询字符串;
  • getUserInfo():用户信息(用户名、口令);
  • getAuthority():授权机构,包括用户信息(可无)、主机、端口(可无)。

实例3:显示URL各部分

public static void showURLParts(String url){    try {        URL u = new URL(url);        System.out.println("protocol: " + u.getProtocol());        System.out.println("userinfo: " + u.getUserInfo());        System.out.println("host: " + u.getHost());        System.out.println("port: " + u.getPort());        System.out.println("default port: " + u.getDefaultPort());        System.out.println("path: " + u.getPath());        System.out.println("query : " + u.getQuery());        System.out.println("ref: " + u.getRef());        System.out.println();        System.out.println("file: " + u.getFile());        System.out.println("Authority: " + u.getAuthority());    } catch (MalformedURLException e) {        System.out.println("Fail to connect to " + url);    }}public static void main(String[] args) {    showURLParts("https://developer.android.google.cn"            + "/reference/java/net/URLConnection.html"            + "#getAllowUserInteraction()");}输出:protocol: httpsuserinfo: nullhost: developer.android.google.cnport: -1default port: 443path: /reference/java/net/URLConnection.htmlquery : nullref: getAllowUserInteraction()file: /reference/java/net/URLConnection.htmlAuthority: developer.android.google.cn

URL类的Object方法

URL类的equals()方法会判断两个URL的各个部分,包括查询字符串和片段标识符,只有全部相等才返回true。需要注意两点:
(1)equals()方法会尝试用DNS查询主机,来判断两个主机是否相同,这导致equals()方法可能阻塞。因此,应避免将URL存储在依赖equals()方法的数据结构(如HashMap中)。
(2)equals不会具体比较两个URL指向的资源,如http://www.oreilly.com不等于http://www.oreilly.com/index.html。
还有一个和equals()方法相似的sameFile()方法,区别在于sameFile()不考虑片段标识符。
URL类的toString()方法会返回一个包含绝对URL的字符串。还有一个toURI()方法,返回一个对应的URI对象。

URI类简介

URI类与URL类的主要区别如下:

  • URI类只有关于资源的标识和对URI进行解析的方法,没有关于获取URI指向的资源的方法。这是因为URI只能用于标识一个资源,不一定能确定资源在网络上的位置;
  • URI对象可以表示相对URI,URL类存储之前会将其绝对化;
  • 相比于URL类,URI类与相关规范更加一致。

简而言之,如果你需要获取资源,那么应当使用URL类;而如果你只需要标识资源,那么用URI类更加合适。
URI类同样提供了一系列的构造器,用于根据不同条件创建对象,和URL类在很多方面非常类似。下面为简要介绍:

public URI(String str) throws URISyntaxException

根据字符串创建对象。例:new URI(“http://www.baidu.com“);

public URI(String scheme, String ssp, String fragment) throws URISyntaxException

根据模式、模式特定部分、片段标识符创建对象。例:uri = new URI(“http”,”//www.baidu.com”,”id=12345”)。注意:模式可以为null,此时创建的就是相对URI,例:new URI(null,”/s/554433”,”id=12345”);需要注意的是,URI类会对特殊字符自动进行编码。

public URI(String scheme, String host, String path, String fragment) throws URISyntaxExceptionpublic URI(String scheme,String authority,String path, String query, String fragment) throws URISyntaxExceptionpublic URI(String scheme,String userInfo, String host, int port,String path, String query, String fragment) throws URISyntaxException

根据传入的不同部分构建URI。参数含义和URL的基本一致,此处不再赘述。唯一需要注意的是,URI类会对特殊字符自动进行编码,但是路径内的“/”会被视作分隔符而不参与编码(比如/s/Java I/O,I和O中间的“/”会被看成分隔符),此时只能手动处理“/”
所有的构造器在传入的参数格式与URI规范不符时都会抛出一个URISyntaxException异常,这个异常不是运行时异常,因此必须在编译时就进行处理。

URI的各部分

和URL类相似,URI类也提供了一系列的get方法,用于获取URI的各个组成部分。由于绝大多数部分和URL类相同,此处不再赘述。下面是和URL类的几点区别:

  • isAbsolute():判断是否是绝对URI,若是则返回true。判断依据是URI的模式(scheme)是否为null。
  • isOpaque():判断是URI是否是不透明的,若是则返回true。不透明的URI只能得到模式、模式特定部分和片段标识符。层次URI是透明的。
  • URI类的许多getXXX方法都有一个getRawXXX版本,区别在于getXXX返回解码后的结果,而getRawXXX返回未解码的结果。

相对URI与绝对URI的转换

URI类提供了以下方法完成相对URI与绝对URI的转换:

//相对URI转绝对URIpublic URI resolve(URI uri)public URI resolve(String uri)//绝对URI转相对URIpublic URI relativize(URI uri)

下面举例说明:

URI base = new URI("https://osu.ppy.sh/");URI relative = new URI("s/452625");URI resolved = base.resolve(relative);System.out.println(resolved);System.out.println(resolved.resolve(new URI("653523")));输出:https://osu.ppy.sh/s/452625https://osu.ppy.sh/s/653523
URI base = new URI("https://osu.ppy.sh/a/b/c/");String relative = "s/452625";URI resolved = base.resolve(relative);System.out.println(resolved);System.out.println(resolved.resolve("/d/653523"));输出:https://osu.ppy.sh/a/b/c/s/452625https://osu.ppy.sh/d/653523
URI absolute = new URI("https://osu.ppy.sh/s/452625");URI base = new URI("https://osu.ppy.sh/");URI relative = base.relativize(absolute);System.out.println(relative);System.out.println(base.resolve(relative));输出:s/452625https://osu.ppy.sh/s/452625

实际使用时需要特别注意各部分之间的“/”,最好多测试几种情况。

URI的Object方法和比较方法

URI的equals()方法不是单纯地比较字符串。两个URI要相等,首先它们必须都是层次的或都是不透明的。比较模式(scheme)和主机(host)时不区分大小写(HTTP和http相同,www.baidu.com和www.BAIDU.com相同),比较其余部分时要区分大小写(除了转义时的十六进制数)。比较前不会进行解码。
hashCode()方法的相等性与equals()一致。
URI的toString()方法返回URI未编码的字符串形式(尽管实际上经过了编码)。可以使用toASCIIString方法返回URI的编码形式。示例如下:

URI uri = new URI("https://www.example.com/s/新年快乐");System.out.println(uri.toString());System.out.println(uri.toASCIIString());输出:https://www.example.com/s/新年快乐https://www.example.com/s/%E6%96%B0%E5%B9%B4%E5%BF%AB%E4%B9%90

URI实现了Comparable接口,因此它可以排序。基于字符串的比较,它的排序规则如下:

  1. 如果模式不同则比较模式,不考虑大小写;
  2. 若模式相同则观察两个URI是否有层次,一般认为层次URI小于不透明URI;
  3. 若都是不透明URI则根据模式特定部分排序;
  4. 若模式特定部分也相同则根据片段排序;
  5. 若都是层次URI,则根据授权机构排序。授权机构本身按照用户信息、主机和端口排序,比较主机时不区分大小写;
  6. 如果模式和授权机构都相等,则根据路径排序;
  7. 路径相等则根据查询字符串排序;
  8. 查询字符串相等则根据片段排序;

URI只能和URI比较,否则会抛出ClassCastException异常。

代理(暂缺)

通过GET方法与服务器端程序通信

URL类使得和使用GET方法的服务器端程序通信非常容易。需要做的事情只有根据网站的表单构造查询字符串,然后拼接到URL上即可。所有查询字符串的名和值必须通过编码。
关于如何获取网站表单所需的参数,可以利用浏览器的调试功能找到对应表单的源代码,从中可以获知相关信息。

访问口令保护的网站

有些网站使用了HTTP认证(BASIC认证等),因此在访问之前需要通过认证。下面简介一下会使用到的类:

PasswordAuthentication

一个final类,用于提供用户名和密码。有两个只读域:String类型的userName和char[]类型的password,需要在构造函数中传入。

Authenticator

java.net提供了一个抽象类Authenticator,用于提供对HTTP认证的支持。为了让URL类使用,需要创建这个类的子类,并实现getPasswordAuthentication()方法,返回一个PasswordAuthentication对象。这个方法的实现形式有很多种,可以从输入流中获取,也可以创建swing对话框提示用户输入。
创建完Authenticator的子类之后,需要调用Authenticator.setDefault()静态方法设置默认Authenticator。在以后URL类需要用户名和口令时,就会自动询问这个子类。

原创粉丝点击