一个文件系统通常指的是一个或多个根目录,其下面包含一定的文件和子目录,并由此组成的目录结构。每一种文件系统都支持一种文件存储机制。这种机制有可能是一个设备,例如C盘或一个磁盘分区,或者是其它的某种组织文件系统空间的方式。Java7中的java.nio.file.FileStore就代表了一种文件存储机制。下面来举例说明如何获得FileStore以及其相关信息。
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本人也没有完全理解,有待进一步的研究和学习。