本文内容来自《On Java基础卷》相关章节学习总结
1 文件和目录路径
Path对象表示一个文件或目录的路径。
java.nio.file.Paths类包含了重载的static get()方法,可以接收一个String序列,或者一个统一资源标识符(URI),将其转化为一个Path对象。
public class PathInfo {
static void show(String id, Object p) {
System.out.println(id + p);
}
static void info(Path p) {
show("toString:\n ", p);
show("Exists: ", Files.exists(p));
show("RegularFile: ", Files.isRegularFile(p));
show("Directory: ", Files.isDirectory(p));
show("Absolute: ", p.isAbsolute());
show("FileName: ", p.getFileName());
show("Parent: ", p.getParent());
show("Root: ", p.getRoot());
System.out.println("******************");
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
info(Paths.get(
"C:", "path", "to", "nowhere", "NoFile.txt"));
Path p = Paths.get("files","PathInfo.java");
info(p);
Path ap = p.toAbsolutePath();
info(ap);
info(ap.getParent());
try {
info(p.toRealPath());
} catch(IOException e) {
System.out.println(e);
}
URI u = p.toUri();
System.out.println("URI:\n" + u);
Path puri = Paths.get(u);
System.out.println(Files.exists(puri));
File f = ap.toFile(); // Don't be fooled
}
}
1.1 选择Path的片段
public class PartsOfPaths {
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
Path p =
Paths.get("files","PartsOfPaths.java").toAbsolutePath();
for(int i = 0; i < p.getNameCount(); i++)
System.out.println(p.getName(i));
System.out.println("ends with '.java': " +
p.endsWith(".java"));
for(Path pp : p) {
System.out.print(pp + ": ");
System.out.print(p.startsWith(pp) + " : ");
System.out.println(p.endsWith(pp));
}
System.out.println("Starts with " + p.getRoot() +
" " + p.startsWith(p.getRoot()));
}
}
获取Path对象路径的各个部分时,可以使用getNameCount()方法拿到路径元素的数量,结合索引和getName()得到,也可以使用增强for循环来遍历(Path继承了Iterable接口)。
1.2 分析Path
Files工具类提供了一系列用于检查Path各种信息的方法。
public class PathAnalysis {
static void say(String id, Object result) {
System.out.print(id + ": ");
System.out.println(result);
}
public static void main(String[] args) throws IOException {
System.out.println(System.getProperty("os.name"));
Path p = Paths.get("files","PathAnalysis.java").toAbsolutePath();
say("Exists", Files.exists(p));
say("Directory", Files.isDirectory(p));
say("Executable", Files.isExecutable(p));
say("Readable", Files.isReadable(p));
say("RegularFile", Files.isRegularFile(p));
say("Writable", Files.isWritable(p));
say("notExists", Files.notExists(p));
say("Hidden", Files.isHidden(p));
say("size", Files.size(p));
say("FileStore", Files.getFileStore(p));
say("LastModified: ", Files.getLastModifiedTime(p));
say("Owner", Files.getOwner(p));
say("ContentType", Files.probeContentType(p));
say("SymbolicLink", Files.isSymbolicLink(p));
if (Files.isSymbolicLink(p))
say("SymbolicLink", Files.readSymbolicLink(p));
if (FileSystems.getDefault()
.supportedFileAttributeViews().contains("posix"))
say("PosixFilePermissions",
Files.getPosixFilePermissions(p));
}
}
1.3 添加或删除路径片段
public class AddAndSubtractPaths {
static Path base = Paths.get("..", "..", "..")
.toAbsolutePath()
.normalize();
static void show(int id, Path result) {
if (result.isAbsolute())
System.out.println("(" + id + ")r " +
base.relativize(result));
else
System.out.println("(" + id + ") " + result);
try {
System.out.println("RealPath: "
+ result.toRealPath());
} catch (IOException e) {
System.out.println(e);
}
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
System.out.println(base);
Path p = Paths.get("files", "AddAndSubtractPaths.java")
.toAbsolutePath();
show(1, p);
Path convoluted = p.getParent().getParent()
.resolve("strings")
.resolve("..")
.resolve(p.getParent().getFileName());
show(2, convoluted);
show(3, convoluted.normalize());
Path p2 = Paths.get("..", "..");
show(4, p2);
show(5, p2.normalize());
show(6, p2.toAbsolutePath().normalize());
Path p3 = Paths.get(".").toAbsolutePath();
Path p4 = p3.resolve(p2);
show(7, p4);
show(8, p4.normalize());
Path p5 = Paths.get("").toAbsolutePath();
show(9, p5);
show(10, p5.resolveSibling("strings"));
show(11, Paths.get("nonexistent"));
}
}
Path提供了在路径中添加和删除某些路径片段来构建新Path对象的方法。
上述代码,创建了一个base基准路径,然后使用relativize()方法,从所有输入Path中删除了基准路径,通过resolve在一个Path对象后面添加路径片段。
2 目录
Files工具类包含了操作目录和文件所需的大部分操作。
2.1 删除多级目录
Files只提供了删除某个文件或空目录的方法,没有提供直接删除目录树的功能(类似rm -rf命令),需要自己实现:
public static void rmdir(Path dir) throws IOException {
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
Files.walkFileTree()可以遍历每个子目录和文件,通过SimpleFileVisitor匿名内部类,可以对每个遍历的对象执行动作:
visitFile():在这个目录下的每个文件上运行postVisitDirectory:先进入当前目录下的文件和目录,最后在当前目录上运行
3 文件系统
通过FileSystems工具,可以获得默认的文件系统。
public class FileSystemDemo {
static void show(String id, Object o) {
System.out.println(id + ": " + o);
}
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
FileSystem fsys = FileSystems.getDefault();
for(FileStore fs : fsys.getFileStores())
show("File Store", fs);
for(Path rd : fsys.getRootDirectories())
show("Root Directory", rd);
show("Separator", fsys.getSeparator());
show("UserPrincipalLookupService",
fsys.getUserPrincipalLookupService());
show("isOpen", fsys.isOpen());
show("isReadOnly", fsys.isReadOnly());
show("FileSystemProvider", fsys.provider());
show("File Attribute Views",
fsys.supportedFileAttributeViews());
}
}
4 监听Path
通过FileSystem可以得到一个WatchService,它使我们能够设置一个进程,对某个目录中的变化做出反应。
public class PathWatcher {
static Path test = Paths.get("test");
/**
* 遍历目录树,删除文件名后缀为.txt的所有文件
*/
static void delTxtFiles() {
try {
Files.walk(test)
.filter(f ->
f.toString().endsWith(".txt"))
.forEach(f -> {
try {
System.out.println("deleting " + f);
Files.delete(f);
} catch (IOException e) {
throw new RuntimeException(e);
}
});//删除test目录下所有以.txt结尾的文件
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
Directories.refreshTestDir();
Directories.populateTestDir();
Files.createFile(test.resolve("Hello.txt"));
WatchService watcher =
FileSystems.getDefault().newWatchService();//生成WatchService
test.register(watcher, ENTRY_DELETE);//将WatchService注册到Path路径,监听DELETE事件,注意,只监听test目录下的事件
Executors.newSingleThreadScheduledExecutor()
.schedule(
PathWatcher::delTxtFiles,
250, TimeUnit.MILLISECONDS);//执行删除文件操作
WatchKey key = watcher.take();//阻塞等待目标事件发生,返回WatchKey
for (WatchEvent evt : key.pollEvents()) {
System.out.println(
"evt.context(): " + evt.context() +
"\nevt.count(): " + evt.count() +
"\nevt.kind(): " + evt.kind());
System.exit(0);
}
}
}
上述代码中,将WatchService和感兴趣的DELETE事件一起注册到Path路径上,从而对该路径下的目标事件进行监听。调用的watcher.take()方法,主线程阻塞等待相应事件发生,返回一个包含WatchEvent的WatchKey。
监听事件类型包括ENTRY_CREATE、ENTRY_DELETE和ENTRY_MODIFY。
注意:
WatchService注册到Path路径上,只会监听这个目录,不会监听整个目录树。要想监听整个目录树,需要在整个树的每个子目录上设置一个WatchService,如下所示:
public class TreeWatcher {
static void watchDir(Path dir) {
try {
WatchService watcher =
FileSystems.getDefault().newWatchService();
dir.register(watcher, ENTRY_DELETE);
Executors.newSingleThreadExecutor().submit(() -> {
try {
WatchKey key = watcher.take();
for (WatchEvent evt : key.pollEvents()) {
System.out.println(
"evt.context(): " + evt.context() +
"\nevt.count(): " + evt.count() +
"\nevt.kind(): " + evt.kind());
System.exit(0);
}
} catch (InterruptedException e) {
return;
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
Directories.refreshTestDir();
Directories.populateTestDir();
Files.walk(Paths.get("test"))
.filter(Files::isDirectory)
.forEach(TreeWatcher::watchDir);
PathWatcher.delTxtFiles();
}
}
5 查找文件
通过在FileSystem对象上调用getPathMatcher()方法,并传入glob或者regex表达式,来获得一个PathMatcher对象。调用该对象的match方法,可以对Path进行匹配。
public class Find {
public static void main(String[] args) throws Exception {
Path test = Paths.get("test");
Directories.refreshTestDir();
Directories.populateTestDir();
// Creating a *directory*, not a file:
Files.createDirectory(test.resolve("dir.tmp"));
PathMatcher matcher = FileSystems.getDefault()
.getPathMatcher("glob:**/*.{tmp,txt}");//使用glob表达式匹配所有文件名以.tmp和.txt结尾的Path
Files.walk(test)
.filter(matcher::matches)
.forEach(System.out::println);
System.out.println("***************");
PathMatcher matcher2 = FileSystems.getDefault()
.getPathMatcher("glob:*.tmp");
Files.walk(test)
.map(Path::getFileName)
.filter(matcher2::matches)
.forEach(System.out::println);
System.out.println("***************");
Files.walk(test) // Only look for files
.filter(Files::isRegularFile)
.map(Path::getFileName)
.filter(matcher2::matches)
.forEach(System.out::println);
}
}
6 读写文件
java.nio.file.Files类包含了方便读写文本文件和二进制文件的工具函数。
Files.readAllLines()方法可以一次性读入整个文件,生成并返回一个List<String>。
public class ListOfLines {
public static void main(String[] args) throws Exception {
Files.readAllLines(
Paths.get("files","../streams/Cheese.dat"))
.stream()
.filter(line -> !line.startsWith("//"))
.map(line ->
line.substring(0, line.length() / 2))
.forEach(System.out::println);
}
}
Files提供了write重载方法,可以将byte数组或者实现了Iterable<? extends CharSequence>接口的对象写入文件。
public class Writing {
static Random rand = new Random(47);
static final int SIZE = 1000;
public static void main(String[] args) throws Exception {
// Write bytes to a file:
byte[] bytes = new byte[SIZE];
rand.nextBytes(bytes);
Files.write(Paths.get("bytes.dat"), bytes);//将byte数组写入文件
System.out.println("bytes.dat: " +
Files.size(Paths.get("bytes.dat")));
// Write an iterable to a file:
List<String> lines = Files.readAllLines(
Paths.get("files","../streams/Cheese.dat"));
Files.write(Paths.get("Cheese.txt"), lines);//将List对象写入文件
System.out.println("Cheese.txt: " +
Files.size(Paths.get("Cheese.txt")));
}
}
注意:上述读取文件的方式,对文件的大小有要求:
- 如果文件非常大,那么一次性读取整个文件将占用大量内存;
- 如果只需要获取文件中的部分内容,那么读取整个文件是不必要的;
Files.lines()方法可以将一个文件变为由一个行组成的Stream。
Files.lines(Paths.get("PathInfo.java"))
.skip(13)
.findFirst()
.ifPresent(System.out::println);
在一个流中完成文件读取、处理和文件写入:
try(
Stream<String> input =
Files.lines(Paths.get("StreamInAndOut.java"));
PrintWriter output =
new PrintWriter("StreamInAndOut.txt")
) {
input
.map(String::toUpperCase)
.forEachOrdered(output::println);
} catch(Exception e) {
throw new RuntimeException(e);
}