URL可以唯一地标识一个资源在Internet上的位置。URL是最常见的URI
URI是采用一种特定的语法标识一个资源的字符串.例如https://www.myserver.com/a.png
URI的结构:
模式:模式特定部分
常见的模式有:
data file ftp http mailto
magnet telnet urn
模式特定部分一般采用层次的结构,非ASCII字符要转换成UTF-8编码的十六进制码
URL是一个URI,除了标识一个资源,还会为资源提供一个特定的网络位置,客户端可以用它来获取这个资源。与之不同的时,通常URI可以告诉你一个资源是什么,但是无法告诉你在哪里,以及如何得到这个资源。
URL中的网络位置通常包括用来访问服务器的协议(FTP、HTTP)、服务器的主机或IP地址、以及文件在该服务器上的路径。
URL的语法为:
protocol://userInfo@host:port/path?query#fragment
这里的protocol就是URI的模式(scheme);userInfo可选;端口也是可选的;用户信息、主机和端口构成一个authority;fragment是片段,指向远程资源的某个特定的部分,比如某个HTML的标签的ID(这样的话,严格上说是URL引用,但是JAVA不做区分)。
URL可以继承其父文档的协议、主机名和路径,因此可以使用相对URL,例如:
这个超链接在http://www.gg.com/gg/a.html中,那么这连接的地址是:http://www.gg.com/gg/javafaq.html
如果相对连接是以“/”开头,那么它相对于文档根目录,例如:
java的URL是一个final类,因此是线程安全的,并且使用了策略设计模式
创建URL的方法:
public URL(String url) throws MalformedURLException;
public URL(String protocol, String hostname, String file) throws MalformedURLException;
public URL(String protocol, String host, int prot, String file) throws MalformedURLEXception;
public URL(URL base, String relative) throws MalformedURLException
能否支持某个协议取决于虚拟机,如果不支持可以安装一个协议处理器,当然更好的方法就是使用一个库
除了验证能否识别URL模式外,java不会它构造的URL完成任何正确性检查。程序员要负责确保所创建的URL是合法的。
从组成部分创建URL
public URL(String protocol, String hostname, String file) throws MalformedURLException;
此方法等同于调用带四个参数的构造方法,四个参数为 protocol、host、-1(即采用默认端口) 和 file。 此构造方法不执行对输入的验证。注意file中不要忘了写“/”
其他方式获取URL
java.io.File类有一个toURL()方法,可以产生一个和平台有关的URL
ClassLoader.getSystemResource(String name)可以返回一个URL
ClassLoader.getSystemRecources(String name)返回包含URL的Enumeration
classloader的实例方法getResource(String name)会在所引用类加载器使用路径中搜索指定资源的URL
public InputStream openStream() throws IOException;
使用案例:
try{
URL u = new URL("http://www.lolcats.com");
try (InputStream in = u.openStream()){
int c;
while((c=in.read())!=-1) System.out.write(c);
}
} catch(IOException ex){
System.out.println(ex);
}
显示一个html页面(这里默认为ASCII码字符,实际上html是存在多种编码的)
public class SourceViewer {
public static void main(String[] args) {
if (args.length > 0) {
InputStream in = null;
try {
URL u = new URL(args[0]);
in = u.openStream();
in = new BufferedInputStream(in);
Reader r = new InputStreamReader(in);
int c ;
while ((c = r.read()) != -1) {
System.out.println((char) c);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
public URLConnection openConnection() throws IOException
该方法为指定的URL打开一个socket,并返回一个URLConneciotn对象。该对象表示一个网络资源的打开的链接,如果失败将会抛出异常。如果希望与服务器直接通信,应当使用这个方法,除了访问原始的文档本身外,它还能访问这个协议指定的元数据,例如HTML的首部。
该方法用一个重载的版本:
可以指定哪个代理服务器传递连接
public URLConnection openConnection(Proxy proxy) throws IOException
public final Object getContent() throws IOException
通过URL数据建立对象,如果URL指示的是某中文本,返回的通常是某种InputStream。如果指示一个图像,返回一个java.awt.ImageProducer对象(getContent会从服务器获取的首部查找Content-type字段,如果服务器没有使用MIME首部,或者是不熟悉的Content-type就返回InputStream)
public final Object getContent(Class[] classes) throws IOException
上面方法的重载版本,允许选择返回的类(多个类,返回第一个匹配项),常配合instanceof来使用
URL由以下5部分组成:模式;授权机构;路径;片段标识符;查询字符串
授权机构可以进一步分为:用户信息,主机和端口 例如:http://[email protected]:8080/
URL类提供了9个方法访问这些数据:
public String getProtocol();
public String getHost();
public int getPort();//没有指定端口,就返回-1
public int getDefaultPort();
public String getFile();//主机名后的都是file
public String getPath();//不包括查询字符串
public String getRef();//返回片段标识符
public String getQuery();
public String getUserInfo();//返回@符号之前的用户信息
public String getAuthority();
仅当两个URL都指向相同的主机、端口和路径上的相同资源,而且有相同的片段标识符和查询字符串,才认为两个URL是相等的。不过,equals会尝试用DNS解析主机,来判断连个主机是否相同。这说明URL是一个阻塞的IO操作,因此要避免在HashMap等依赖equals的数据结构中使用(一般情况下应该使用URI来存)。
public boolean sameFile(URL other)
也是比较URL,也会进行DNS查询,但不会比较片段标识符
URL有三个方法可以将一个实例转换为另一种形式,分别是:
toString()
toExternalForm() 可以在浏览器中打开
toURI()
URI是对URL的抽象,不仅包括URL,还包括URN(统一资源名),实际上大多URI就是URL。两者的区别有
URI类只和资源的标识和URI的解析有关,没有提供获取RUI所标识资源的方法
相比URI类,URI与相关的规范更一致
URI对象可以表示相对URI,URL类在存储URI之前会将其绝对化
与URL不同,URI类不依赖于底层协议处理器。只要URI语法正确,java就不需要为了创建URI对象而理解其协议。
public URI(String uri) throws URISyntaxException;
public URI(String scheme, String schemeSpecificPart, String fragment) throw...;
public URI(String shceme, String host, String path, String fragment) throws ...;
public URI(String scheme, String authority, String path, String query, String fragment) throws ...;
public URI(String scheme, String userInfo, String host, int port, String path,String query, String fragment) throws URISyntaxException;
上面方法中任何参数都可以省略,从而忽略该参数
URI的引用主要包括三个部分:模式、模式特定部分、片段标识符
如果省略模式,表示这是一个相对URI,如果省略片段标识符,这个URI就是一个纯URI。
主要方法如下:
public Sting getScheme();
public String getSchemeSpecificPart();
public String getRawSchemeSpecificPart();
public String getFragment();
public String getRawFragment();
带raw的是编码形式,不带raw的是对所有用百分号转义的字符进行解码,然后返回。scheme只有ASCII码形式。
有模式的URI是绝对URI,没有模式的是相对URI
public boolean isAbsolute();//判断是不是绝对URI
public boolean isOpaque();//是否 不是有层次的URI
如果是有层次的,有相关的获取相应部分的方法:
public String getAuthority();
public String getFragment();
public String getHost();
public String getPath();
public String getPort();
public String getQuery();
public String getUserInfo();
这些都是解码后,也就是百分号转义会改为它们实际表示的字符,如果希望得到原始的部分,在get后面加上Raw,注意,host和Port(返回-1表示省略端口)没有raw方法,因为都是由ASCII码组成的
java并不是在开始就检测授权机构部分中的语法错误,所以无法返回授权机构的各个部分:端口、主机、用户信息。可以调用parseServerAuthority()强制重新解析授权机构。(注意URI是final类)
public URI resolve(URI uri);
public URI resolve(String uri);
public URI relative(String uri);
相对URI构造绝对URI:
URI absolute = new URI("http://www.example.com");
URI relative = new URI("images/logo.png");
URI resolved = absolute.resolve(relative);
当然也可以通过相对URI构造相对URI
绝对URI构造相对URI:
URI absolute = new URI("http;//www.example.com/images/logo.png");
URI top = new URI("http://www.example.com/");
URI relative = top.relativize(absolute);
在进行equals比较时,相等的URI必须都是层次或非层次的,模式和授权机构不会区分大小写,转义字符在比较前不解码
URI实现了Comparable,一次URI可以排序。
toString() 未编码的字符串形式,更方便阅读,但显示结果不能保证是一个语法正确的URI
toASCIIString() 编码后的字符串形式,推荐使用
对字符串完成URL编码
例如:
String encoded = URLEncoder.encode("this*string*has*asterisks","UTF-8");
注意这个方法对于/,&,=,: 都进行了编码,因此有时候需要分部分的编码(只编码需要编码的,而不是整个URL一起编码)
public static String decode(String s, String encoding) throws UnsupportedEncodingException;
该方法可以传入整个URL
Proxy类有三种代理:
Proxy.Type.DIRECT //实际就是没代理
Proxy.Type.HTTP
Proxy.Type.SOCKS
代理信息可以用SocketAddress对象表示:
SocktAddress address - new InetSocketAddress("proxy.example.com",80);
Proxy proxy = new Proxy(Proxy.Type.HTTP, address);
每个运行中的虚拟机都有一个Java.net.ProxySelector对象,可以使用自己的ProxySelector子类来替代默认的选择器
ProxySelector类可以根据URI返回合适的代理:
public abstarct list select(URI uri);//返回代理
public void connectFailed(URI uri, SocketAddress sa, IOException ioe);//同时必须实现这个连接失败的方法
案例:
public class LocalProxySelector extends ProxySelector {
private List failed = new ArrayList<>();
@Override
public List select(URI uri) {
List result = new ArrayList<>();
if (failed.contains(uri) || !"http".equalsIgnoreCase(uri.getScheme())) {
result.add(Proxy.NO_PROXY);
} else {
SocketAddress proxyAddress = new InetSocketAddress("proxy.example.com", 80);
Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyAddress);
result.add(proxy);
}
return result;
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
failed.add(uri);
}
}
改变虚拟机默认的选择器:
ProxySelector selector = new LocalProxySelector();
ProxySelector.setDefault(seletor);
因为虚拟机只有一个ProxySelector,因此不推荐在共享的环境下使用这个方法