Guava 是什么
Guava 是 Google 开源的 Java 核心工具库,提供了一系列 JDK 没有但开发中常用的功能:
- 集合增强 —
ImmutableList、Multiset、BiMap、Table等 - 缓存 —
CacheBuilder本地缓存(Caffeine 是它的现代替代) - 字符串处理 —
Splitter、Joiner、CharMatcher - 前置条件 —
Preconditions.checkNotNull()、checkArgument() - I/O —
Files、ByteStreams、CharStreams简化流操作 - 并发 —
ListenableFuture(在CompletableFuture出现之前是 Java 并发的主流方案) - 事件总线 —
EventBus进程内发布/订阅
其中 InternetDomainName 是一个容易被忽视但设计精巧的类,专门用于域名的结构化解析。
InternetDomainName 的核心原理
InternetDomainName 的核心原理是基于 Mozilla Public Suffix List (PSL) 来解析域名的层级结构。
为什么需要 PSL
域名有层级,但光看 . 的数量无法判断”注册级别”在哪里:
example.com→ 注册域是example.com(1 级 + 公共后缀com)example.co.uk→ 注册域是example.co.uk(1 级 + 公共后缀co.uk,不是uk)example.github.io→ 公共后缀是github.io,注册域是example.github.io
所以 Guava 把 Mozilla 维护的 PSL(~9000 条规则)编译进了 JAR,用查表法判断。
PSL 规则匹配算法
从右往左匹配,规则有三种类型:
| 规则类型 | 示例 | 含义 |
|---|---|---|
| 普通规则 | com | com 是公共后缀 |
| 通配规则 | *.ck | xxx.ck 都是公共后缀 |
| 例外规则 | !www.ck | www.ck 不是公共后缀(覆盖通配) |
匹配时最长规则优先:先匹配例外,再匹配通配,最后匹配普通。
关键概念
blog.example.co.uk ─────┬─────┬──┬── │ │ └─ public suffix: co.uk │ └─ top private domain: example.co.uk ← 你能注册的 └─ subdomain- publicSuffix() — 查 PSL 得到公共后缀(
co.uk) - topPrivateDomain() — 公共后缀 + 往左一级 = 注册域(
example.co.uk) - isUnderPublicSuffix() — 是否在某个公共后缀之下
- hasPublicSuffix() — 该域名本身是否包含已知的公共后缀
源码级流程
InternetDomainName.from("blog.example.co.uk") ① 校验格式(小写化、ASCII、标签长度 1-63、总长 ≤253) ② 按 "." 拆成 labels: ["blog", "example", "co", "uk"] ③ 从右往左拼接,逐级查 PSL Trie 树 ④ 找到匹配的 public suffix → "co.uk" ⑤ topPrivateDomain = public suffix 左边第一个 label + suffix → "example.co.uk"PSL 在 Guava 里被预处理成一个紧凑的 Trie(前缀树) 结构(TrieParser 生成的字节码),查找是 O(n)(n = 标签数),非常快。
为什么 InternetDomainName 不接受 URL
一个自然的疑问:既然都是从右往左匹配,为什么不直接传 URL 进去?
因为”从右往左匹配 PSL”和”从 URL 里提取域名”是两个完全不同的问题。
从 URL 提取域名并不简单
http://user:pass@example.co.uk:8080/path?q=1 │ │ │ auth host portURL 中包含认证信息、端口号、IPv6 地址、路径、查询参数等,如果 InternetDomainName.from() 内部默默做这些提取,调用者反而不知道发生了什么。
三个设计理由
1. 单一职责 — PSL 匹配是域名层的事,URL 解析是网络层的事。混在一起违反正交性。
2. 避免隐式行为 — 如果传入 http://evil.com@safe.com,你是取 evil.com 还是 safe.com?静默提取会藏 bug。直接报错,用户就知道用错了。
3. 调用者已有工具:
// Java 标准库已经能提取 hostString host = new URL(input).getHost();InternetDomainName.from(host); // 清晰、两步各管各的“反正也是从右往左匹配”——那只是 PSL Trie 查找的方向,跟输入格式无关。拿到干净的域名标签序列之前,URL 的解析工作跟 PSL 没有关系。
一句话总结
InternetDomainName 的本质:用 Mozilla 的公共后缀列表建 Trie,从右往左匹配,确定域名的”注册边界”在哪里。不接受 URL 不是技术限制,是 API 设计原则——不替调用者做隐式转换,强制显式分离关注点。