Java网络编程学习笔记(一):URI和URL总结

1,671 阅读11分钟
原文链接: www.geekmuseo.com

1、URI概述

URI全称统一资源标识符,它是采用一种特定语法标识一个资源的字符串。

URI的语法由以下三部分组成,它们之间用冒号隔开

模式:模式特定部分:片段


1.1、模式

目前场景的模式有:

1、data:链接中直接包含的Base64编码数据

2、file:本地磁盘文件

3、ftp:FTP服务器

4、http:使用超文本传输协议的互联网服务器

5、mailto:电子邮件地址

6、magnet:磁力链接

7、telnet:基于Telnet服务的连接

8、URN:统一资源名


1.2、模式特定部分

没有统一格式,随着模式的变化而变化


1.3、片段

是用来指定资源的特定部分


2、URI类

2.1、构造函数

public URI(String str) throws URISyntaxException

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

public URI(String scheme, String host, String path, String fragment)throws URISyntaxException

public URI(String scheme,String authority,String path, String query, String fragment)throws URISyntaxException

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

URI不依赖具体协议,只要语法符合URI规范,就可以构造一个URI对象,如果语法错误,则会抛出一个URISyntaxException异常,下面是一个自己发明的关于书籍位置的URI:URI book = new URI(“urn:isbn:1-298-11AB009-4”);

上面5个URI构造函数,从上到下由一般到特殊,最后一个URI构造函数,可以看出来它的语法和URL的语法已经没什么区别了,由此也可以看出,URL是URI的一种特殊形式。


2.2、URI组成部分

URI由三个部分组成:模式、模式特定部分、片段,因此有下面的获取方法:

public String getScheme()

public String getSchemeSpecificPart()

public String getRawSchemeSpecificPart()

public String getFragment()

public String getRawFragment()

上面带Raw的是返回的原始数据,不带Raw的返回的是经过解码后的数据。模式(Scheme)由于规范要求必须是ASCII字符组成,不允许有百分号转义,因此没有带Raw的方法。上面三个组成部分不一定都有,没有的话就会返回null。


模式特定部分(SchemeSpecificPart)如果有一个特定的分层格式,则表明该URI是透明的,否则就是不透明的,可以使用下面的方法进行判断:

public boolean isOpaque()

对于透明的URI,我们除了可以获得模式、模式特定部分、片段外,还可以获得更详细的信息:

public String getAuthority()

public String getHost()

public int getPort()

public String getPath()

public String getQuery()

public String getUserInfo()

这些方法除了host和port外(这两个永远是ASCII字符组成),同样含有相应的Raw版本:

public String getRawAuthority()

public String getRawPath()

public String getRawQuery()

public String getRawUserInfo()


2.3、URI的转换

有模式的URI对象是绝对的,没有模式的URI对象是相对的,可以使用下面的方法进行判断:

public boolean isAbsolute()

对于相对URI和绝对URI,我们可以使用下面三个方法进行互相转换:

public URI resolve(String str)

public URI resolve(URI uri)

public URI relativize(URI uri)
相对URI转换成绝对URI:

URI absolute = new URI(“http://www.example.com/”);

URI relative = new URI(“html/book.html”);

URI result = absolute.resolve(relative)

最终result的结果是:www.example.com/html/book.h…

绝对URI转换成相对URI:

URI absolute = new URI(“http://www.example.com/images/banner.jpg”);

URI u = new URI(“http://www.example.com/”)

URI result = u.relativize(absolute)

最终result的结果是:images/banner.jpg


2.4、URI的比较

URI实现了Comparable接口,因此是可以比较的,它按照下列规则进行比较:

1、如果模式不同就比较模式,不考虑大小写

2、否则,如果模式相同,一般认为透明URI小于不透明URI

3、如果两个URI都是不透明的,则根据模式特定部分进行比较

4、如果模式特定部分相同,则比较片段

5、如果两个URI都是透明的但不同,则比较授权信息,授权信息本身按照用户信息、主机、端口进行比较,主机比较不区分大小写

6、如果授权信息相同,则比较路径

7、如果路径相同则比较查询字符串

8、如果查询字符串相同,则比较片段

3、URL概述

URL是URI的一个特殊形态,它不仅包含资源的标识,还提供了获取资源的方法。通俗的讲,URI告诉你一个资源是什么,URL不仅告诉你是什么,还告诉你如何取得这个资源。

URL的语法为:

protocol://usrInfo@host:port/path?query#fragmentprotocol是URI中的模式,在URL中称为协议userInfo是用户登录服务器时的登录信息,一般在ftp服务器里比较常见,比如:ftp://user:password@aashu-a.sshia.nc.home.com:33/cobert.txt 

host是服务器名字,它可以是主机名,比如www.baidu.com,也可以是IP地址,比如204.118.40.9

port是端口号,不同的协议端口号不同,比如http协议的端口号为80,telnet端口号为23

path是服务器上的一个特定文件路径,它类似于文件系统路径,比如/forum/index.jsp

query是字符串查询参数,是传递给服务器的,一般只用于HTTP URL中。

fragment是指远程资源的某个特定部门,比如远程资源是HTML,那么这个片段标识符将指定该HTML文档中的一个锚。如果远程资源是一个XML文档,那么这个片段标识符将指定该XML文档中的一个XPointer


4、URL类

4.1、构造函数

public URL(String spec) throws MalformedURLException

public URL(String protocol, String host, String file)throws MalformedURLException

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

public URL(URL context, String spec) throws MalformedURLException

上面这些构造函数的使用取决于你所拥有的信息,URL会校验语法,如果语法不正确则抛出MalformedURLException。URL具体支持哪些协议由虚拟机决定,一般都会支持http、https、file、jar等协议。

除了通过构造函数获取一个URL对象,还可以通过其他方式获取,比如:ClassLoader.getSystemResource(String name)返回一个URL对象java.swing.JEditorPane.getPage()返回一个URL对象java.net.URLConnection.getURL()返回一个URL对象


4.2、URL的组成部分

由上面可知,URL的语法为:

protocol://usrInfo@host:port/path?query#fragment

针对URL的语法,URL类提供以下方法进行各个部分的获取:

public String getProtocol()

public String getUserInfo()

public String getHost()

public int getPort()

public int getDefaultPort()

public String getPath()

public String getQuery()

public String getRef()

public String getFile()

上面这些方法中,唯一需要注意的是最后一个方法getFile,它是一个快捷方法,用于返回主机名后第一个斜线(/)开始,一直到片段标识符之前的字符串。

4.3、URL获取资源

URL和URI最大的区别在于URL可以获取资源,而URI不能。

URL通过下列方法进行资源获取:

public final InputStream openStream() throws IOException

public URLConnection openConnection() throws IOException

public URLConnection openConnection(Proxy proxy)throws IOException

public final Object getContent() throws IOException

public final Object getContent(Class[] classes)throws IOException


4.3.1、openStream()

openStream()方法连接到URL所引用的资源,在客户端和服务器之间完成必要的握手,然后返回InputStream,可以由此读取数据。从这个InputStream读取的是URL引用的原始数据,如果是读取的是ASCII文件则返回的是ASCII,读取的是HTML文件则返回的是原始HTML,读取的是图像文件则返回二进制数据。


4.3.2、openConnection()

openConnection()方法为指定的URL打开一个socket,然后返回一个URLConnection,我们可以通过如下方法获取流:URLConnection uc = url.openConnection()InputStream in = uc.getInputStream();URLConnection除了获取流外,还有其他功能,具体在下一节介绍。如果你仅仅想获取流,那么用openStream()方法就足够了。

openConnection()还有个重载方法:

openConnection(Proxy proxy),它指定了一个代理服务器,会覆盖默认的socksProxyHost、socksProxyPort、http.proxyHost、http.proxyPort这些代理服务器属性。如果底层不支持代理,则会忽略传入的参数,直接建立连接。


4.3.3、getContent()

getContent()方法和它的重载方法getContent(Class[] classes),首先会获取远程资源,然后查找Content-type首部字段,如果找不到或者无法识别,则返回一个InputStream,如果找到了则尝试建立某种类型的对象。


4.4、相等性和比较

URL类包含equals()和hashCode()方法,当且仅当两个URL都指向相同的主机、端口、路径、查询字符串、片段标识符时才认为他们是相等的。不过,值得注意的是,equals()方法会尝试用DNS解析主机,而这个行为可能导致socket阻塞,因此,尽量使用URI而不是URL进行比较,特别是当需要把URL存入HashMap的时候,尽量用URI类代替。

5、URLConnection

URLConnection是一个抽象类,表示指向URL资源的活动连接。URLConnection的出现是为了对客户端和服务器的交互提供更多控制。按理说URLConnection应该是各种协议的抽象,Java实际却把它设计成了HTTP的URLConnection。URLConnection提供了获取服务器首部的接口,也提供了设置客户端首部的接口,而很多协议并不需要首部(比如FTP)。


5.1、构建URLConnection

URLConnection是个抽象类,且构造函数是保护方法,因此它无法直接构造,必须通过URL类进行构造:

URL url = new URL(“http://www.baidu.com);

URLConnection uc = url.openConnection()


5.2、读取首部

HTTP服务器的每个响应都包含很多首部,这些首部提供了大量有用信息,比如:HTTP/1.1 304 Not Modified

Server: NWS_TCloud_S1

Connection: keep-alive

Date: Mon, 04 Dec 2017 06:17:52 GMT

Cache-Control: max-age=600

Expires: Mon, 04 Dec 2017 06:27:52 GMT

Content-Type: application/javascript

Content-Length: 0

一般来说,HTTP首部可能包括所请求文档的内容类型、文档长度、编码字符集、日期、过期时间、最后修改时间等等。这些在URLConnection中都有相应的方法:

public String getContentType()

public int getContentLength()

public String getContentEncoding()

public long getDate()

public long getExpiration()

public long getLastModified()


a)、public String getContentType()

返回响应主体的MIME内容类型,也就是首部Content-Type的值。它依赖于服务器发送的内容,如果服务器没有发送,则返回null。常见的MIME有:text/plain、image/gif、application/javascript等等


b)、public int getContentLength()

返回响应体的内容的长度,也就是首部Content-Type的值,如果没有这个首部则返回-1。这个方法的返回值为int类型,如果是大文件,int可能会溢出,可以使用getContentLengthLong()返回long类型的内容长度。


c)、public String getContentEncoding()

返回响应编码,也就是首部Content-Encoding的值。如果没有编码则返回null。注意这个编码不是字符编码,而是内容编码,比如x-gizp用来对内容进行压缩。字符编码会出现在Content-Type首部中


d)、public long getDate()

返回响应时间,也就是首部Date的值,它返回的是一个毫秒数。


e)、public long getExpiration()

指定文档何时过期的首部,一旦时间超过了过期时间,则客户端需要重新下载文档。返回的是一个毫秒数。


f)、public long getLastModified()

返回文档最后更新时间。

5.3、获取任意首部

针对不常用的或者自定义的首部,URLConnection提供了获取任意首部的方法。public String getHeaderField(String name)

public String getHeaderFieldKey(int n)

public String getHeaderField(int n)

public int getHeaderFieldInt(String name, int Default)

public long getHeaderFieldLong(String name, long Default)

public long getHeaderFieldDate(String name, long Default)

public Map<String,List<String>> getHeaderFields()


a)、public String getHeaderField(String name)

返回指定首部字段的值,5.2中的6个方法底层都是调用的这个方法


b)、public String getHeaderFieldKey(int n)

这个方法返回第N个首部字段的键名。请求方法本身是第0个首部字段,实际的第一个首部字段编号为1.


c)、public String getHeaderField(int n)

这个方法返回第N个首部字段的值。请求方法本身是第0个首部字段,实际的第一个首部字段编号为1.


d)、public int getHeaderFieldInt(String name, int Default)

获取指定首部字段的值,并会转换成int类型,如果没有获取到则用默认值替代。


e)、public long getHeaderFieldLong(String name, long Default)

获取指定首部字段的值,并会转换成long类型,如果没有获取到则用默认值替代。


f)、public long getHeaderFieldDate(String name, long Default)

获取指定首部字段的值,并会转换成相应日期的毫秒数,如果没有获取到则用默认值替代。


g)、public Map<String,List<String>> getHeaderFields()

返回所有首部字段的映射关系


5.4、读取响应

URLConnection会返回一个InputStream,然后进行内容读取:

URL url = new URL(“http://www.baidu.com);

URLConnection uc = url.openConnection()

InputStream in = uc.getInputStream()


5.5、向服务器写数据

a)、设置客户端首部

URLConnection可以通过下面的方法设置客户端首部:

public void setRequestProperty(String key, String value)

比如客户端设置cookie:uc.setRequestProperty(“Cookie”,”username=test;session=asas1902903″);

如果出于某种原因需要检查客户端首部,则可以用下面的方法:

public String getRequestProperty(String key)

public Map<String,List<String>> getRequestProperties()


b)、写入数据

URLConnection不仅可以读数据,还可以写数据,它通过返回一个OutputStream来实现:

public OutputStream getOutputStream() throws IOException

具体的例子如下:

URL url = new URL(“http://www.example.com/search.do”);

URLConnection uc = url.openConnection();

uc.setDoOutput(true);


OutputStream raw = uc.getOutputStream();

OutputStream buffer = new BufferedOutputStream(raw);

OutputStreamWriter writer = new OutputStreamWriter(buffer,”UTF-8″);

writer.write(“key=Julie&page=2\r\n”);

writer.flush();

writer.close();

6、URLEncoder类/URLDecoder类

URLEncoder和URLDecoder是两个工具类,用来对URL内容进行编解码,不过值得注意的是,URLEncoder在编码的时候会将一切非ASCII字符进行编码,比如:www.baidu.com编码后的结果是:http%3A%2F%2Fwww.baidu.com这显然不是我们想要的结果,因此在使用URLEncoder的时候,需要人工干涉编码哪些部分,比如只对查询串进行编码。