Java7中那些新特性 - 5 (文件系统篇)
程序员文章站
2022-03-16 13:04:51
...
一个文件系统通常指的是一个或多个根目录,其下面包含一定的文件和子目录,并由此组成的目录结构。每一种文件系统都支持一种文件存储机制。这种机制有可能是一个设备,例如C盘或一个磁盘分区,或者是其它的某种组织文件系统空间的方式。Java7中的java.nio.file.FileStore就代表了一种文件存储机制。下面来举例说明如何获得FileStore以及其相关信息。
上面这个例子中,我们在控制台上打印出来了各个磁盘的名称,类型以及磁盘空间使用情况。与此同时FileSystem类提供了getRootDirectories方法来获得磁盘的各个跟目录(Linux中通常只有一个根目录/,但Windows中通常有多个根目录C: D: E:)。参照下面的例子来看看如何打印出磁盘的各个根目录。
当我们和一个目录文件系统打交道的时候,很多情况下都要遍历整个文件夹以及其子文件夹。Java7中对此也提供了新的API - java.nio.file.SimpleFileVisitor。话不多说直接看例子:
Files的walkFileTree方法以我们给定的一个目录作为根目录,对每一级文件夹都做了遍历。SimpleFileVisitor接口的每一个方法通过其名称就能知道其具体用途。本人理解即是对访问目录前,访问文件以及访问目录后的回调函数。每个方法都返回一个FileVisitResult,上层API由这个返回值来决定是否继续访问后面的目录以及文件。
我们之前的博客提到过,如果想删除一个文件夹,文件夹必须是空的,那有没有办法能做到删除一个包含了很多目录和文件的文件夹呢?此时,我们就可以通过SimpleFileVisitor来实现。
同样的方法,我们也可以复制一个文件夹下所有的内容到一个新文件夹下。
最后我们来看看如何监控一个目录下的变化,并对不同变化作出一些处理。
当调用WatchService的take方法时,这个方法会阻塞当前线程,直到一个文件变化的事件发生。此时,一个WatchKey对象会被返回。WatchKey对象包含了所发生事件的相关信息。它的watchable方法返回的是所被观察的对象信息(例子中的D:/home/sample文件夹)。pollEvents方法返回了一个所有要被执行的事件列表。最后我们调用了watchKey的reset方法来将这个key设置成初始状态,接受新的响应事件。
Java7中新增对于文件系统操作的API不少,在此只是列出了一些常会用到的功能,有一些API本人也没有完全理解,有待进一步的研究和学习。
public static final long kiloByte = 1024; public static void main(String[] args) { String format = "%-16s %-20s %-8s %-8s %12s %12s %12s\n"; System.out.printf(format, "Name", "Filesystem", "Type", "Readonly", "Size(KB)", "Used(KB)", "Available(KB)"); FileSystem fileSystem = FileSystems.getDefault(); try { for (FileStore fileStore : fileSystem.getFileStores()) { long totalSpace = fileStore.getTotalSpace() / kiloByte; long usedSpace = (fileStore.getTotalSpace() - fileStore.getUnallocatedSpace()) / kiloByte; long usableSpace = fileStore.getUsableSpace() / kiloByte; String name = fileStore.name(); String type = fileStore.type(); boolean readOnly = fileStore.isReadOnly(); NumberFormat numberFormat = NumberFormat.getInstance(); System.out.printf(format, name, fileStore, type, readOnly, numberFormat.format(totalSpace), numberFormat.format(usedSpace), numberFormat.format(usableSpace)); } } catch (IOException e) { e.printStackTrace(); } }
上面这个例子中,我们在控制台上打印出来了各个磁盘的名称,类型以及磁盘空间使用情况。与此同时FileSystem类提供了getRootDirectories方法来获得磁盘的各个跟目录(Linux中通常只有一个根目录/,但Windows中通常有多个根目录C: D: E:)。参照下面的例子来看看如何打印出磁盘的各个根目录。
public static void main(String[] args) { FileSystem fileSystem = FileSystems.getDefault(); FileSystemProvider provider = fileSystem.provider(); System.out.println("Provider: " + provider.toString()); System.out.println("Open: " + fileSystem.isOpen()); System.out.println("Read Only: " + fileSystem.isReadOnly()); Iterable<Path> rootDirectories = fileSystem.getRootDirectories(); System.out.println(); System.out.println("Root Directories"); for (Path path : rootDirectories) { System.out.println(path); } }
当我们和一个目录文件系统打交道的时候,很多情况下都要遍历整个文件夹以及其子文件夹。Java7中对此也提供了新的API - java.nio.file.SimpleFileVisitor。话不多说直接看例子:
public static void main(String[] args) { try { Path path = Paths.get("D:/Home/sample"); ListFiles listFiles = new ListFiles(); Files.walkFileTree(path, listFiles); } catch (IOException e) { e.printStackTrace(); } }
class ListFiles extends SimpleFileVisitor<Path> { private final int indentionAmount = 3; private int indentionLevel; public ListFiles() { indentionLevel = 0; } private void indent() { for (int i = 0; i < indentionLevel; i++) { System.out.print(' '); } } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { indent(); System.out.println("Visiting file:" + file.getFileName()); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { indentionLevel = indentionLevel - indentionAmount; indent(); System.out.println("Finished with the directory: " + dir.getFileName()); return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { indent(); System.out.println("About to traverse the directory: " + dir.getFileName()); indentionLevel = indentionLevel + indentionAmount; return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { System.out.println("A file traversal error ocurred"); return super.visitFileFailed(file, exc); }
Files的walkFileTree方法以我们给定的一个目录作为根目录,对每一级文件夹都做了遍历。SimpleFileVisitor接口的每一个方法通过其名称就能知道其具体用途。本人理解即是对访问目录前,访问文件以及访问目录后的回调函数。每个方法都返回一个FileVisitResult,上层API由这个返回值来决定是否继续访问后面的目录以及文件。
我们之前的博客提到过,如果想删除一个文件夹,文件夹必须是空的,那有没有办法能做到删除一个包含了很多目录和文件的文件夹呢?此时,我们就可以通过SimpleFileVisitor来实现。
public static void main(String[] args) { try { Files.walkFileTree(Paths.get("D:/Home/sample/subtest"), new DeleteDirectory()); } catch (IOException e) { e.printStackTrace(); } }
class DeleteDirectory extends SimpleFileVisitor<Path> { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println("Deleting " + file.getFileName()); Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exception) throws IOException { if (exception == null) { System.out.println("Deleting " + dir.getFileName()); Files.delete(dir); return FileVisitResult.CONTINUE; } else { throw exception; } } }
同样的方法,我们也可以复制一个文件夹下所有的内容到一个新文件夹下。
public static void main(String[] args) { try { Path source = Paths.get("D:/Home/sample"); Path target = Paths.get("D:/Home/backup"); Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new CopyDirectory( source, target)); } catch (IOException ex) { ex.printStackTrace(); } }
class CopyDirectory extends SimpleFileVisitor<Path> { private Path source; private Path target; public CopyDirectory(Path source, Path target) { this.source = source; this.target = target; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { System.out.println("Copying " + source.relativize(file)); Files.copy(file, target.resolve(source.relativize(file))); return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Path targetDirectory = target.resolve(source.relativize(dir)); try { System.out.println("Copying " + source.relativize(dir)); Files.copy(dir, targetDirectory); } catch (FileAlreadyExistsException e) { if (!Files.isDirectory(targetDirectory)) { throw e; } } return FileVisitResult.CONTINUE; } }
最后我们来看看如何监控一个目录下的变化,并对不同变化作出一些处理。
public static void main(String[] args) { try { FileSystem fileSystem = FileSystems.getDefault(); WatchService watchService = fileSystem.newWatchService(); Path dir = Paths.get("D:/home/sample"); WatchEvent.Kind<?>[] events = { StandardWatchEventKinds.ENTRY_CREATE, // StandardWatchEventKinds.ENTRY_MODIFY, // StandardWatchEventKinds.ENTRY_DELETE }; dir.register(watchService, events); while (true) { System.out.println("Waiting for a watch event"); WatchKey watchKey = watchService.take(); System.out.println("Path being watched: " + watchKey.watchable()); System.out.println(); if (watchKey.isValid()) { for (WatchEvent<?> event : watchKey.pollEvents()) { System.out.println("Kinds: " + event.kind()); System.out.println("Contnet: " + event.context()); System.out.println("Count: " + event.count()); System.out.println(); } boolean valid = watchKey.reset(); if (!valid) { // The watchKey is not longer registered } } } } catch (IOException | InterruptedException e) { // TODO: handle exception } }
当调用WatchService的take方法时,这个方法会阻塞当前线程,直到一个文件变化的事件发生。此时,一个WatchKey对象会被返回。WatchKey对象包含了所发生事件的相关信息。它的watchable方法返回的是所被观察的对象信息(例子中的D:/home/sample文件夹)。pollEvents方法返回了一个所有要被执行的事件列表。最后我们调用了watchKey的reset方法来将这个key设置成初始状态,接受新的响应事件。
Java7中新增对于文件系统操作的API不少,在此只是列出了一些常会用到的功能,有一些API本人也没有完全理解,有待进一步的研究和学习。