1. 概述
本文将深入探讨 URI 和 URL 的核心差异,并通过 Java 代码示例直观展示这些区别。对于有经验的开发者来说,这些概念虽然基础,但在实际开发中踩坑的情况并不少见。
2. URI 和 URL
先明确定义:
- **URI (Uniform Resource Identifier)**:用于唯一标识任何抽象或物理资源的字符序列
- **URL (Uniform Resource Locator)**:URI 的子集,除了标识资源位置外,还描述了访问该资源的主要机制
✅ 关键结论:所有 URL 都是 URI,但并非所有 URI 都是 URL
2.1. 语法结构
所有 URI(无论是否为 URL)都遵循统一格式:
scheme:[//authority][/path][?query][#fragment]
各组成部分解析:
| 部分 | 说明 |
|------|------|
| scheme
| URL 中指协议(如 http),其他 URI 中是标识符规范 |
| authority
| 可选部分,包含认证信息、主机和端口 |
| path
| 在 scheme 和 authority 范围内标识资源 |
| query
| 附加数据,与 path 共同标识资源(URL 中即查询字符串) |
| fragment
| 资源特定部分的可选标识符 |
⚠️ 快速判断技巧:检查 scheme 开头
- 有效的 URL scheme:
ftp
,http
,https
,gopher
,mailto
,news
,nntp
,telnet
,wais
,file
,prospero
- 不符合上述规则的就不是 URL
示例对比(前三个是 URL,后三个不是):
ftp://ftp.is.co.za/rfc/rfc1808.txt
https://tools.ietf.org/html/rfc3986
mailto:john.doe@example.com
tel:+1-816-555-1212
urn:oasis:names:docbook:dtd:xml:4.1
urn:isbn:1234567890
3. Java 中 URI 和 URL 的 API 差异
3.1. 实例化方式
创建 URI 和 URL 实例的代码对比:
@Test
public void whenCreatingURIs_thenSameInfo() throws Exception {
URI firstURI = new URI(
"somescheme://theuser:thepassword@someauthority:80"
+ "/some/path?thequery#somefragment");
URI secondURI = new URI(
"somescheme", "theuser:thepassword", "someuthority", 80,
"/some/path", "thequery", "somefragment");
assertEquals(firstURI.getScheme(), secondURI.getScheme());
assertEquals(firstURI.getPath(), secondURI.getPath());
}
@Test
public void whenCreatingURLs_thenSameInfo() throws Exception {
URL firstURL = new URL(
"http://theuser:thepassword@somehost:80"
+ "/path/to/file?thequery#somefragment");
URL secondURL = new URL("http", "somehost", 80, "/path/to/file");
assertEquals(firstURL.getHost(), secondURL.getHost());
assertEquals(firstURL.getPath(), secondURL.getPath());
}
❌ 关键差异:
- URI 类提供便捷创建方法:
@Test public void whenCreatingURI_thenCorrect() { URI uri = URI.create("urn:isbn:1234567890"); assertNotNull(uri); }
- URL 类没有类似方法
⚠️ 踩坑警告:尝试用无效 scheme 创建 URL 会抛异常:
@Test(expected = MalformedURLException.class)
public void whenCreatingURLs_thenException() throws Exception {
URL theURL = new URL("otherprotocol://somehost/path/to/file");
assertNotNull(theURL);
}
3.2. 实例转换
转换操作代码示例:
@Test
public void givenObjects_whenConverting_thenCorrect()
throws MalformedURLException, URISyntaxException {
String aURIString = "http://somehost:80/path?thequery";
URI uri = new URI(aURIString);
URL url = new URL(aURIString);
URL toURL = uri.toURL();
URI toURI = url.toURI();
assertNotNull(url);
assertNotNull(uri);
assertEquals(toURL.toString(), toURI.toString());
}
❌ 转换限制:非 URL 的 URI 无法转换为 URL:
@Test(expected = MalformedURLException.class)
public void givenURI_whenConvertingToURL_thenException()
throws MalformedURLException, URISyntaxException {
URI uri = new URI("somescheme://someauthority/path?thequery");
URL url = uri.toURL();
assertNotNull(url);
}
3.3. 远程连接操作
URL 类的核心优势在于可直接访问远程资源:
@Test
public void givenURL_whenGettingContents_thenCorrect()
throws MalformedURLException, IOException {
URL url = new URL("http://courses.baeldung.com");
String contents = IOUtils.toString(url.openStream());
assertTrue(contents.contains("<!DOCTYPE html>"));
}
⚠️ 性能陷阱:
- URL 的
equals()
和hashCode()
可能触发 DNS 解析 - 会导致网络延迟和结果不一致
- ❌ 不建议在虚拟主机环境使用
- ✅ 推荐改用 URI 进行标识操作
4. 总结
本文通过代码示例直观展示了 URI 和 URL 的核心差异:
- 本质区别:URL 是 URI 的子集,包含访问机制
- API 差异:
- URI 提供更灵活的创建方式
- URL 支持直接网络连接
- 最佳实践:
- 使用 URI 进行资源标识
- 仅在需要网络访问时使用 URL
- 避免依赖 URL 的
equals()
/hashCode()
完整源码可在 GitHub 获取。