第17章、简单地读写文件

在非常难用的文件I/O编程存在多年之后,Java终于简化了文件读写的基本操作

本文将研究操作文件的连个基本组件:

  1. [文件或目录]的路径
  2. 文件本身

1、文件或目录路径

Path对象代表一个文件或目录的路径,它是不同操作系统和文件系统之上的抽象。
java.nio.file.Paths类包含了重载的static.get()方法,可以接受一个String序列或URI,然后将其转化为Path对象。

import java.nio.file.*;
import java.net.URI;
import java.io.File;
import java.io.IOException;

public class PathInfo {
    static void show(String id, Object p) {
        System.out.println(id + p);
    }
    static void info(Path p) {
        show("toString:\n ", p);
        //Files工具类
        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("src/files/PathInfo.java");
        info(p);
        Path ap = p.toAbsolutePath();
        info(ap);
        info(ap.getParent());
        try {
            //对于不区分大小写的文件系统,将返回原始大小写Path
            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));
        //实际上也是路径(类似于Path),向后兼容的妥协
        File f = ap.toFile(); // Don't be fooled
    }
}
/* Output:
Windows 10
toString:
 C:\path\to\nowhere\NoFile.txt
Exists: false
RegularFile: false
Directory: false
Absolute: true
FileName: NoFile.txt
Parent: C:\path\to\nowhere
Root: C:\
******************
toString:
 src\files\PathInfo.java
Exists: true
RegularFile: true
Directory: false
Absolute: false
FileName: PathInfo.java
Parent: src\files
Root: null
******************
toString:
 F:\IdeaProjects\OnJava\src\files\PathInfo.java
Exists: true
RegularFile: true
Directory: false
Absolute: true
FileName: PathInfo.java
Parent: F:\IdeaProjects\OnJava\src\files
Root: F:\
******************
toString:
 F:\IdeaProjects\OnJava\src\files
Exists: true
RegularFile: false
Directory: true
Absolute: true
FileName: files
Parent: F:\IdeaProjects\OnJava\src
Root: F:\
******************
toString:
 F:\IdeaProjects\OnJava\src\files\PathInfo.java
Exists: true
RegularFile: true
Directory: false
Absolute: true
FileName: PathInfo.java
Parent: F:\IdeaProjects\OnJava\src\files
Root: F:\
******************
URI:
file:///F:/IdeaProjects/OnJava/src/files/PathInfo.java
true
*/

1.1、选择Path的片段

import java.nio.file.*;

public class PartsOfPaths {
    public static void main(String[] args) {
        System.out.println(System.getProperty("os.name"));
        Path p =
                Paths.get("src/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) {//或者结合getNameCount()与getName(n)
            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()));
    }
}
/*
Windows 10
IdeaProjects
OnJava
src
files
PartsOfPaths.java
ends with '.java': false
IdeaProjects: false : false
OnJava: false : false
src: false : false
files: false : false
PartsOfPaths.java: false : true
Starts with F:\ true
*/

1.2、分析Path

Files工具类用于检查Path的各种信息

import java.nio.file.*;
import java.io.IOException;

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("src/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));
    }
}
/*
Windows 10
Exists: true
Directory: false
Executable: true
Readable: true
RegularFile: true
Writable: true
notExists: false
Hidden: false
size: 1443
FileStore: DevelopData (F:)
LastModified: : 2023-03-29T03:24:28.7453995Z
Owner: DESKTOP-M8M3I1T\Hampix (User)
ContentType: text/plain
SymbolicLink: false

*/

1.3、添加删除路径片段

relativize()用于去掉Path的基准路径(此例中为base),只有Path是绝对路径是生效。
resolve()用于在Path后面增加路径片段。

import java.nio.file.*;
import java.io.IOException;

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("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"));
  }
}
/* Output:
Windows 10
C:\Git
(1)r OnJava8\ExtractedExamples\files\AddAndSubtractPaths.java
RealPath:
C:\Git\OnJava8\ExtractedExamples\files\AddAndSubtractPaths.java
(2)r OnJava8\ExtractedExamples\files
RealPath: C:\Git\OnJava8\ExtractedExamples\files
(3)r OnJava8\ExtractedExamples\files
RealPath: C:\Git\OnJava8\ExtractedExamples\files
(4)  ..\..
RealPath: C:\Git\OnJava8
(5)  ..\..
RealPath: C:\Git\OnJava8
(6)r OnJava8
RealPath: C:\Git\OnJava8
(7)r OnJava8
RealPath: C:\Git\OnJava8
(8)r OnJava8
RealPath: C:\Git\OnJava8
(9)r OnJava8\ExtractedExamples\files
RealPath: C:\Git\OnJava8\ExtractedExamples\files
(10)r OnJava8\ExtractedExamples\strings
RealPath: C:\Git\OnJava8\ExtractedExamples\strings
(11)  nonexistent
java.nio.file.NoSuchFileException:
C:\Git\OnJava8\ExtractedExamples\files\nonexistent
*/

2、目录

Files工具类包含了操作目录和文件的大部分操作,但是没有删除目录树的工具,咱们创建一个。

import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;

public class RmDir {
    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;
                    }
                });
    }
}

下面探索创建和填充目录

package files;
import java.util.*;
import java.nio.file.*;
import onjava.RmDir;

public class Directories {
    static Path test = Paths.get("src/files/test");
    static String sep =
            FileSystems.getDefault().getSeparator();
    static List<String> parts =
            Arrays.asList("foo", "bar", "baz", "bag");
    static Path makeVariant() {
        Collections.rotate(parts, 1);
        return Paths.get("src/files/test", String.join(sep, parts));
    }
    static void refreshTestDir() throws Exception {
        if(Files.exists(test))
            RmDir.rmdir(test);
        if(!Files.exists(test))
            //如果目录存在将抛出异常
            Files.createDirectory(test);
    }
    public static void
    main(String[] args) throws Exception {
        refreshTestDir();
        Files.createFile(test.resolve("Hello.txt"));
        Path variant = makeVariant();
        // Throws exception (too many levels):
        try {
            //只能用于创建单层目录,所以抛出异常。
            //populateTestDir()的Files.createDirectories(variant)才能正常创建多层目录
            Files.createDirectory(variant);
        } catch(Exception e) {
            System.out.println("Nope, that doesn't work.");
        }
        populateTestDir();
        Path tempdir =
                Files.createTempDirectory(test, "DIR_");
        Files.createTempFile(tempdir, "pre", ".non");
        //只显示一层目录
        Files.newDirectoryStream(test)
                .forEach(System.out::println);
        System.out.println("*********");
        Files.walk(test).forEach(System.out::println);
    }
    static void populateTestDir() throws Exception {
        for(int i = 0; i < parts.size(); i++) {
            Path variant = makeVariant();
            if(!Files.exists(variant)) {
                Files.createDirectories(variant);
                Files.copy(Paths.get("src/files/Directories.java"),
                        variant.resolve("File.txt"));
                Files.createTempFile(variant, null, null);
            }
        }
    }
}
/*
Nope, that doesn't work.
src\files\test\bag
src\files\test\bar
src\files\test\baz
src\files\test\DIR_16351143387970971436
src\files\test\foo
src\files\test\Hello.txt
*********
src\files\test
src\files\test\bag
src\files\test\bag\foo
src\files\test\bag\foo\bar
src\files\test\bag\foo\bar\baz
src\files\test\bag\foo\bar\baz\2288129667382909257.tmp
src\files\test\bag\foo\bar\baz\File.txt
src\files\test\bar
src\files\test\bar\baz
src\files\test\bar\baz\bag
src\files\test\bar\baz\bag\foo
src\files\test\bar\baz\bag\foo\10024225869691822436.tmp
src\files\test\bar\baz\bag\foo\File.txt
src\files\test\baz
src\files\test\baz\bag
src\files\test\baz\bag\foo
src\files\test\baz\bag\foo\bar
src\files\test\baz\bag\foo\bar\12798335110278345190.tmp
src\files\test\baz\bag\foo\bar\File.txt
src\files\test\DIR_16351143387970971436
src\files\test\DIR_16351143387970971436\pre3449890123475405278.non
src\files\test\foo
src\files\test\foo\bar
src\files\test\foo\bar\baz
src\files\test\foo\bar\baz\bag
src\files\test\foo\bar\baz\bag\9307929951700391831.tmp
src\files\test\foo\bar\baz\bag\File.txt
src\files\test\Hello.txt

Process finished with exit code 0

*/

3、文件系统

import java.nio.file.*;

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());
  }
}
/*
Windows 10
File Store: 系统 (C:)
File Store: 软件 (D:)
File Store: FunData (E:)
File Store: DevelopData (F:)
File Store: LearnData (G:)
File Store: Backup Plus (H:)
Root Directory: C:\
Root Directory: D:\
Root Directory: E:\
Root Directory: F:\
Root Directory: G:\
Root Directory: H:\
Separator: \
UserPrincipalLookupService: sun.nio.fs.WindowsFileSystem$LookupService$1@7b23ec81
isOpen: true
isReadOnly: false
FileSystemProvider: sun.nio.fs.WindowsFileSystemProvider@6acbcfc0
File Attribute Views: [owner, dos, acl, basic, user]

Process finished with exit code 0

*/

4、监听Path

WatchService能开一个进程,对某一目录的变化做出反应

package files;
import java.io.IOException;
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.util.concurrent.*;

public class PathWatcher {
    static Path test = Paths.get("src/files/test");
    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);
                        }
                    });
        } 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();
        //只监听此目录,而不是目录树
        test.register(watcher, ENTRY_DELETE);
        Executors.newSingleThreadScheduledExecutor()
                .schedule(
                        PathWatcher::delTxtFiles,
                        250, TimeUnit.MILLISECONDS);
        //阻塞等待事件
        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);
        }
    }
}
/*
deleting src\files\test\bag\foo\bar\baz\File.txt
deleting src\files\test\bar\baz\bag\foo\File.txt
deleting src\files\test\baz\bag\foo\bar\File.txt
deleting src\files\test\foo\bar\baz\bag\File.txt
deleting src\files\test\Hello.txt
evt.context(): Hello.txt
evt.count(): 1
evt.kind(): ENTRY_DELETE

Process finished with exit code 0

*/
import java.io.IOException;
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import java.util.concurrent.*;

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("src/files/test"))
                .filter(Files::isDirectory)
                .forEach(TreeWatcher::watchDir);
        PathWatcher.delTxtFiles();
    }
}
/*
deleting src\files\test\bag\foo\bar\baz\File.txt
deleting src\files\test\bar\baz\bag\foo\File.txt
deleting src\files\test\baz\bag\foo\bar\File.txt
deleting src\files\test\foo\bar\baz\bag\File.txt
evt.context(): File.txt
evt.count(): 1
evt.kind(): ENTRY_DELETE
*/

5、查找文件

上面都是通过toString(),然后用String的各种操作查看结果。可以使用PathMatcher,通过FileSystem对象上调用getPathMatcher()获得,有glob和regex两种模式。

import java.nio.file.*;

public class Find {
    public static void
    main(String[] args) throws Exception {
        Path test = Paths.get("src/files/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}");
        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);
    }
}
/*
src\files\test\bag\foo\bar\baz\1371568932388083606.tmp
src\files\test\bag\foo\bar\baz\File.txt
src\files\test\bar\baz\bag\foo\146766178072502416.tmp
src\files\test\bar\baz\bag\foo\File.txt
src\files\test\baz\bag\foo\bar\15935029429709749912.tmp
src\files\test\baz\bag\foo\bar\File.txt
src\files\test\dir.tmp
src\files\test\foo\bar\baz\bag\15790184490602227067.tmp
src\files\test\foo\bar\baz\bag\File.txt
***************
1371568932388083606.tmp
146766178072502416.tmp
15935029429709749912.tmp
dir.tmp
15790184490602227067.tmp
***************
1371568932388083606.tmp
146766178072502416.tmp
15935029429709749912.tmp
15790184490602227067.tmp

Process finished with exit code 0

*/

6、读写文件

Files.readAllLinews()一次性读取整个文件,返回一个List<String>。
小文件。

import java.util.*;
import java.nio.file.*;

public class ListOfLines {
    public static void
    main(String[] args) throws Exception {
        Files.readAllLines(
                        Paths.get("src/streams/Cheese.dat"))
                .stream()
                //跳过注释行
                .filter(line -> !line.startsWith("//"))
                .map(line ->
                        line.substring(0, line.length()/2))
                .forEach(System.out::println);
    }
}
/* Output:
Not much of a cheese
Finest in the
And what leads you
Well, it's
It's certainly uncon
*/

Files.write()

import java.util.*;
import java.nio.file.*;

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("src/files/bytes.dat"), bytes);
        System.out.println("bytes.dat: " +
                Files.size(Paths.get("src/files/bytes.dat")));

        // Write an iterable to a file:
        List<String> lines = Files.readAllLines(
                Paths.get("src/streams/Cheese.dat"));
        Files.write(Paths.get("src/files/Cheese.txt"), lines);
        System.out.println("Cheese.txt: " +
                Files.size(Paths.get("src/files/Cheese.txt")));
    }
}
/* Output:
bytes.dat: 1000
Cheese.txt: 199
*/

Files.lines()
大文件

import java.nio.file.*;

public class ReadLineStream {
    public static void
    main(String[] args) throws Exception {
        Files.lines(Paths.get("src/files/PathInfo.java"))
                .skip(13)
                .findFirst()
                .ifPresent(System.out::println);
    }
}
/*
        //Files工具类
*/

在一个流中完成读取、处理、写入

import java.io.*;
import java.nio.file.*;
import java.util.stream.*;

public class StreamInAndOut {
    public static void main(String[] args) {
        try(
                Stream<String> input =
                        Files.lines(Paths.get("src/files/StreamInAndOut.java"));
                PrintWriter output =
                        new PrintWriter("src/files/StreamInAndOut.txt")
        ) {
            input
                    .map(String::toUpperCase)
                    .forEachOrdered(output::println);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值