glob 看起来像 shell 通配符,但 PathMatcher 的规则有不少细节:匹配的是整个路径、* 不跨目录、** 与 ** / 的差别、大小写、取反写法。这篇用一张匹配矩阵与几个目录实例逐项验证。
[matrix]
P1 | P2 | P3 | P4 | P5 | P6 | P7 | P8 | P9 | P10 | P11 | P12
P1 = Main.java
P2 = src/Main.java
P3 = src/a/b/Main.java
P4 = /abs/src/Main.java
P5 = ./Main.java
P6 = src/../Main.java
P7 = .hidden.java
P8 = main.java
P9 = Nain.java
P10 = src
P11 = a.class
P12 = Main.java/
*.java ✓ | · | · | · | · | · | ✓ | ✓ | ✓ | · | · | ✓ |
**.java ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | ✓ |
**/*.java · | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | · | · | · | · |
{*.java,**/*.java} ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | · | · | ✓ |
src/*.java · | ✓ | · | · | · | · | · | · | · | · | · | · |
src/**.java · | ✓ | ✓ | · | · | ✓ | · | · | · | · | · | · |
src/**/*.java · | · | ✓ | · | · | ✓ | · | · | · | · | · | · |
src/**/Main.java · | · | ✓ | · | · | ✓ | · | · | · | · | · | · |
*.{java,class} ✓ | · | · | · | · | · | ✓ | ✓ | ✓ | · | ✓ | ✓ |
?ain.java ✓ | · | · | · | · | · | · | ✓ | ✓ | · | · | ✓ |
[A-M]ain.java ✓ | · | · | · | · | · | · | · | · | · | · | ✓ |
[!A-M]ain.java · | · | · | · | · | · | · | ✓ | ✓ | · | · | · |
[^A-M]ain.java ✓ | · | · | · | · | · | · | · | · | · | · | ✓ |
Main.java ✓ | · | · | · | · | · | · | · | · | · | · | ✓ |
src/** · | ✓ | ✓ | · | · | ✓ | · | · | · | · | · | · |
** ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
* ✓ | · | · | · | · | · | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
*/* · | ✓ | · | · | ✓ | · | · | · | · | · | · | · |
.* · | · | · | · | · | · | ✓ | · | · | · | · | · |
*.java* ✓ | · | · | · | · | · | ✓ | ✓ | ✓ | · | · | ✓ |
[a-z]* · | · | · | · | · | · | · | ✓ | · | ✓ | ✓ | · |
(列依次是 P1..P12;✓ 表示 matches(Path) 为 true;Paths.get("Main.java/") 的 toString 是 Main.java,所以 P12 与 P1 结果相同) [case]
FileSystem:MacOSXFileSystem;separator = /
glob:*.JAVA 匹配 a.java → false
glob:*.java 匹配 A.JAVA → false
glob:[A-Z].java 匹配 a.java → false
glob:{A,b}.java 匹配 a.java → false
glob:main.java 匹配 Main.java → false
文件系统本身是否区分大小写(Files.exists(hello.txt),实际文件是 Hello.txt):true;isSameFile 两者 = true
newDirectoryStream(dir, "hello.*") 返回 [](glob 区分大小写,即使文件系统不区分)
newDirectoryStream(dir, "Hello.*") 返回 [Hello.txt]
regex 想忽略大小写:regex:(?i).*\.JAVA → true [escape]
glob:\*.java 匹配 *.java → true
glob:\*.java 匹配 a.java → false
glob:a\?b 匹配 a?b → true
glob:a\?b 匹配 axb → false
glob:\{a\} 匹配 {a} → true
glob:[*] 匹配 * → true
glob:[?]x 匹配 ?x → true
glob:[{] 匹配 { → true
glob:a/b 匹配 a/b → true
glob:* 匹配 a/b → false
glob:? 匹配 / → false
glob:a?b 匹配 a/b → false
regex 匹配的是 Path.toString() 的整串:regex:.*\.java 对 src/Main.java → true;regex:src/.* 对 src/a/b.java → true;regex:\.java 对 Main.java(不是整串)→ false
Path.toString 的归一化:Paths.get("src//a///b.java") = src/a/b.java;Paths.get("src/a/") = src/a;g("src/*").matches(Paths.get("src//a")) = true [errors]
"glob:{a,b" → PatternSyntaxException: Missing '} near index 3 ⏎ {a,b ⏎
"glob:[abc" → PatternSyntaxException: Missing '] near index 3 ⏎ [abc ⏎
"glob:{a,{b,c}}" → PatternSyntaxException: Cannot nest groups near index 3 ⏎ {a,{b,c}} ⏎
"glob:[]" → PatternSyntaxException: Unclosed character class near index 11 ⏎ ^[[^/]&&[]]$ ⏎
"glob:[z-a]" → PatternSyntaxException: Invalid range near index 1 ⏎ [z-a]
"glob:a\" → PatternSyntaxException: No character to escape near index 1 ⏎ a\
"glob:{}" → 创建成功;对 "a" = false,对 "ab" = false
"glob:{,a}" → 创建成功;对 "a" = true,对 "ab" = false
"glob:***" → 创建成功;对 "a" = true,对 "ab" = true
"glob:" → 创建成功;对 "a" = false,对 "ab" = false
"regex:(" → PatternSyntaxException: Unclosed group near index 1 ⏎
"regex:" → 创建成功;对 "a" = false,对 "ab" = false
"foo:bar" → UnsupportedOperationException: Syntax 'foo' not recognized
"glob" → IllegalArgumentException: null
"GLOB:*.java" → 创建成功;对 "a" = false,对 "ab" = false
"glob:[!]a" → PatternSyntaxException: Unclosed character class near index 13 ⏎ ^[[^/]&&[^]]a$ ⏎
"glob:a[/]b" → PatternSyntaxException: Explicit 'name separator' in class near index 2 ⏎ a[/]b ⏎ [stream]
newDirectoryStream(root, "*.java") = [.d.java, a.java](只有一层;.d.java 也在内——隐藏文件不特殊)
newDirectoryStream(root, "**.java") = [.d.java, a.java](流里每个条目的文件名与 glob 比较,不会进入子目录)
newDirectoryStream(root, "{a,b}.*") = [a.java, b.class]
newDirectoryStream(root, "*/") 条目数 0
walk + glob:**/*.java 用「相对路径」匹配 → [sub/deep/g.java, sub/e.java]
walk + glob:{*.java,**/*.java} 用「相对路径」匹配 → [.d.java, a.java, sub/deep/g.java, sub/e.java]
walk + glob:**.java 用「相对路径」匹配 → [.d.java, a.java, sub/deep/g.java, sub/e.java]
walk + glob:*.java 用「相对路径」匹配 → [.d.java, a.java]
walk + glob:**.java 用「绝对路径」匹配:[.d.java, a.java, sub/deep/g.java, sub/e.java](** 可以吞掉绝对路径的前缀目录)
walk + glob:*.java 用绝对路径匹配:0 个
用 path.getFileName() 匹配 *.java:[.d.java, a.java, sub/deep/g.java, sub/e.java]