遍历目录树看起来是个一行的事,但回调的先后、跳过语义、符号链接、错误处理和性能差别都藏在细节里。这篇用一棵真实的小树和一棵 5000 项的大树逐项验证。
[order]
pre .
file z.txt
pre a
file a/a1.txt
file a/a2.txt
pre a/deep
file a/deep/d1.txt
post a/deep
post a
pre c
post c
pre b
file b/b1.txt
post b
post .
DirectoryStream 对 root 的顺序:[z.txt, a, c, b];排序后:[a, b, c, z.txt];与排序一致? false
File.list 的顺序:[z.txt, a, c, b] [results]
[SKIP_SUBTREE at a] 共 9 条:pre:. , file:z.txt , pre:a , pre:c , post:c , pre:b , file:b/b1.txt , post:b , post:.
[SKIP_SIBLINGS at a/a1.txt] 共 11 条:pre:. , file:z.txt , pre:a , file:a/a1.txt , post:a , pre:c , post:c , pre:b , file:b/b1.txt , post:b , post:.
[SKIP_SIBLINGS at a (preVisit)] 共 4 条:pre:. , file:z.txt , pre:a , post:.
[TERMINATE at a/a2.txt] 共 5 条:pre:. , file:z.txt , pre:a , file:a/a1.txt , file:a/a2.txt
[SKIP_SUBTREE returned from visitFile] 共 15 条:pre:. , file:z.txt , pre:a , file:a/a1.txt , file:a/a2.txt , pre:a/deep , file:a/deep/d1.txt , post:a/deep , post:a , pre:c , post:c , pre:b , file:b/b1.txt , post:b , post:. [depth]
maxDepth=0 共 1 条;其中被当作 file 的目录:[.]
maxDepth=1 共 6 条;其中被当作 file 的目录:[a, c, b]
pre .
file z.txt
file a (dir)
file c (dir)
file b (dir)
post .
maxDepth=2 共 13 条;其中被当作 file 的目录:[a/deep]
maxDepth=3 共 15 条;其中被当作 file 的目录:[]
maxDepth=MAX_VALUE 共 15 条;其中被当作 file 的目录:[]
maxDepth=-1 → java.lang.IllegalArgumentException: 'maxDepth' is negative [delete]
递归删除的顺序(文件先于所在目录):
del file z.txt
del file a/a1.txt
del file a/a2.txt
del file a/deep/d1.txt
del dir a/deep
del dir a
del dir c
del file b/b1.txt
del dir b
del dir .
root 是否还存在:false
Files.delete(非空目录) → DirectoryNotEmptyException: .
Files.walk + 逆序删除:删了 10 个,root 存在? false [links]
(1) 不跟随符号链接(默认):
pre .
file dangling (symlink)
file z.txt
pre a
file a/a1.txt
file a/a2.txt
pre a/deep
file a/deep/d1.txt
post a/deep
post a
file link-a (symlink)
pre c
post c
pre b
file b/up (symlink)
file b/b1.txt
post b
post .
(2) FOLLOW_LINKS:
pre .
file dangling (symlink)
file z.txt
pre a
file a/a1.txt
file a/a2.txt
pre a/deep
file a/deep/d1.txt
post a/deep
post a
pre link-a
file link-a/a1.txt
file link-a/a2.txt
pre link-a/deep
file link-a/deep/d1.txt
post link-a/deep
post link-a
pre c
post c
pre b
FAIL b/up → FileSystemLoopException
file b/b1.txt
post b
post .
FOLLOW_LINKS 下共 24 条,不跟随时 18 条 [errors]
a/deep 的权限设为 --------- 后:
FAIL a/deep → AccessDeniedException
默认的 SimpleFileVisitor.visitFileFailed 会重新抛出:AccessDeniedException: <root>/a/deep
Files.walk(…).count() 抛 UncheckedIOException:java.nio.file.AccessDeniedException: <root>/a/deep
Files.walk 在抛异常之前已经遍历的:[., z.txt, a, a/a1.txt, a/a2.txt, → UncheckedIOException]
Files.find 找 *.txt:[a/a1.txt, a/a2.txt, a/deep/d1.txt, b/b1.txt, z.txt]
Files.walk 权限恢复后:10 个 [perf]
构造的树:200 个一级目录,各 5 个子目录,各 4 个文件 → 目录 1201 个,文件 4000 个
第 4 轮:walkFileTree(读 attrs.size)32.8 ms;Files.walk + isRegularFile + Files.size 51.4 ms;File.listFiles 递归 40.2 ms;Files.walk 只计数 32.3 ms;字节数 4000 / 4000 / 4000,条数 5201
第 5 轮:walkFileTree(读 attrs.size)34.2 ms;Files.walk + isRegularFile + Files.size 54.8 ms;File.listFiles 递归 42.0 ms;Files.walk 只计数 32.4 ms;字节数 4000 / 4000 / 4000,条数 5201
(自写计时,macOS APFS,文件已在页缓存里;不是冷缓存)