面向基础系列之---Java网络编程---网络连接组件的使用(URL与URI) 3ff0
2018-09-12 17:31
851 查看
一、URI与URL的定义与区别
URI:统一资源标识符(Uniform Resource Identifier)是采用一种特定的语法标识一个资源的字符串URL:是一个URI,除了能标识一个资源,还会为资源提供一个特定的网络位置,客户端可以用它来获取资源
1、URI组成
a、基本格式如下:模式:模式特定部分
b、一些模式列举:
data:包含Base64编码的数据
file:本地磁盘上的文件
ftp:FTP服务器
http:使用超文本传输协议的国际互联网服务
mailto:电子邮件地址
magnet:可以通过对等网络下载的资源(例如BT种子)
telnet:telnet服务器
urn:统一资源名
2、URI编码
编码,对于这个章节来说,是一个重头戏。往往一个链接地址,会被编码问题搞得晕头转型,所以要特别重视a、模式部分组成:
字母
数字
加号(+)
点(.)
连号(-)
b、模式特定部分组成:
ASCII字母数字
-、_、.、!和~
定界符:/、?、&和=
其他非ASCII字符,都要用%号进行转义编码
”/“与”@“如果不是用于特定的部分(定界符)都要进行转义编码
3、URL组成
http://my.oschina.net/ubw/blog/Protocol:/userInfo@host:port/path?query#fragment按照上面的罗列,顺序说就是:
协议(protocol)
用户信息(userInfo)
URL主机(host)
端口(port)
路径(path)
查询(query)
片段(fragment)
4、理解相对URL
这里主要理解两种变现形式的相对URL定位,绝对路径是https://my.oschina.net/UBW/blog/2046461情况下a、第一种导航定位:
<a href="javafaq.html">
结果是:https://my.oschina.net/UBW/blog/javafaq.html
b、第二种导航定位
<a href="/projects/ipv6/">
结果是:https://my.oschina.net/projects/ipv6/
二、URL类
java.net.URL类是对统一资源定位符的抽象:final类型,直接继承Object类,线程安全
内部使用了策略设计模式,根据不同策略处理不同协议
既能标识资源,又能获取资源
1、创建URL
public URL(String spec) throws MalformedURLException//① public URL(String protocol, String hostname, String file) throws MalformedURLException//② public URL(String protocol, String host, int port, String file) throws MalformedURLException//③ public URL(URL base, String relative) throws MalformedURLException//④
a、公共的一些点:
如果所在的JVM不支持某种协议,创建URL的时候会抛出MalformedURLException异常
Java不对对它构造的URL完成任何正确性检查与合法性检查
b、判断一个协议是否被支持的代码片段:
public static void testProtocol(String url){ try{ URL url = new URL(url); System.out.prinln("supported"); }catch (MalformedURLException e){ System.out.prinln("not supported"); } }
c、②与③构造的情况
②的构造器没有port,所以默认设置成-1,-1的port被解析成默认协议的端口,例如http就是80
file这个参数包含:路径、文件名、可选的片段标识符
file是一个全路径,如果不小心写成不是/开头的相对路径,会抛错MalformedURLException
d、④的使用
try{ URL u1 = new URL("https://my.oschina.net/UBW/blog/2046461"); URL u2 = new URL(ur, "mailinglists.html"); /** u2包装的url是:https://my.oschina.net/UBW/blog/mailinglists.html */ } catch(MalformedURLException e){ System.out.prinln(e); }
2、通过URL获取地址数据
这个小结是重点的重点,主要获取地址数据的方法,为后面的服务器构建的基石。public final InputStream openStream() throws java.io.IOException //① public URLConnection openConnection() throws java.io.IOException //② public final Object getContent() throws java.io.IOException //③ public final Object getContent(Class[] classes) throws java.io.IOException //④
a、①方法:简单的获取原始内容,不包括任何http的header与协议其他信息
import java.io.IOException; import java.io.InputStream; import java.net.URL; public class NetworkMain { public static void main(String[] args) { try { URL u = new URL("http://www.baidu.com"); InputStream inputStream = u.openStream(); int c; while((c = inputStream.read()) != -1){ System.out.print((char) c); } inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
这种方式是最简单的方式,也是用处最少的,因为只能获取简单的文本方式的内容,对二进制的文件内容就无济于事了。并且还获取不到header,这个对于识别文件的编码,是很不友好的。
b、②方法:出了原始内容之外,可以访问这个协议指定的所有元数据和header
import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.List; import java.util.Map; public class NetworkMain { public static void main(String[] args) { try { URL u = new URL("http://www.baidu.com"); URLConnection urlConnection = u.openConnection(); Map<String, List<String>> headerFields = urlConnection.getHeaderFields(); // 这里就是获取header的方式 for(Map.Entry<String, List<String>> entry : headerFields.entrySet()){ System.out.println(entry.getKey()+":"+entry.getValue().toString()); } // 获取的输入流其实也是内容 InputStream inputStream = urlConnection.getInputStream(); int c; while((c = inputStream.read()) != -1){ System.out.print((char) c); } inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
c、③方法:返回一个Object对象,根据源的不同能向下转型成不同的对象
例如图像对象java.awt.image.ImageProducer
例如音频对象sun.applet.AppletAudioClip
例如我们熟悉的对象java.io.InputStream
这个方法会读取header里面的Content-type字段,来进行类型识别。如果不识别的对象,会返回一个java.io.InputStream对象来代替
d、④方法:类似于③,不过会更具传入的class对象返回所希望的对象
URL u = new URL("http://www.nwu.org"); Class<?>[] types = new Class[3]; types[0] = String.class; types[1] = Reader.class; types[2] = InputStream.class; Object o = u.getContent(types);
3、获取URL各个部分
下面写一个完整的URL的地址:http ://admin:123456@www.ibiblio.org:8080/java/books/jnp/index.html/isbn=123423423#toc
模式(http)
授权机构:用户信息+主机名+端口(admin:123456@www.ibiblio.org:8080)
路径(/java/books/jnp/index.html)
片段标识符(toc)
查询字符串 (isbn=123423423)
public String getProtocol();//协议信息(例如:http、https) public String getHost();//主机名 public int getPort();//端口,没指定的话返回-1 public int getDefaultPort();//协议的默认端口,协议没有默认端口的话返回-1,即使设定了端口,也不影响这个的返回 public String getFile();//路径+文件名(例如上面url中的:/java/books/jnp/index.html) public String getPath();//路径(例如上面url中的:/java/books/jnp/) public String getRef();//片段(例如上面例子中的toc) public String getQuery();//查询字符串例如(isbn=123423423) public String getUserInfo();//用户信息,例如(admin:123456) public String getAuthority();//授权机构
4、URL相等性比较
equal()方法会阻塞,进行DNS查询,主机一样,将会判定相等当且仅当两个URL有相同的主机、端口和路径上相同的资源,而且有相同的片段和查询,才相等
URL没有实现Comparable接口
http://www.url.com:80 不等于http://www.url.com
http://www.url.com/index.html 不等于http://www.url.com
sameFile()不考虑片段标识符
URL的equal是先根据字面进行层级性判定,如果字面都有着同样的层级且不相等,才会进行DNS解析
/*** * equals源码解析:不难,几乎都是字符串的判断,就一处是使用了InetAdress */ //java.net.URLStreamHandler protected boolean equals(URL u1, URL u2) { String ref1 = u1.getRef(); String ref2 = u2.getRef(); return (ref1 == ref2 || (ref1 != null && ref1.equals(ref2))) && sameFile(u1, u2); } protected boolean sameFile(URL u1, URL u2) { // Compare the protocols. if (!((u1.getProtocol() == u2.getProtocol()) || (u1.getProtocol() != null && u1.getProtocol().equalsIgnoreCase(u2.getProtocol())))) return false; // Compare the files. if (!(u1.getFile() == u2.getFile() || (u1.getFile() != null && u1.getFile().equals(u2.getFile())))) return false; // Compare the ports. int port1, port2; port1 = (u1.getPort() != -1) ? u1.getPort() : u1.handler.getDefaultPort(); port2 = (u2.getPort() != -1) ? u2.getPort() : u2.handler.getDefaultPort(); if (port1 != port2) return false; // Compare the hosts. // hostsEqual会调用InetAdress的getByName(),进行DNS解析 if (!hostsEqual(u1, u2)) return false; return true; } protected boolean hostsEqual(URL u1, URL u2) { InetAddress a1 = getHostAddress(u1); InetAddress a2 = getHostAddress(u2); // if we have internet address for both, compare them if (a1 != null && a2 != null) { return a1.equals(a2); // else, if both have host names, compare them } else if (u1.getHost() != null && u2.getHost() != null) return u1.getHost().equalsIgnoreCase(u2.getHost()); else return u1.getHost() == null && u2.getHost() == null; } protected synchronized InetAddress getHostAddress(URL u) { if (u.hostAddress != null) return u.hostAddress; String host = u.getHost(); if (host == null || host.equals("")) { return null; } else { try { // 这里进行了DNS查询操作 u.hostAddress = InetAddress.getByName(host); } catch (UnknownHostException ex) { return null; } catch (SecurityException se) { return null; } } return u.hostAddress; }
三、URI类
列出一些关键点吧:URI完全有关于资源的标识和URI解析。
URI不能解析目标地址的数据
如果单纯进行地址字符串的比较,最好使用URI,因为URL的比较方法有可能进行DNS查询,而RUI不会
URI有toURL()方法,同样的URL也有toURI()方法
1、创建URI
public URI(String uri) throws URISyntaxException;//① public URI(String scheme, String schemeSpecificPart, 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 URISyntaxExceptio 3ff8 n;//④ public URI(String scheme, String userInfo, String host, int port, String path, String query, String fragment) throws URISyntaxException;//⑤
URI不依赖于底层的协议处理器,所以只要语法上面正确,就可以,所以可以创建类似于:tel:、urn:等这种
如果URI以冒号开头,说白了就是语法不正确,就会抛出URISyntaxException
scheme是协议也是模式的意思,必须由ASCII字母、数字以及三个标点符号(+、-和.)组成,并且必须以字母开头
模式scheme字段可以为null,如果为null那么就是一个相对的URI
模式scheme字段不为null的时候,并且path参数不以/开头,那么就会抛出URISyntaxException异常
上面各个构造方法都是对各个参数进行合法性检查的。如果确定URI是一个合法的URI,可以直接使用
public static URI create(String str)进行创建
2、URI各部分
这里有个不透明和层次的概念,很难理解a、透明与不透明(opaque:不透明的)
下面是JDK源码:/** * Tells whether or not this URI is opaque. * * <p> A URI is opaque if, and only if, it is absolute and its * scheme-specific part does not begin with a slash character ('/'). * An opaque URI has a scheme, a scheme-specific part, and possibly * a fragment; all other components are undefined. </p> * * @return {@code true} if, and only if, this URI is opaque */ public boolean isOpaque() { return path == null; }
解释下方法官方说明:一个URI如果要是不透明,当且仅当他是一个绝对路径且他的模式特定部分不是/开头。一个不透明的URI会有模式,模式头顶部分,和可能存在片段。其他各个部分都是未定义的。
如果一个URI是透明的,就是我们经常使用的层次形式的URI,当然URL是透明的
其他的各种URI都是不透明的,isOpaque都返回true
public static void main(String[] args) { try { URI opaque = new URI("mail:jicheng@163.com"); System.out.println(opaque.isOpaque());//true URI notOpaque = new URI("mail:/jicheng@163.com"); System.out.println(notOpaque.isOpaque());//false } catch (URISyntaxException e) { e.printStackTrace(); } }
b、获取各个部分
//下面方法获取到的是解码的字符串 public String getAuthority(); public String getFragment(); public String getHost(); public String getPath(); public int getPort(); public String getQuery(); public String getUserInfo(); //下面方法获取到的是未解码的字符串,就是带%那种 public String getRawAuthority(); public String getRawFragment(); public String getRawPath(); public String getRawQuery(); public String getRawUserInfo();
不透明的URI只能获取到getScheme、getSchemeSpecificPart、getFragment
c、URI相等性比较
几点要注意的:比较模式与授权机构不区分大小写,其余部分区分
转义字符比较前不解码
hashCode与相等性是一致行为
下面是比较点,顺序向下进行判断:
如果模式不同就比价模式,不区分大小写
模式相等,一般认为透明(层次)的URI小于有相同模式的不透明的URI
如果都是不透明的URI,则根据模式特定部分进行比较
如果不透明切模式特定部分都相等,就根据片段进行比较
如果两个URI都是透明的,则根据授权机构进行排序,其中主机比较是不区分大小写
如果模式和授权机构都相等,使用路径来排序
如果路径也相等,就比较查询字符串
如果查询字符串也相等,就比较片段
public static void main(String[] args) { try { URI uri1= new URI("maIL://jicheng:jicheng@wwW.JICHENg.com"); URI uri2= new URI("mail://jicheng:jicheng@www.jicheng.com"); System.out.println(uri1.equals(uri2));//true } catch (URISyntaxException e) { e.printStackTrace(); } }
四、URL的编码与解码
对于URL字符串的编解码是业界的一个频率非常高的操作。平时互联网的相关开发,尤其要注意这个。1、URL字符的组成
大写字母小写字母
数字
标点:-、_、.、!、~、*、'、,
字符:/、&、?、@、#、;、$、+、=、%出现在路径或者查询字符串中要进行编码
2、URLEncoder
默认情况下使用这个进行编码,会对路径分隔符:/、&、=、:进行编码的,这不是我们想要的,我们只要对查询字符串进行编码就行:public static void main(String[] args) { String url = "https://www.jicheng.com/search?"; try { url+=URLEncoder.encode("h1","UTF-8"); url+="="; url+=URLEncoder.encode("I/O","UTF-8"); System.out.println(url);//https://www.jicheng.com/search?h1=I%2FO } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }
3、URLDecoder
这个可以全部字符串进行解码操作,不用担心局部被影响的问题了public static void main(String[] args) { String url = "//https://www.jicheng.com/search?h1=I%2FO"; try { String decode = URLDecoder.decode(url, "UTF-8"); System.out.println(decode); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }
相关文章推荐
- 面向基础系列之---Java网络编程---网络连接组件的使用(URLConnecti 3ff8 on)
- JAVA基础知识之网络编程——-使用Proxy创建连接
- Java基础复习笔记系列 九 网络编程
- Java网络编程之URI、URL研究(下) 03
- Java网络编程之URI、URL研究
- Java网络编程基础 ServerSocket类使用
- Java基础知识强化之网络编程笔记01:InetAddress类的概述和使用
- java基础--网络编程之 自定义浏览器和URL类的使用
- Java基础知识强化之网络编程笔记18:Android网络通信之 使用HttpClient的Post / Get 方式读取网络数据(基于HTTP通信技术)
- Java基础 网络编程 Socket UDP TCP URL
- Java网络编程基础 ServerSocket类使用
- Java网络编程之URI、URL研究(下)
- Java基础知识强化之网络编程笔记16:Android网络通信之 使用Http的Get方式读取网络数据(基于HTTP通信技术)
- 网络编程基础:使用UDP连接
- Java 网络编程(六) 使用无连接的数据报(UDP)进行通信
- 网络编程基础:使用TCP连接
- Java网络编程之URI、URL研究(下) 01
- java基础--网络编程之 自定义浏览器和URL类的使用
- Java网络编程基础 InetAddress类的使用
- Java网络编程基础 InetAddress类的使用