欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java7中那些新特性 - 5 (文件系统篇)

程序员文章站 2022-03-16 13:04:51
...
  一个文件系统通常指的是一个或多个根目录,其下面包含一定的文件和子目录,并由此组成的目录结构。每一种文件系统都支持一种文件存储机制。这种机制有可能是一个设备,例如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本人也没有完全理解,有待进一步的研究和学习。
相关标签: java7