参考地址: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.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,即移除路径中的 . 与 ..,
如:
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.file.Files,与java.nio.file.Path 一起使用。
介绍几个基本的方法,其他方法参考javaDoc
根据给定的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()来判断文件是否存在。
根据给定的path判断文件是否不存在。boolean notExists(Path path, LinkOption… options)
默认情况,符号链接是会跟从的。但是,如果传递LinkOption.NOFOLLOW_LINKS参数,符号链接就不会跟从了。如果目录或文件不存在,则返回true,如果目录或文件存在或者不知道其存在性,则返回false。
注: !exists(path) 不等于 notExists(path)(因为 !exists() 不一定是原子的,而 notExists() 是原子的)。同时,如果 exists() 和 notExists() 都返回false,则说明文件的存在性不清楚。最后,类似于访问性判断方法,这些方法的结果也是会瞬间过时的,因此,在安全性敏感的应用中应该避免至少应改谨慎使用。
创建目录,目录已存在情况下抛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();
}
文件的复制,目标文件已存在情况下抛
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: 表明如果目标文件已存在,覆盖。
文件移动,也可以用作文件重命名。
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:表明如果目标文件已存在,覆盖。
文件删除。注:只能删除空文件 。
Path path = Paths.get("data/subdir/logging-moved.properties");
try {
Files.delete(path);
} catch (IOException e) {
//deleting file failed
e.printStackTrace();
}
递归读取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() {
@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() {
@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() {
@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();
}
异步从文件中读取、写入数据。
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 operation = fileChannel.read(buffer, 0);
fileChannel.read(buffer, position, buffer, new CompletionHandler() {
@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 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 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效率 。
fileChannel.read(buffer, position, buffer, new CompletionHandler() {
@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 operation = fileChannel.write(buffer, position);
示例如下:
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 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()判断写操作是否完成,完成后做完成后续操作。
即向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() {
@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();
}
});