JAVA NIO path

参考地址:http://tutorials.jenkov.com/java-nio/path.html

JAVA NIO Path为JAVA 7中JAVA NIO新增的接口。完整包名为: java.nio.file.Path
JAVA PATH指向的是文件系统里的一个路径,可以指向文件,也可以指向目录,可以用绝对路径表示,也可以用相对路径表示。
注:java.nio.file.Path接口与文件系统中环境变量path无关。

Java NIO path实例创建**

通过java.nio.file.Paths.get()方法即可创建Path实例。
可通过绝对路径创建:

//通过绝对路径创建,windows os
    Path path=Paths.get("D://test/test.txt");
    //通过绝对路径创建,linux
    Path path1=Paths.get("/home/opt/app/test/test.txt");

如果windows环境下以绝对路径创建时,以/开头,如:

/home/jakobjenkov/myfile.txt

创建path实例时会解析在前面加上磁盘所在目录,即,解析成:

C:/home/jakobjenkov/myfile.txt

也可以通过相对路径创建,通过Paths.get(basePath, relativePath)方法,

//全路径为d:\\data\\projects    
Path projects = Paths.get("d:\\data", "projects");
//全路径为d:\\data\projects\\a-project\\myfile.txt
    Path file     = Paths.get("d:\\data", "projects\\a-project\\myfile.txt");

. 与 ..

. 表明当前目录
.. 表明上级目录

Path currentDir = Paths.get("d:\\data\\projects\.\a-project");

指明的路径为:

d:\data\projects\a-project
String path = "d:\\data\\projects\\a-project\\..\\another-project";

指明的路径为:

d:\\data\\projects\\another-project

Path.normalize()

格式化path,即移除路径中的 . 与 ..,
如:

String originalPath ="d:\\data\\projects\\a-project\\..\\another-project";
    Path path1 = Paths.get(originalPath);
    System.out.println("path1 = " + path1);

    Path path2 = path1.normalize();
    System.out.println("path2 = " + path2);

输出为:

path1 = d:\data\projects\a-project\..\another-project
path2 = d:\data\projects\another-project

path2会移除..

JAVA NIO Files

完整包名java.nio.file.Files,与java.nio.file.Path 一起使用。
介绍几个基本的方法,其他方法参考javaDoc

Files.exists()

根据给定的path判断文件是否存在。

boolean exists(Path path, LinkOption… options)

示例如下:

Path path=java.nio.file.Paths.get("data/logging.properties");
boolean pathExists=java.nio.file.Files.exists(path, new LinkOption[]{ LinkOption.NOFOLLOW_LINKS});

通过Files.exist()方法判断路径是否存在时,必须先指定一个Path实例。默认情况下符号链接会被追踪。
第二个参数是一个LinkeOption数组,决定文件是否存在。LinkOption.NOFOLLOW_LINKS表明不追踪符号链接。如果目录或文件存在,则返回true,如果目录或文件不存在或者不知道其存在性,则返回false。

在对文件进行操作前,需要调用exists()来判断文件是否存在。

Files.noExist()

根据给定的path判断文件是否不存在。boolean notExists(Path path, LinkOption… options)
默认情况,符号链接是会跟从的。但是,如果传递LinkOption.NOFOLLOW_LINKS参数,符号链接就不会跟从了。如果目录或文件不存在,则返回true,如果目录或文件存在或者不知道其存在性,则返回false。
注: !exists(path) 不等于 notExists(path)(因为 !exists() 不一定是原子的,而 notExists() 是原子的)。同时,如果 exists() 和 notExists() 都返回false,则说明文件的存在性不清楚。最后,类似于访问性判断方法,这些方法的结果也是会瞬间过时的,因此,在安全性敏感的应用中应该避免至少应改谨慎使用。

Files.createDirectory()

创建目录,目录已存在情况下抛FileAlreadyExistsException,其他情况抛IOException,如下:

Path path = Paths.get("data/subdir");
    try {
        Path newDir = Files.createDirectory(path);
    } catch(FileAlreadyExistsException e){
        // the directory already exists.
    } catch (IOException e) {
        //something else went wrong
        e.printStackTrace();
    }

Files.copy()

文件的复制,目标文件已存在情况下抛
java.nio.file.FileAlreadyExistsException,其他情况下会抛IOException
示例如下

Path sourcePath      = Paths.get("data/logging.properties");
Path destinationPath = Paths.get("data/logging-copy.properties");
try {
    Files.copy(sourcePath, destinationPath,
            StandardCopyOption.REPLACE_EXISTING);
} catch(FileAlreadyExistsException e) {
    //destination file already exists
} catch (IOException e) {
    //something else went wrong
    e.printStackTrace();
}

StandardCopyOption.REPLACE_EXISTING: 表明如果目标文件已存在,覆盖。

Files.move()

文件移动,也可以用作文件重命名。

Path sourcePath      = Paths.get("data/logging-copy.properties");
Path destinationPath = Paths.get("data/subdir/logging-moved.properties");

try {
    Files.move(sourcePath, destinationPath,
            StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
    //moving file failed.
    e.printStackTrace();
}

StandardCopyOption.REPLACE_EXISTING:表明如果目标文件已存在,覆盖。

Files.delete()

文件删除。注:只能删除空文件 。

Path path = Paths.get("data/subdir/logging-moved.properties");
try {
    Files.delete(path);
} catch (IOException e) {
    //deleting file failed
    e.printStackTrace();
}

Files.walkFileTree()

递归读取path下的所有文件,完整方法Files.walkFileTree(Path, FileVisitor );可通过实现FileVisitor 接口在文件读取之前或者过程中进行某些操作。
FileVisitor接口如下:

public interface FileVisitor {
    public FileVisitResult preVisitDirectory(
        Path dir, BasicFileAttributes attrs) throws IOException;
    public FileVisitResult visitFile(
        Path file, BasicFileAttributes attrs) throws IOException;
    public FileVisitResult visitFileFailed(
        Path file, IOException exc) throws IOException;
    public FileVisitResult postVisitDirectory(
        Path dir, IOException exc) throws IOException {
}

在调用Files.walkFileTree(Path, FileVisitor )之前,必须自己实现 FileVisitor 接口,然后将该实现的一个实例当作参数传入到walkFileTree()方法中。对于walkFileTree()读取的每一个文件,都会调用FileVisitor中实现的方法。如果不想自己实现FileVisitor 接口,可以直接继承类SimpleFileVisitor ,SimpleFileVisitor 实现了FileVisitor 接口中的每一个方法。具体见例子

Files.walkFileTree(path, new FileVisitor<Path>() {
  @Override
  public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
    System.out.println("pre visit dir:" + dir);
    return FileVisitResult.CONTINUE;
  }
  @Override
  public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
    System.out.println("visit file: " + file);
    return FileVisitResult.CONTINUE;
  }
  @Override
  public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
    System.out.println("visit file failed: " + file);
    return FileVisitResult.CONTINUE;
  }
  @Override
  public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
    System.out.println("post visit directory: " + dir);
    return FileVisitResult.CONTINUE;
  }
});

其中:
preVisitDirectory():在每次访问目录前,会调用该方法。
postVisitDirectory():在每次访问目录后,会调用该方法
visitFile():在访问文件时,会调用该方法,注意此处是文件,不包括目录 。
visitFileFailed():访问文件失败时,会调用该方法。如没访问权限会导致访问文件失败。

这四个方法都返回一个FileVisitResult,该实现是一个枚举类型,以此决定文件递归访问是否继续,包括四个值:
- CONTINUE:表明文件访问继续
- TERMINATE:表明文件访问中止
- SKIP_SIBLINGS:表明访问文件访问继续,但不再访问当前访问文件或者目录的兄弟结点
- SKIP_SUBTREE:表明访问文件继续,但不再访问该目录下的其他文件 。仅当 preVisitDirectory()返回该值时有意义,如果其他方法返回该值,意味着与CONTINUE意义相同。

文件查询

以下是一个通过继承类SimpleFileVisitor 来调用walkFileTree() 来实现查询文件README.txt的例子

Path rootPath = Paths.get("data");
String fileToFind = File.separator + "README.txt";

try {
  Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
      String fileString = file.toAbsolutePath().toString();
      //System.out.println("pathString = " + fileString);

      if(fileString.endsWith(fileToFind)){
        System.out.println("file found at path: " + file.toAbsolutePath());
        return FileVisitResult.TERMINATE;
      }
      return FileVisitResult.CONTINUE;
    }
  });
} catch(IOException e){
    e.printStackTrace();
}

递归删除目录

以下是一个通过调用Files.walkFileTree()递归删除目录的例子。在visitFile()和postVisitDirectory()里进行文件删除的动作file.delete();

Path rootPath = Paths.get("data/to-delete");

try {
  Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
      System.out.println("delete file: " + file.toString());
      Files.delete(file);
      return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
      Files.delete(dir);
      System.out.println("delete dir: " + dir.toString());
      return FileVisitResult.CONTINUE;
    }
  });
} catch(IOException e){
  e.printStackTrace();
}

Java NIO AsynchronousFileChannel

异步从文件中读取、写入数据。
AsynchronousFileChannel创建
通过AsynchronousFileChannel.open()创建一个AsynchronousFileChannel.

Path path = Paths.get("data/test.xml");
AsynchronousFileChannel fileChannel =
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);

第二个参数StandardOpenOption.READ表明文件以读的方式打开。

异步读

通过调用AsynchronousFileChannel.read()方法读取数据,有两种方法
- 通过Future,即read()方法返回Future,如下所示:

Future<Integer> operation = fileChannel.read(buffer, 0);
  • 通过CompletionHandler,即将CompletionHandler实例传入read()方法,如下所示:
fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("result = " + result);

        attachment.flip();
        byte[] data = new byte[attachment.limit()];
        attachment.get(data);
        System.out.println(new String(data));
        attachment.clear();
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

    }
});
通过Future读
Future<Integer> operation = fileChannel.read(buffer, 0);

buffer:即从channel中读取数据到buffer中。
0: 表明从文件中读取的位置。

该read()方法立即返回,此时读操作可能并未完全完成,可通过返回的Future实例的isDone()来判断读操作是否完成 。示例如下:

AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

Future<Integer> operation = fileChannel.read(buffer, position);
while(!operation.isDone());
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();

以上例子中,通过循环调用operaion.isDone()来判断读是否完成 。
注:该例子仅供参考,并未考虑CPU效率 。

通过CompletionHandler读
fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("result = " + result);

        attachment.flip();
        byte[] data = new byte[attachment.limit()];
        attachment.get(data);
        System.out.println(new String(data));
        attachment.clear();
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
    }
});

通过向read()方法中传入一个CompletionHandler实例来实现异步读,当读操作完成时,会调用 completed()方法,读操作失败时,会调用failed()方法。

异步写

从buffer中写入到channel中,与异步读类似,异步写也可以通过两种方法
- 通过Future,即write()方法返回Future,如下所示:

Future<Integer> operation = fileChannel.write(buffer, position);
  • 通过CompletionHandler,即将CompletionHandler实例传入write()方法
通过返回Future异步写

示例如下:

Path path = Paths.get("data/test-write.txt");
AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
buffer.put("test data".getBytes());
buffer.flip();

Future<Integer> operation = fileChannel.write(buffer, position);
buffer.clear();

while(!operation.isDone());
System.out.println("Write done");

首先以写的方式open一个channel. StandardOpenOption.WRITE
其次,将数据放入buffer中。
再次,调用channel.write(buffer,position)方法,从buffer中写入channel,返回Future
最后,循环调用future.isDone()判断写操作是否完成,完成后做完成后续操作。

通过CompletionHandler异步写

即向write()中传入一个ComletionHandlder实例,写操作完成时,会调用completed()方法,写操作失败时,会调用failed()方法。

Path path = Paths.get("data/test-write.txt");
if(!Files.exists(path)){
    Files.createFile(path);
}
AsynchronousFileChannel fileChannel = 
    AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

buffer.put("test data".getBytes());
buffer.flip();

fileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {

    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("bytes written: " + result);
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        System.out.println("Write failed");
        exc.printStackTrace();
    }
});