持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情
概述
URI和Uri的比较:
- 所属的包不同:
URI位于java.net包下,是由java提供的类;而Uri位于android.net包下,是由Android提供的类。这两个类之间并不存在继承等关系,从类之间的关系上来说,这两个类之间没有任何关系,从功能的角度来说,Uri是对URI的扩展,用于满足Android特定的需要。 - 这两个类的作用也不一样,
URI类代表了一个URI(这里的URI是指其本来的意思:通用资源标识符 -- Uniform Resource Identifier)实例。Uri则表示了一个不可变的URI引用。
URI
URI全称是Uniform Resource Identifier,也就是统一资源标识符,它是采用一种特定的语法标识一个资源的字符串。URI所标识的资源可能是服务器上的一个档案,也可能是一个邮件地址,图书,主机名等。URI的语法构成是:一个模式和一个模式特定部分,如下所示:
模式:模式特定部分
scheme:scheme specific part
需要注意的是:模式特定部分的表示形式取决于当前所属的模式,URI当前常用的模式包括:
- data: 连接中直接包含经过BASE64编码的资料
- file: 本地磁盘上的文件
- ftp: FTP服务器
- http: 超文本传输协议
- mailto: 电子邮件地址
- magnet: 可以通过对等网络(端对端P2P,如BitTorrent)下载的资源
- telnet: 基于Telnet的服务连线
- urn: 统一资源名(Uniform Resource Name)
上面是一些标准的模式,Java中还大量使用了一些非标准模式,如jdbc,doc,jar等,这些非标准模式也可以实现不同的功能。
而针对模式特定部分则没有固定的语法,不过很多时候都采用一种层次结构形式,如:
//授权机构/路径?查询数据
//authority/path?query
上面的authority部分指定了负责解析该URI其它部分的授权机构,很多时候,URI都是使用Internet主机作为授权机构,例如http://www.baidu.com/s?ie=utf-8,授权机构就是www.baidu.com(在URL的角度来看,主机名就是www.baidu.com)
而上面的路径path部分则是授权机构用来确定所标识资源的字符串。不同的授权机构可能会把相同的路径解析后指向不同的资源。另外,路径是可以分层的,分层的各部分使用斜线/进行分割,而.和..操作符用于在分层层次结构中的导航(这两个操作符使用的比较少).
URI的语法
模式部分
URI的模式的组成部分可以是小写字母,数字,加号,点号.和连字符-
模式特定部分
典型的URI的其它三部分(授权机构,路径,查询数据)分别有ASCII字母组成(也就是字母A-Z,a-z,数字0-9),此外,还可以使用标点符号-,_,.,!和~。而定界符/,?,&,=有其它预定义的用途。除了上面指定的这些字符,所有的其它字符,包括ASCII中的拉丁字母,都需要使用%进行转义,转以后的字符格式为:(%字符按照utf-8编码再转成16进制的字符串,比如字符木,)
URI类
URI在Java中的抽象为java.net.URI类。
构造URI实例
URI类中提供了多个构造函数用于构造URI实例,如下所示:
//空参数的构造函数为私有的,我们无法使用这个构造函数
private URI() { }
//使用这个构造函数,我们需要直接传递一个字符串,然后通过解析这个字符串来构造一个URI
public URI(String str) throws URISyntaxException {
new Parser(str).parse(false);
}
//使用给定的参数来构造一个分层的URI,需要注意的是:
//如果指定了scheme,那么指定path的时候必须以 / 开始
public URI(String scheme,
String userInfo, String host, int port,
String path, String query, String fragment)
throws URISyntaxException
{
String s = toString(scheme, null,
null, userInfo, host, port,
path, query, fragment);
checkPath(s, scheme, path);
new Parser(s).parse(true);
}
//和上一个构造函数一样,根据给定的参数构造URI,仍然要注意如果给定了scheme,那么在
//指定path时需要以 / 开始,否则会抛出 URISyntaxException 异常
public URI(String scheme,
String authority,
String path, String query, String fragment)
throws URISyntaxException
{
String s = toString(scheme, null,
authority, null, null, -1,
path, query, fragment);
checkPath(s, scheme, path);
new Parser(s).parse(false);
}
//根据指定参数构造一个URI,注意在指定scheme的情况下 path需要以 / 开始
public URI(String scheme, String host, String path, String fragment)
throws URISyntaxException
{
this(scheme, null, host, -1, path, null, fragment);
}
//根据指定参数构造URI,这里不要求path必须以 / 开头
public URI(String scheme, String ssp, String fragment)
throws URISyntaxException
{
new Parser(toString(scheme, ssp,
null, null, null, -1,
null, null, fragment))
.parse(false);
}
根据上面的构造函数,我们对同一个地址使用不同的构造函数来构造URI.需要构造的地址为本机地址"D:\Project\PersonalStudyProject\Kotlin\Demo"
- 使用第一种方式构造
URI
val fileURI = URI("D:/Project/PersonalStudyProject/Kotlin/Demo");
val fileURI = URI("file://D:/Project/PersonalStudyProject/Kotlin/Demo");
使用上面两种方式均可以构造出相应的URI,但是需要注意的是,path中的分隔符必须是/,D:\\Project\\PersonalStudyProject\\Kotlin\\Demo这样的路径将无法构造出URI并抛出异常。
- 使用第二种方式构造
URI
val fileURI = URI("file",null,null,0,"/D:\\Project\\PersonalStudyProject\\Kotlin\\Demo",null,null);
这里我们指定了scheme为file,因此在指定path的时候需要以/开头,另外就是这里路径中的分隔符使用\\也是可以的,最终构造出的URI如下:
file:/D:%5CProject%5CPersonalStudyProject%5CKotlin%5CDemo
使用这个URI复制到资源管理器中就可以直接跳转到对应的文件夹下。
这里很多参数都传递了空值,我们可以尝试构造一个网络地址:
val fileURI = URI("https",null,"xueshu.baidu.com",443,"/usercenter/paper/show","paperid=170f06907p7h0ja0bg330af0wt377801",null)
使用这个函数构造出来的URI可以直接访问对应的网页。
- 使用第三种方式构造
URI
val fileURI = URI("file",null,"/D:\\Project\\PersonalStudyProject\\Kotlin\\Demo",null,null)
- 使用第四种方式构造
URI
val fileURI = URI("file",null,"D:\\Project\\PersonalStudyProject\\Kotlin\\Demo",null)
- 使用第五种方式构造
URI
val fileURI = URI("file","D:\\Project\\PersonalStudyProject\\Kotlin\\Demo",null)
- 使用静态工厂方法的方式构造
URI
val fileURI = URI.create("file://D:/Project/PersonalStudyProject/Kotlin/Demo")
静态工厂方法和构造函数创建URI的不同之处在于异常的类型不同。构造函数可能会抛出URISyntaxException,这个异常继承自Exception,我们可以在代码中直接捕获这个异常,从而对可能出现的问题进行处理。而create()方法中直接捕获了这个异常,并在出现异常的时候抛出了IllegalArgumentException,这个异常继承自RuntimeException,我们可以不用显式地去捕获这个异常。
上面几种方式最终都能构造出我们想要的URI,其实我们仅仅指定了scheme和path参数,还有很多参数没有指定,对于一些复杂的URI,可能会需要用到那些多个参数的构造函数。
除了上面的构造函数,URI类中还向我们提供了静态工厂方法来构造一个URI:
public static URI create(String str) {
try {
return new URI(str);
} catch (URISyntaxException x) {
throw new IllegalArgumentException(x.getMessage(), x);
}
}
可以看到,这里主要是转换了异常的类型,构造函数抛出的URISyntaxException直接继承自Exception,所以我们在使用URI的构造函数构造URI的时候需要捕获URISyntaxException异常,而静态方法抛出的异常是IllegalArgumentException,继承自RuntimeException,所以我们并不需要捕获这个异常,有问题的时候虚拟机会自动抛出这个异常。
获取URI属性
通过上面的学习,我们已经了解了URI的基本组成部分,一个URI由以下三部分组成:
模式:模式特定部分:片段识别符号
scheme:schemeSpecificPart:fragment
针对这三部分,URI中也为我们提供了获取这三部分属性的方法:
首先创建一个URI:
val fileURI = URI(
"https",
"user",
"xueshu.baidu.com",
443,
"/usercenter/paper/show",
"paperid=170f06907p7h0ja0bg330af0wt377801",
"this is fragment")
上面的代码创建的URI为:"user@xueshu.baidu.com:443/usercenter/…"
-
getScheme()这个方法用于获取
URI中的模式部分,我们仍然使用上面创建的这个URI来获取其中的相关信息,获取这个URI的scheme的信息为:
val scheme = fileURI.scheme
-
getSchemeSpecificPart()和getRawSchemeSpecificPart()这两个方法用于获取模式特定部分,之所以有两个方法,是因为模式特定部分的文本可能会使用编码后的文本信息,
getSchemeSpecificPart()用于获取解码后的文本,这样的文本我们可以直接看得懂,而getRawSchemeSpecificPart()方法用于获取原始文本,这里的文本我们可能难以直接看得懂,使用这两个方法获取上面的URI的模式特定部分的代码如下:
val specific = fileURI.schemeSpecificPart
val rawSpecific = fileURI.rawSchemeSpecificPart
-
getFragment()和getRawFragment()这两个方法用于获取
URI中的片段标识符,和获取模式特定部分的方法一样,getFragment()用于获取我们能够看得懂的解码后的字符,getRawFragment()用于获取原始的字符信息。
val fragment = fileURI.fragment
val rawFragment = fileURI.rawFragment
打印上面的信息,可以看到最终获取到的数据:
LogUtils.e("\nscheme:$scheme,\n" +
"specific:$specific,\n" +
"rawSpecific:$rawSpecific,\n" +
"fragment:$fragment,\n" +
"rawFragment:$rawFragment")
输出的信息如下:
scheme:https,
specific://user@xueshu.baidu.com:443/usercenter/paper/show?paperid=170f06907p7h0ja0bg330af0wt377801,
rawSpecific://user@xueshu.baidu.com:443/usercenter/paper/show?paperid=170f06907p7h0ja0bg330af0wt377801,
fragment:this is fragment,
rawFragment:this%20is%20fragment
从打印的信息可以看出:rawXXX相关的方法输出的信息中包含对特殊字符进行编码后的数据,而没有rawXXX()的方法则输出了原始的文本。而scheme则没有rawXXX()相关的方法,这是因为URI规范中强制规定,所有的模式名称必须由URI规范中合法的ASCII字符组成,也就是模式名称中不允许出现百分号转义。
获取URI中的其它属性
上面我们已经学习了如何获取一个URI中的各个主要部分的数据,包括scheme模式名称,schemeSpecificPart模式特定部分,fragment片段标识符。而我们知道,根据scheme的不同,schemeSpecificPart也有所差异。通过之前的学习,我们也能够明白,一个很复杂的URI可能由以下格式组成:
scheme://userInfo@host:port/path?query#fragment
模式名称://用户信息@认证机构/路径?查询参数#片段标识符
对于其中的scheme和fargment,我们在上面已经学习了如何获取其中的信息,而ssp SchemeSpecificPart部分则包含较多的参数,有些有有些没有,java.net.URI中也为我们提供了相关的方法去获取其中的属性。仍然使用上面我们已经创建好的URI,下面的方法介绍了如何获取其中其它部分的属性。
-
isAbsolute()获取当前
URI是否是绝对URI.//是否是绝对URI LogUtils.e("$fileURI is absolute uri:${fileURI.isAbsolute}") //输出日志:is absolute uri:true -
isOpaque()这个方法用于获取当前的
URI是否是不透明的URI,如果URI是不透明的,只能获取到模式,模式特定部分,片段标识符,获取不到host,port等。//是否是不透明URI LogUtils.e("$fileURI is opaque:${fileURI.isOpaque}") //输出日志:is opaque:false -
getAuthority()和getRawAuthority()这两个方法用于获取
URI中的授权机构,和上面的一样,getAuthority()返回解码后的信息,getRawAuthority()返回原始的数据。//authority val authority = fileURI.authority; val rawAuthority = fileURI.rawAuthority; LogUtils.e(" authority is $authority \t rawAuthority is $rawAuthority") //输出日志:authority is user@xueshu.baidu.com:443 rawAuthority is user@xueshu.baidu.com:443 -
getUserInfo()和getRawUserInfo()这两个方法用于获取
URI中的用户信息。//userInfo val userInfo = fileURI.userInfo val rawUserInfo = fileURI.rawUserInfo LogUtils.e("userInfo is: $userInfo \t rawUserInfo is: $rawUserInfo") //输出日志:userInfo is: user rawUserInfo is: user -
getHost()这个方法用于获取
URI的授权机构中的host。//host val host = fileURI.host LogUtils.e("host is $host") //输出日志:host is xueshu.baidu.com -
getPort()这个方法用于获取
URI的授权机构中的port.//port val port = fileURI.port LogUtils.e("port is: $port") //输出日志:port is: 443 -
getPath()和getRawPath()这两个方法用于获取
URI中的path部分.//path val path1 = fileURI.path; val rawPath = fileURI.rawPath; LogUtils.e("path is: $path1, rawPath is: $rawPath") //输出日志: path is: /usercenter/paper/show, rawPath is: /usercenter/paper/show -
getQuery()和getRawQuery()这两个方法用于获取
URI中的Query查询参数部分。//query val query = fileURI.query val rawQuery = fileURI.rawQuery LogUtils.e("query is: $query, rawQuery is: $rawQuery") //输出日志: query is: paperid=170f06907p7h0ja0bg330af0wt377801, rawQuery is: paperid=170f06907p7h0ja0bg330af0wt377801
绝对URI和相对URI
URI中提供了三个方法用于绝对URI和相对URI的互相转换.
public URI resolve(URI uri)
public URI resolve(String str)
public URI reletivize(URI uri)
-
resolve(URI uri)和resolve(String uri)这两个方法是基于绝对
URI将相对URI补全为绝对URI,如下所示://首先定义一个绝对URI val absoluteURI = URI("https","xueshu.baidu.com","/usercenter/paper/show","paperid=170f06907p7h0ja0bg330af0wt377801",null) //定义一个相对URI val relativeURI = URI("/relative"); //转换后的URI val resultURI = absoluteURI.resolve(relativeURI) LogUtils.e("relative uri to absolute uri is:$resultURI") //输出日志信息如下:relative uri to absolute uri is:https://xueshu.baidu.com/relative -
relativize(URI uri)这个方法是基于绝对
URI将另一个绝对URI反解出绝对URI中的相对URI部分。//定义另一个绝对URI val absoluteURI1 = URI("https","xueshu.baidu.com","/usercenter/paper/show",null) LogUtils.e("absoluteURI1 is absolute URI:${absoluteURI1.isAbsolute}") val result1 = absoluteURI1.relativize(absoluteURI) LogUtils.e("absolute uri to relative url is:$result1") //输出日志信息为:absolute uri to relative url is:?paperid=170f06907p7h0ja0bg330af0wt377801
URI的比较
URI类实现了Comparable接口,因此URI可以排序。URI的相等性不是基于字符串的直接比较,相等的URI在透明性上必须是一致的,例如都是不透明的,在此前提下,其它部分才可以进行比较。URI比较的时候,模式和授权机构是忽略大小写的,其它部分必须区分大小写,用于转义无效字符的十六进制数字除外。需要转义的字符在转义前和转义之后进行比较,会认为是不同的URI.
//创建第一个URI
val firstURI = URI.create("http://userinfo@localhost:8080/index?page=10#fragment=fragment");
//创建第二个URI
val secondURI = URI.create("HTTP://userinfo@LOCALhost:8080/index?page=10#fragment=fragment")
//比较两个URI
LogUtils.e("firstURI == secondURI:${firstURI.equals(secondURI)}")
//输出日志信息为:firstURI == secondURI:true
从上面的输出信息可以看出,即使URI中的scheme和authority部分大小写不一致,最后比较的结果也是两个URI是相同的。
//创建第三个URI
val threeURI = URI("http","userinfo","localhost",80,"/index/A","page=10","fragment")
//创建第四个URI
val fourURI = URI.create("/index/%41?page=10#fragment")
//使用绝对URI补全相对URI
val fiveURI = threeURI.resolve(fourURI)
//比较两个URI是否相等
LogUtils.e("threeURI == fiveURI:${threeURI.equals(fiveURI)}")
//输出信息如下:threeURI == fiveURI:false
从上面的输出日志可以看出,虽然A转义之后本身就是%41,但是我们在路径上直接写A和写%41还是完全不同的。
URI的字符串表示
下面两个方法均可以输出当前URI的字符串:
public String toString()
public String toASCIIString()
其中toString()方法返回未编码的字符串形式,也就是特殊字符不使用百分号转义,因此这个方法的返回值不能保证符合URI的语法,尽管它的各个部分都是遵循URI规范的。toASCIIString()返回的是经过编码的字符串形式(编码格式为US-ASCII码),也就是特殊字符一定经过了百分号转义。
toString()存在的意义是提高URI的可读性,toASCIIString()存在的意义是提高URI的可用性。
//创建第六个URI
val sixURI = URI("http", "userinfo", "localhost", 80, "/index/路径结束", "page=10", "fragment")
//创建第七个URI
val sevenURI = URI.create("/index/路径结束?page=10#fragment")
//使用绝对URI补全相对URI
val eightURI = sixURI.resolve(sevenURI)
LogUtils.e(
"\n sixURI ASCII String is:${sixURI.toASCIIString()} \n "
+ " sixURI toString is:${sixURI.toString()} \n"
+ " eightURI ASCII String is:${eightURI.toASCIIString()} \n"
+ " eightURI toString is:${eightURI.toString()}"
)
//最终输出的结果如下:
sixURI ASCII String is:http://userinfo@localhost:80/index/%E8%B7%AF%E5%BE%84%E7%BB%93%E6%9D%9F?page=10#fragment
sixURI toString is:http://userinfo@localhost:80/index/路径结束?page=10#fragment
eightURI ASCII String is:http://userinfo@localhost:80/index/%E8%B7%AF%E5%BE%84%E7%BB%93%E6%9D%9F?page=10#fragment
eightURI toString is:http://userinfo@localhost:80/index/路径结束?page=10#fragment