1Z0-809复习 第9章 NIO.2

NIO.2

  • Use Path interface to operate on file and directory paths
  • Use Files class to check, read, delete, copy, move, manage metadata of a file or directory
  • Use Stream API with NIO.2

NIO.2 中的N是指Non-blocking(非阻塞)
java.io API能做更多的事,具体是指什么?
学习如何读取和修改文件属性。

Introducing NIO.2

java.io 是基于byte stream对文件进行操作。
java 1.4开始增加了 NIO, 基于buffers 和 channels。 什么是channels?

The basic idea is that you load the data from a file channel into a temporary buffer that, unlike byte streams, can be read forward and backward without blocking on the underlying resource.

从文件频道读取数据到一个临时的buffer中。跟byte streams是不同的。
可以无阻赛的前后读取resource。

1.4中的初版NIO没有流行,绝大多数程序员还是在使用java.io API

NIO打算取代 java.io streams,而NIO.2 是取代java.io.File以及相关交互。
NIO.2和NIO要区分对待。NIO.2是为了更加直观,更多特性来操作File。并且对FileClass也有更多的性能改善。

f:id:pinetree-chen:20180927201916p:plain

Introducing Path

Path,用来取代java.io.File,跟File基本相同
区别:Path能够创建,操作symblolic link这个特殊的file。

Creating Instances with Factory and Helper Classes

通过Paths这个工厂类来创建Path的一个instance。Path 本身是一个interface。

为什么要用工厂类而不是直接调用Path实体类的构造函数
原因是创建一个文件或目录是需要基于文件系统、
使用Path Object,所有基于文件系统的特性就是透明的,
相对的java.io.File就不是透明的。
通过不同的Path的Implementation,Java可以文件处理文件系统的不同之处。

NIO.2还有不helper class,例如 java.nio.file.Files,用于操作Path Objects。

Creating Paths

Using the Paths Class

Paths.get("绝对路径/相对路径的文件名");
参数可以是vararg,也可以是URI
注意下面这个用/来表示绝对路径的例子。
Path path3 = Paths.get("/","home","zoodirector");
注意不要搞错了Path和Paths的含义。
下面这个例子是用java.net.URI来创建Path。
Path path3 = Paths.get(new URI("file:///home/zoodirectory"));
注意有3个///,因为URI只能是绝对路径。
另外这个方法一定会抛出URISyntaxException,所以必须try/catch
isAbsolute()是指URI前面带没有带Schema。
Path有toUri()这个方法来将Path转回URI

Accessing the Underlying FileSystem Object

可以通过FileSystems这个工厂类来获取FileSystem这个instance,
然后通过FileSystem来获取Path。
Path path3 = FileSystems.getDefault().getPath("/home/zoodirector");
注意这里是getPath方法。

FileSystem fileSystem = FileSystems.getFileSystem(
    new URI("http://www.selikoff.net"));
Path path = fileSystem.getPath("duck.txt");

通过FileSystem,NIO2增加了访问外部文件系统的能力。
但访问文件系统有什么意义,还是不是很明白。

Path比File的好处:更多的特性,内制支持FileSystem和symbolic links。

Working with Legacy File Instances

File.toPath();
Path.toFile();
一般还是推荐用Path,因为比File有更多好处。

Interacting with Paths and Files

Path的内容并不是实际存在的文件内容,因此即使文件不存在,
很多方法都不会出错。
其中之一toRealPath()会去看文件是否实际存在。
不存在会出NoSuchFileException

Providing Optional Arguments

ATOMIC_MOVE是一个不可分割的原子操作,这个操作让进行观察者看不到没有完成的状态,
或者说只有写了部分的文件。
NOFOLLOW_LINKS,FOLLOW_LINKS COPY_ATTRIBUTES,REPLACE_EXISTING,ATOMIC_MOVE

使用vararg的目的是为了松散耦合这些方法,今后万一增加属性,也不需要修改方法签名。

Using Path Objects

Path的很多方法都是返回Path的instance,因此可以chain起来。例如:
Paths.get("/zoo/../home").getParent().normalize().toAbsolutePath();

Viewing the Path with toString(), getNameCount(), and getName()

getNameCount(),根目录不包含在里面。
如果是一个只有根目录的目录调用该方法,返回的结果是0。

Accessing Path Components with getFileName(), getParent(),and getRoot()

getFileName()返回的是文件名,离root最远的那个元素。
如果是文件,当然返回文件;如果是目录,则是最远的那个目录。
getParent(),返回上层路径,如果本身是根目录,那就返回null。
getRoot();绝对路径就返回根目录,如果是相对路径就返回null。

Checking Path Type with isAbsolute() and toAbsolutePath()

如果已经是绝对路径了,那toAbsolutePath()返回的是一个新的Path Object,跟原来的Path内容一样。

Creating a New Path with subpath()

subpath(int,int),前面是闭口,后面是开口。
返回的是相对路径。
越界了会出java.lang.IllegalArgumentException错。 开始终了的int相同也会出java.lang.IllegalArgumentException。

Using Path Symbols

./ 当前目录; ../ 上层目录

Deriving a Path with relativize()
Path path1 = Paths.get("fish.txt");
Path path2 = Paths.get("birds.txt");
System.out.println(path1.relativize(path2));
System.out.println(path2.relativize(path1));

返回的结果

..\birds.txt
..\fish.txt

特别要注意:不是.\

relativize(path)必须都是绝对路径或者相对路径。
单方面的㐊的话,出java.lang.IllegalArgumentException。

path1.resolve(path2),如果path2是绝对路径。
那么path1就会被放弃,生成一个跟path2内容相同的新的对象。

Cleaning Up a Path with normalize()

E:\data..\user\home 经过normalize(), 变成 E:\user\home

Checking for File Existence with toRealPath()

内部会做normalize(),跟toAbsolutePath()相同,多一个检测文件是否真实存在。

Interacting with Files

Files这个helpclass操作的是Path instance,跟File类完全没有关系,要注意。

Testing a Path with exists()
Testing Uniqueness with isSameFile()

Files.isSameFile(Path,Path) 首先比较两个Path是不是相同,如果是相同的,就不去看文件是否真实存在。 如果两个Path的结果是不同的,会去locate两个实际文件,得到其绝对路径,看其是否相同。
文件完全相同,但在不同路径的话,就是不同的。

try {
    System.out.println(Files.isSameFile(Paths.get("/user/home/cobra"),
    Paths.get("/user/home/snake")));//相同
    System.out.println(Files.isSameFile(Paths.get("/user/tree/../monkey"),
    Paths.get("/user/monkey")));//相同
    System.out.println(Files.isSameFile(Paths.get("/leaves/./giraffe.exe"),Paths.get("/leaves/giraffe.exe")));//相同
    System.out.println(Files.isSameFile(Paths.get("/flamingo/tail.data"),
    Paths.get("/cardinal/tail.data")));//不同
} catch (IOException e) {
// Handle file I/O exception...
}
Making Directories with createDirectory() and createDirectories()

相对于File的mkdir()和mkdirs()

Duplicating File Contents with copy()

Files.copy(Path,Path),
目录拷贝时不拷贝子目录,也不拷贝文件。那跟直接建立一个目录有什么区别?
拷贝行为,可以通过NOFOLLOW_LINKS,REPLACE_EXISTING, and COPY_ATTRIBUTES, 这些option来改变。

Changing a File Location with move()

可以用来移动或者改名。

移动一个空目录到另一个drive是可以的。
移动一个非空目录到另一个drive will throw an NIO.2 DirectoryNotEmptyException

Removing a File with delete() and deleteIfExists()

Files.delete(Path)只能删除空的目录或者文件,
如果目录非空,则出DirectoryNotEmptyException
如果target是一个symbol link,那么这个symbol link被删掉,对象目录不会被删掉。

deleteIfExists(Path),如果不存在不会出错。返回false

Reading and Writing File Data with newBufferedReader() and newBufferedWriter()

Files.newBufferedReader(Path,Charset),需要事先定义字符集或者用缺省的字符集(Charset.defaultCharset())。
推荐用Buffer类,提高性能。

后面用lambda表达式,更快更简单。

Reading Files with readAllLines()

后面新方法,处理更大的文件。

Understanding File Attributes

file attributes = metadata

Discovering Basic File Attributes

Reading Common Attributes with isDirectory(), isRegularFile(), and isSymbolicLink()

不需要调用exists(),不存在就是false。

Checking File Visibility with isHidden()
Testing File Accessibility with isReadable() and isExecutable()

同样,如果不存在就是false,而不是Exception

Reading File Length with size()

目录的大小没法用这个方法,在后面会讲到。

Managing File Modifications with getLastModifiedTime() and

setLastModifiedTime() 返回的是FileTime这个对象。
Files.setLastModifiedTime(Path,FileTime) 设值的时候也需要用FileTime。

Managing Ownership with getOwner() and setOwner()

返回的是UserPrincipal对象。
Files.setOwner(Path,UserPrincipal) 设值的时候也需要用UserPrincipal。

try {
    // Read owner of file
    Path path = Paths.get("/chicken/feathers.txt");
    System.out.println(Files.getOwner(path).getName());
    // Change owner of file
    UserPrincipal owner = path.getFileSystem()
        .getUserPrincipalLookupService().lookupPrincipalByName("jane");
    Files.setOwner(path, owner);
    // Output the updated owner information
    System.out.println(Files.getOwner(path).getName());
} catch (IOException e) {
    // Handle file I/O exception...
}

Improving Access with Views

如果要读入很多FileAttribute,一次读取一个肯定会耗费很多IO资源。
因此有一个view,一次读入多个属性,性能提高会比较高。
即使真的只读入一个属性,也可以用View的方法。性能也不会差很多。
但是用一个的方法更好一点。

Understanding Views

Files.readAttributes()返回一个只读的view Files.getFileAttributeView(),返回一个可以更新的view

  • BasicFileAttributes: 基本的文件属性View
  • DosFileAttributes : DOS/Windows的文件属性View
  • PosixFileAttributes: Unix/Linux的文件属性View
Reading Attributes
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;

public class BasicFileAttributesSample {

    public static void main(String[] args) throws IOException {
        Path path = Paths.get("C:/A01.Chen/09_陳 剛/fj3731iv.p12");
        BasicFileAttributes data = Files.readAttributes(path,
            BasicFileAttributes.class);
        System.out.println("Is path a directory? " + data.isDirectory());
        System.out.println("Is path a regular file? " + data.isRegularFile());
        System.out.println("Is path a symbolic link? " + data.isSymbolicLink());
        System.out.println("Path not a file, directory, nor symbolic link? " +
            data.isOther());
        System.out.println("Size (in bytes): " + data.size());
        System.out.println("Creation date/time: " + data.creationTime());
        System.out.println("Last modified date/time: " + data.lastModifiedTime());
        System.out.println("Last accessed date/time: " + data.lastAccessTime());
        System.out.println("Unique file identifier (if available): " +
            data.fileKey());
    }
}
Modifying Attributes
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.*;
public class BasicFileAttributeViewSample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("/turtles/sea.txt");
        BasicFileAttributeView view =
            Files.getFileAttributeView(path,BasicFileAttributeView.class);
        BasicFileAttributes data = view.readAttributes();
        FileTime lastModifiedTime = FileTime.fromMillis(
            data.lastModifiedTime().toMillis()+10_000);
        view.setTimes(lastModifiedTime,null,null);
    }
}
void setTimes(FileTime lastModifiedTime,
              FileTime lastAccessTime,
              FileTime createTime)

Presenting the New Stream Methods

Conceptualizing Directory Walking

遍历目录树的概念。

Selecting a Search Strategy

Streams API uses depth-first searching with a default maximum depth of Integer.MAX_VALUE.

Walking a Directory

Files.walk(path)返回一个Stream 对象。lazy manner

Path path = Paths.get("C:/A01.Chen/09_陳 剛");
try {
    Files.walk(path)
        .filter(p -> p.toString().endsWith(".xlsx"))
        .forEach(System.out::println);
} catch (IOException e) {
    // Handle file I/O exception...
}

这个非常好用,可以取回某个目录下的所有的.xlsx文件。

Searching a Directory

Path path = Paths.get("C:/A01.Chen/09_陳 剛");
long dateFilter = 1420070400000l;
try {
    Files.find(path, 10,
        (p, a) -> p.toString().endsWith(".xlsx")
            && a.lastModifiedTime().toMillis() > dateFilter)
        .forEach(System.out::println);
} catch (Exception e) {
    // Handle file I/O exception...
}

感觉这个还是有点怪的,似乎一定要加上第二个条件

Listing Directory Contents

Path path = Paths.get("C:/A01.Chen/09_陳 剛");
Files.list(path)
    .filter(p -> !Files.isDirectory(p))
    .map(p -> p.toAbsolutePath())
    .forEach(System.out::println);

跟walk的区别是这个只返回一层里面所有的内容。

Printing File Contents

Files.readAllLines(Paths.get("birds.txt")).forEach(System.out::println);
Files.lines(Paths.get("birds.txt")).forEach(System.out::println);

Comparing Legacy File and NIO.2 Methods