JAVA中URI、URL和URN的概念

URI
URI = Uniform Resource Identifier
There are two types of URIs: URLs andURNs.
See RFC 1630: Universal Resource Identifiers in WWW: A Unifying Syntax for the Expression of Names and Addresses of Objects on the Network as used in the WWW.
URL
URL = Uniform Resource Locator
See RFC 1738: Uniform Resource Locators (URL)
URN
URN = Uniform Resource Name.

URI
URL URN 是彼此关联的。 URI 位于顶层, URL URN 的范畴位于底层。 URL URN 都是 URI 的子范畴。

URI
翻译为统一资源标识,它是以某种标准化的方式标识资源的字符串。这种字符串以 scheme 开头,语法以下:

[scheme:] scheme-specific-part

URI
scheme 和冒号开头。冒号把 scheme scheme-specific-part 分开,而且 scheme-specific-part 的语法由 URI scheme 决定。例如 http://www.cnn.com ,其中 http scheme //www.cnn.com scheme-specific-part

URI 分为绝对( absolute )或相对( relative )两类。绝对 URI 指以 scheme (后面跟着冒号)开头的 URI 。前面提到的 http://www.cnn.com 就是绝对的 URI 的一个例子,其它的例子还有 mailto:jeff@javajeff.com news:comp.lang.java.help xyz://whatever 。能够把绝对 URI 看做是以某种方式引用某种资源,而对环境没有依赖。若是使用文件系统做类比,绝对 URI 相似于从根目录开始的某个文件的路径。相对 URI 不以 scheme 开始,一个例子是 articles/articles.html 。能够把相对 URI 看做是以某种方式引用某种资源,而这种方式依赖于标识符出现的环境。若是用文件系统做类比,相对 URI 相似于从当前目录开始的文件路径。
 
URI 能够进一步分为不透明的( opaque )和分层( hierarchical )的两类。不透明的 URI scheme-specific-part 不是以‘ / ’开头的绝对的 URI 。其例子有 news:comp.lang.java 和前面的 mailto:jeff@javajeff.com 。不透明的 URI 不能作进一步的解析,不须要验证 scheme-specific-part 的有效性。与它不一样的是,分层的 URI 是以‘ / ’开头的绝对的 URI 或相对的 URL 。分层的 URI scheme-specific-part 必须被分解为几个组成部分。分层的 URI scheme-specific-part 必须符合下面的语法:

[//authority] [path] [?query] [#fragment]

可选的受权机构( authority )标识了该 URI 名字空间的命名机构。若是有这一部分则以‘ // ’开始。它能够是基于服务器或基于受权机构的。基于受权机构有特定的语法(本文没有讨论,由于不多使用它),而基于服务器的语法以下:

[userinfo@] host [:port]

基于服务器的 authority 以用户信息(例如用户名)开始,后面跟着一个 @ 符号,紧接着是主机的名称,以及冒号和端口号。例如 jeff@x.com:90 就是一个基于服务器的 authority ,其中 jeff 为用户信息, x.com 为主机, 90 为端口。

可选的 path 根据 authority (若是提供了)或 schema (若是没有 authority )定义资源的位置。路径( path )能够分红一系列的路径片段( path segment ),每一个路径片段使用‘ / ’与其它片段隔开。若是第一个路径片段以‘ / ’开始,该路径就被认为是绝对的,不然路径就被认为是相对的。例如, /a/b/c 由三个路径片段 a b c 组成,此外这个路径是绝对的,由于第一个路径片段( a )的前缀是‘ / ’。

可选的 query 定义要传递给资源的查询信息。资源使用该信息获取或生成其它的的数据传递回调用者。例如, http://www.somesite.net/a?x=y, x=y 就是一个 query ,在这个查询中 x 是某种实体的名称, y 是该实体的值。

最后一个部分是 fragment 。当使用 URI 进行某种检索操做时,后面执行操做的软件使用 fragment 聚焦于软件感兴趣的资源部分。

分析一个例子 ftp://george@x.com:90/public/notes?text=shakespeare#hamlet

上面的 URI ftp 识别为 schema ,把 george@x.com:90 识别为基于服务器的 authority (其中 george 是用户信息, x.com 是主机, 90 是端口),把 /public/notes 识别为路径,把 text=shakespeare 识别为查询,把 hamlet 识别为片段。本质上它是一个叫作 george 的用户但愿经过 /public/notes 路径在服务器 x.com 90 端口上检索 shakespeare 文本的 hamlet 信息。
 
URI 的标准化( normalize

标准化能够经过目录术语来理解。假定目录
x 直接位于根目录之下, x 有子目录 a b b 有文件 memo.txt a 是当前目录。为了显示 memo.txt 中的内容,你可能输入 type \x\.\b\memo.txt 。你也可能输入 type \x\a\..\b\memo.txt ,在这种状况下, a .. 的出现是没有必要的。这两种形式都不是最简单的。可是若是输入 \x\b\memo.txt ,你就指定了最简单的路径了,从根目录开始访问 memo.txt 。最简单的 \x\b\memo.txt 路径就是标准化的路径。

一般经过 base + relative URI 访问资源。 Base URI 是绝对 URI ,而 Relative URI 标识了与 Base URI 相对的资源。所以有必要把两种 URI 经过解析过程合并,相反地从合并的 URI 中提取 Relative URI 也是可行的。

假定把 x://a/ 做为 Base URI ,并把 b/c 做为 Relative URI Resolve 这个相对 URI 将产生 x://a/b/c 。根据 x://a/ 相对化( Relative x://a/b/c 将产生 b/c

URI
不能读取 / 写入资源,这是统一的资源定位器( URL )的任务。 URL 是一种 URI ,它的 schema 是已知的网络协议,而且它把 URI 与某种协议处理程序联系起来(一种与资源通信的读 / 写机制)。

URI
通常不能为资源提供持久不变的名称。这是统一的资源命名( URN )的任务。 URN 也是一种 URI ,可是全球惟一的、持久不便的,即便资源再也不存在或再也不使用。
 
使用 URI

Java
API 经过提供 URI 类(位于 java.net 包中),使咱们在代码中使用 URI 成为可能。 URI 的构造函数创建 URI 对象,而且分析 URI 字符串,提取 URI 组件。 URI 的方法提供了以下功能: 1 )决定 URI 对象的 URI 是绝对的仍是相对的; 2 )决定 URI 对象是 opaque 仍是 hierarchical 3 )比较两个 URI 对象; 4 )标准化( normalize URI 对象; 5 )根据 Base URI 解析某个 Relative URI 6 )根据 Base URI 计算某个 URI 的相对 URI 7 )把 URI 对象转换为 URL 对象。
URI 里面有多个构造函数,最简单的是 URI(String uri) 。这个构造函数把 String 类型的参数 URI 分解为组件,并把这些组件存储在新的 URI 对象中。若是 String 对象的 URI 违反了 RFC 2396 的语法规则,将会产生一个 java.net.URISyntaxException

下面的代码演示了使用 URI(String uri) 创建 URI 对象:
URI uri = new URI ("http://www.cnn.com");

若是知道 URI 是有效的,不会产生 URISyntaxException ,可使用静态的 create(String uri) 方法。这个方法分解 uri ,若是没有违反语法规则就创建 URI 对象,不然将捕捉到一个内部 URISyntaxException ,并把该对象包装在一个 IllegalArgumentException 中抛出。
下面的代码片段演示了 create(String uri)
URI uri = URI.create ("http://www.cnn.com");

URI 构造函数和 create(String uri) 方法试图分解出 URI authority 的用户信息、主机和端口部分。对于正确形式的字符串会成功,对于错误形式的字符串,他们将会失败。若是想确认某个 URI authority 是基于服务器的,而且 分解出用户信息、主机和端口,这时候能够调用 URI parseServerAuthority() 方法。若是成功分解出 URI ,该方法将返回包含用户信息、主机和端口部分的新 URI 对象,不然该方法将产生一个 URISyntaxException

下面的代码片段演示了 parseServerAuthority()
// 下面的 parseServerAuthority() 调用出现后会发生什么状况?
URI uri = new URI ("//foo:bar").parseServerAuthority();
 
一旦拥有了 URI 对象,你就能够经过调用 getAuthority() getFragment() getHost() getPath() getPort() getQuery() getScheme() getSchemeSpecificPart() getUserInfo() 方法提取信息。以及 isAbsolute() isOpaque() 等方法。

程序 1: URIDemo1.java
import java.net.*;

public class URIDemo1 {
 public static void main (String [] args) throws Exception {
    if (args.length != 1) {
      System.err.println ("usage: java URIDemo1 uri");
      return;
    }
    URI uri = new URI (args [0]);

    System.out.println ("Authority = " +uri.getAuthority ());
    System.out.println ("Fragment = " +uri.getFragment ());
    System.out.println ("Host = " +uri.getHost ());
    System.out.println ("Path = " +uri.getPath ());
    System.out.println ("Port = " +uri.getPort ());
    System.out.println ("Query = " +uri.getQuery ());
    System.out.println ("Scheme = " +uri.getScheme ());
    System.out.println ("Scheme-specific part = " + uri.getSchemeSpecificPart ());
    System.out.println ("User Info = " +uri.getUserInfo ());
    System.out.println ("URI is absolute: " +uri.isAbsolute ());
    System.out.println ("URI is opaque: " +uri.isOpaque ());
 }
}

输入 java URIDemo1 命令后,输出结果以下:
query://jeff@books.com:9000/public/manuals/appliances?stove#ge
Authority = jeff@books.com:9000
Fragment = ge
Host = books.com
Path = /public/manuals/appliances
Port = 9000
Query = stove
Scheme = query
Scheme-specific part = //jeff@books.com:9000/public/manuals/appliances?stove
User Info = jeff
URI is absolute: true
URI is opaque: false


URI 类支持基本的操做,包括标准化( normalize )、分解( resolution )和相对化( relativize )。下例演示了 normalize() 方法。

程序 2: URIDemo2.java
import java.net.*;

class URIDemo2 {
 public static void main (String [] args) throws Exception {
    if (args.length != 1) {
      System.err.println ("usage: java URIDemo2 uri");
      return;
    }
    URI uri = new URI (args [0]);
    System.out.println ("Normalized URI = " + uri.normalize());
 }
}

在命令行输入 java URIDemo2 x/y/../z/./q ,将看到下面的输出:
Normalized URI = x/z/q

上面的输出显示 y .. . 消失了。

URI 经过提供 resolve(String uri) resolve(URI uri) relativize(URI uri) 方法支持反向解析和相对化操做。若是指定的 URI 违反了 RFC 2396 语法规则, resolve(String uri) 经过的内部的 create(String uri) 调用间接地产生一个 IllegalArgumentException 下面 的代码演示了 resolve(String uri) relativize(URI uri)

程序 3: URIDemo3.java
import java.net.*;

class URIDemo3 {
 public static void main (String [] args) throws Exception {
    if (args.length != 2) {
      System.err.println ("usage: " + "java URIDemo3 uriBase uriRelative");
      return;
    }

    URI uriBase = new URI (args [0]);
    System.out.println ("Base URI = " +uriBase);

    URI uriRelative = new URI (args [1]);
    System.out.println ("Relative URI = " +uriRelative);

    URI uriResolved = uriBase.resolve (uriRelative);
    System.out.println ("Resolved URI = " +uriResolved);

    URI uriRelativized = uriBase.relativize (uriResolved);
    System.out.println ("Relativized URI = " +uriRelativized);
 }
}

编译 URIDemo3 后,在命令行输入 java URIDemo3 http://www.somedomain.com/ x/../y ,输出以下:
Base URI = http://www.somedomain.com/
Relative URI = x/../y
Resolved URI = http://www.somedomain.com/y
Relativized URI = y


使用 URL

Java
提供了 URL 类,每个 URL 对象都封装了资源标识符和协议处理程序。得到 URL 对象的途径之一是调用 URI toURL() 方法,也能够直接调用 URL 的构造函数来创建 URL 对象。

URL 类有多个构造函数。其中最简单的是 URL(String url) ,它有一个 String 类型的参数。若是某个 URL 没有包含协议处理程序或该 URL 的协议是未知的,其它的构造函数会产生一个 java.net.MalformedURLException
下面的代码片段演示了使用 URL(String url) 创建一个 URL 对象,该对象封装了一个简单的 URL 组件和 http 协议处理程序。
URL url = new URL ("http://www.informit.com");

一旦拥有了 URL 对象,就可使用 getAuthority() getDefaultPort() getFile() getHost() getPath() getPort() getProtocol() getQuery() getRef() getUserInfo() getDefaultPort() 等方法提取各类组件。若是 URL 中没有指定端口, getDefaultPort() 方法返回 URL 对象的协议默认端口。 getFile() 方法返回路径和查询组件的结合体。 getProtocol() 方法返回资源的链接类型(例如 http mailto ftp )。 getRef() 方法返回 URL 的片段。最后, getUserInfo() 方法返回 Authority 的用户信息部分。还能够调用 openStream() 方法获得 java.io.InputStream 引用。使用这种引用,能够用面向字节的方式读取资源。
 
 
下面是 URLDemo1 的代码。该程序创建一个 URL 对象,调用 URL 的各类方法来检索该 URL 的信息,调用 URL openStream() 方法打开与资源的链接并读取 / 打印这些字节。

程序 4: URLDemo1.java
import java.io.*;
import java.net.*;

class URLDemo1 {
 public static void main (String [] args) throws IOException {
    if (args.length != 1) {
    System.err.println ("usage: java URLDemo1 url");
    return;
    }

    URL url = new URL (args [0]);

    System.out.println ("Authority = "+ url.getAuthority ());
    System.out.println ("Default port = " +url.getDefaultPort ());
    System.out.println ("File = " +url.getFile ());
    System.out.println ("Host = " +url.getHost ());
    System.out.println ("Path = " +url.getPath ());
    System.out.println ("Port = " +url.getPort ());
    System.out.println ("Protocol = " +url.getProtocol ());
    System.out.println ("Query = " +url.getQuery ());
    System.out.println ("Ref = " +url.getRef ());
    System.out.println ("User Info = " +url.getUserInfo ());

    System.out.print ('\n');

    InputStream is = url.openStream ();

    int ch;
    while ((ch = is.read ()) != -1) {
      System.out.print ((char) ch);
    }
    is.close ();
 }
}

在命令行输入 java URLDemo1 http://www.javajeff.com/articles/articles/html 后,上面的代码的输出以下:
Authority = http://www.javajeff.com
Default port = 80
File = /articles/articles.html
Host = http://www.javajeff.com
Path = /articles/articles.html
Port = -1
Protocol = http
Query = null
Ref = null
User Info = null



URL openStream() 方法返回的 InputStream 类型,这意味着你必须按字节次序读取资源数据,这种作法是恰当的,由于你不知道将要读取的数据是什么类型。若是你事先知道要读取的数据是文本,而且每一行以换行符( \n )结束,你就能够按行读取而不是按字节读取数据了。

下面的代码片段演示了把一个 InputStream 对象包装进 InputStreamReader 以从 8 位过渡到 16 位字符,进而把结果对象包装进 BufferedReader 以调用其 readLine() 方法。
 
InputStream is = url.openStream ();
BufferedReader br = new BufferedReader (new InputStreamReader (is));
String line;
while ((line = br.readLine ()) != null) {
System.out.println (line);
}
is.close ();

有时候按字节的次序读取数据并不方便。例如,若是资源是 JPEG 文件,那么获取一个图像处理过程并向该过程注册一个用户使用数据的方法更好。若是出现这种状况,你就有必要使用 getContent() 方法。

当调用 getContent() 方法时,它会返回某种对象的引用,而你能够调用该对象的方法(在转换成适当的类型后),采用更方便的方式取得数据。可是在调用该方法前,最好使用 instanceof 验证对象的类型,防止类产生异常。

对于 JPEG 资源, getContent() 返回一个对象,该对象实现了 java.awt.Image.ImageProducer 接口。下面的代码演示了使用如何 getContent()
URL url = new URL (args [0]);
Object o = url.getContent ();
if (o instanceof ImageProducer) {
 ImageProducer ip = (ImageProducer) o;
 // ...
}


查看一下 getContent() 方法的源代码,你会找到 openConnection().getContent() URL openConnection() 方法返回一个 java.net.URLConnection 对象。 URLConnection 的方法反映了资源和链接的细节信息,使咱们能编写代码访问资源。

下面 URLDemo2 代码演示了 openConnection() ,以及如何调用 URLConnection 的方法。

程序 5: URLDemo2.java
import java.io.*;
import java.net.*;
import java.util.*;

class URLDemo2 {
 public static void main (String [] args) throws IOException {
    if (args.length != 1) {
      System.err.println ("usage: java URLDemo2 url");
      return;
    }

    URL url = new URL (args [0]);

    //
返回表明某个资源的链接的新的特定协议对象的引用
    URLConnection uc = url.openConnection ();

    //
进行链接
    uc.connect ();

    //
打印 header 的内容
    Map m = uc.getHeaderFields ();
    Iterator i = m.entrySet ().iterator ();
    while (i.hasNext ()) {
      System.out.println (i.next ());
    }
    //
检查是否资源容许输入和输出操做
    System.out.println ("Input allowed = " +uc.getDoInput ());
    System.out.println ("Output allowed = " +uc.getDoOutput ());
 }
}
 
 
URLConnection getHeaderFields() 方法返回一个 java.util.Map 。该 map 包含 header 名称和值的集合。 header 是基于文本的名称 / 值对,它识别资源数据的类型、数据的长度等等。

编译 URLDemo2 后,在命令行输入 java URLDemo2 http://www.javajeff.com ,输出以下:
Date=[Sun, 17 Feb 2002 17:49:32 GMT]
Connection=[Keep-Alive]
Content-Type=[text/html; charset=iso-8859-1]
Accept-Ranges=[bytes]
Content-Length=[7214]
null=[HTTP/1.1 200 OK]
ETag=["4470e-1c2e-3bf29d5a"]
Keep-Alive=[timeout=15, max=100]
Server=[Apache/1.3.19 (Unix) Debian/GNU]
Last-Modified=[Wed, 14 Nov 2001 16:35:38 GMT]
Input allowed = true
Output allowed = false


仔细看一下前面的输出,会看到叫作 Content-Type 的东西。 Content-Type 识别了资源数据的类型是 text/html text 部分叫作类型, html 部分叫作子类型。若是内容是普通的文本, Content-Type 的值多是 text/plain text/html 代表内容是文本的可是 html 格式的。
 
Content-Type 是多用途 Internet 邮件扩展( MIME )的一部分。 MIME 是传统的传输消息的 7 ASCII 标准的一种扩展。经过引入了多种 header MIME 使视频、声音、图像、不一样字符集的文本与 7 ASCII 结合起来。当使用 URLConnection 类的时候,你会遇到 getContentType() getContentLength() 。这些方法返回的值是 Content-Type Content
相关文章
相关标签/搜索