Java 7对原有的NIO进行了重大改进,改进的主要内容主要包括以下两个方面:
1、提供了全面的文件IO和文件系统访问支持
2、基于异步Channel的IO。
第一个改进表现为Java 7 新增的java.nio.file包及其各个子包;第二个改进表现为Java 7在java.nio.channels包下新增的多个以Asynchronous开头的Channel接口和类。Java 7 把这种改进称为NIO.2。本节先介绍第二个改进
一、Path、Paths和Files核心API
传统的Java里,只有一个File类,即代表文件,又代表目录。但File类的功能比较有限,它不能利用特定文件系统的特性,File提供的方法性能也不高。而且大多数方法在出错时仅返回失败,并不会提供异常信息。
NIO.2为了弥补这种不足,Java 7新增了如下API来访问文件
(1)Path - 接口,代表一个平台无关的平台路径。提供了大量的方法来操作目录。
(2)Paths - 工具类。包含了两个返回path的静态工厂方法。
(2)Files - 包含了大量的静态的工具方法来操作文件。
1.1 Paths工具类与path使用举例
下面程序简单示范了path接口的功能和用法:
package section10;
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathTest
{
public static void main(String[] args) throws Exception
{
//以当前路径创建path对象
Path path= Paths.get(".");
System.out.println("path里包含的路径数量:"+path.getNameCount());//1
System.out.println("path的根路径:"+path.getRoot());//null
//获取path对应的绝对路径
Path absolutePath=path.toAbsolutePath();
System.out.println(absolutePath);//E:Javachap15.
//获取绝对路径的根路径
System.out.println("absolutePath的根路径:"+absolutePath.getRoot());//E:
//获取绝对路径所包含的路径数量
System.out.println("absolutePath里包含的路径数量:"+absolutePath.getNameCount());//3
for(int i=0;i<absolutePath.getNameCount();i++)
{System.out.println(absolutePath.getName(i));}
//Java
//chap15
//.
//一多个String来构建Path对象
Path path1=Paths.get("g:","publish","codes");
System.out.println(path1);//g:publishcodes
}
}
从上面的程序可以看出,Paths提供了get(String first,String...more)方法来获取Path对象,Path会将多个给定的字符串连接成路径,比如Paths.get("g:","publish","codes");返回g:publishcodes。getNameCount()方法用于返回Path路径所包含的路径名数量,例如g:publishcodes返回2,分别为publish,codes.
1.2 Files工具类与File使用举例
Files是一个工具类,她提供了大量的便捷的工具方法,下面程序简单示范了Files类的用法:
import java.io.FileOutputStream;
import java.nio.charset.Charset;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class FileTest
{
public static void main(String[] args)
throws Exception
{
//复制文件
Path path= Paths.get("src","FileTest.java");
System.out.println(path);//srcFileTest.java
Files.copy(path,new FileOutputStream("src//a.txt"));
//判断FileTest.java是否为隐藏文件
System.out.println("FileTest.java是否为隐藏文件:"+Files.isHidden(path));//false
//一次性读取FileTest.java文件的所有行
List<String> lines=Files.readAllLines(path);
System.out.println(lines.get(0));//import java.io.FileOutputStream;
//判断文件大小
System.out.println("FileTest.java文件的大小:"+Files.size(path));//1826
List<String> poem=new ArrayList<>();
poem.add("窗前明月光");
poem.add("疑似地上霜");
//直接将多个字符串内容写进指定文件
Files.write(Paths.get("src","poem.txt"),poem, Charset.forName("utf-8"));
//使用Java8新增的Stream API列出当前目录下所有文件和子目录
Files.list(Paths.get(".")).forEach(path1 -> System.out.println(path1));//代码1
//使用Java8新增得Stream API读取文件内容
Files.lines(path,Charset.forName("utf-8")).forEach(line->System.out.println(line));//代码2
//判断C盘总空间、可用空间
FileStore cstore=Files.getFileStore(Paths.get("C:"));
System.out.println("C盘总空间:"+cstore.getTotalSpace());//85900390400
System.out.println("C盘可用空间:"+cstore.getUsableSpace());//43449065472
}
}
Files类是一个高度封装得工具类,它提供大量的工具方法来完成文件得复制、读取文件、写入文件等功能——这些原本需要程序员通过IO操作才能完成的功能,现在只需一个Files工具类即可。
二、使用FileVisitor遍历文件和目录
Files工具类提供两个方法来遍历文件和子目录:
(1)walkFileTree(Path start,FileVisitor<? super path>visitor):遍历start路径下所有文件和目录。
(2)walkFileTree(Path start,Set
上面两个方法都需要FileVisitor参数,FileVisitor代表一个文件访问器,walkFileTree()方法会自动遍历start路径下所有文件和子目录,遍历文件和子目录都会触发FileVisitor中相应的方法。FileVisitor中定义了4个方法:
//访问子目录之前触发该方法
public FileVisitResult preVisitDirectory(Object dir, BasicFileAttributes attrs)
//访问文件时触发该方法
public FileVisitResult visitFile(Object file, BasicFileAttributes attrs)
//访问文件失败时触发该方法
public FileVisitResult visitFileFailed(Object file, IOException exc)
//访问子目录之后触发该方法
public FileVisitResult postVisitDirectory(Object dir, IOException exc)
上面4个方法都返回一个FileVisitResult对象,它是一个枚举类,代表访问值后的后续行为。
CONTINUE:代表“继续访问”的后续行为
TERMINATE:代表“终止访问”的后续行为
SKIP_SUBTREE:代表“继续访问“,但不访问该目录文件或目录的子目录
SKIP_SIBLINGS:代表“继续访问”,但不访问该文件或目录的兄弟文件或目录
实际编程中没必要为FileVisitor中的4个方法都提供实现,可以通过继承SimpleFileVisitor(FileVisitor的实现类)来实现自己的文件访问器,这样就根据需要、选择性地重写指定方法。
下面程序使用了FileVisitor来遍历文件和子目录:
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class FileVisitorTest
{
public static void main(String[] args)
throws Exception
{
// 遍历g:publishcodes15目录下的所有文件和子目录
Files.walkFileTree(Paths.get("g:","codes", "15"),
new SimpleFileVisitor<Path>()
{
// 访问文件时候触发该方法
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) throws IOException
{
System.out.println("正在访问" + file + "文件");
// 找到了FileInputStreamTest.java文件
if (file.endsWith("FileInputStreamTest.java"))
{
System.out.println("--已经找到目标文件--");
return FileVisitResult.TERMINATE;
}
return FileVisitResult.CONTINUE;
}
// 开始访问目录时触发该方法
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException
{
System.out.println("正在访问:" + dir + " 路径");
return FileVisitResult.CONTINUE;
}
});
}
}
正在访问:g:codes15 路径
正在访问:g:codes1515.1 路径
正在访问g:codes1515.11546017388045文件
正在访问g:codes1515.1FilenameFilterTest.class文件
正在访问g:codes1515.1FilenameFilterTest.java文件
正在访问g:codes1515.1FileTest.class文件
正在访问g:codes1515.1FileTest.java文件
正在访问:g:codes1515.10 路径
正在访问g:codes1515.10a.txt文件
正在访问g:codes1515.10AttributeViewTest.class文件
正在访问g:codes1515.10AttributeViewTest.java文件
正在访问g:codes1515.10FilesTest.class文件
正在访问g:codes1515.10FilesTest.java文件
正在访问g:codes1515.10FileVisitorTest$1.class文件
正在访问g:codes1515.10FileVisitorTest.class文件
正在访问g:codes1515.10FileVisitorTest.java文件
正在访问g:codes1515.10PathTest.class文件
正在访问g:codes1515.10PathTest.java文件
正在访问g:codes1515.10pome.txt文件
正在访问g:codes1515.10WatchServiceTest.class文件
正在访问g:codes1515.10WatchServiceTest.java文件
正在访问:g:codes1515.3 路径
正在访问g:codes1515.3FileInputStreamTest.class文件
正在访问g:codes1515.3FileInputStreamTest.java文件
--已经找到目标文件--
上面程序使用Files工具类的walkFileTree()方法来遍历G:codes15目录下的所有文件和子目录,如果找到文件以FileVisitorTest.java结尾的,则程序停止遍历——这就实现了对指定目录仅需搜索,直到找到文件为止。
三、使用WatchService监控文件变化
在以前的Java版本中,如果程序需要监控文件的变化,则可以考虑启动一条后台线程,这条线程每个一段时间就去“遍历”一次指定目录的文件,如果发现次次遍历结果与上次遍历结果不同,则认为文件发生了变化。但是这种方式不仅十分繁琐,而且性能不好。
NIO.2的Path类提供了一个方法来监听文件系统的变化
register(WatchService watcher,WatchEvent.Kind<?>...events):用watcher监听该path代表的目录下的文件变化。event参数指定要监听哪些类型的事件。
在这个方法中WatchService代表一个文件监听服务,它负责监听path代表的目录下的文件变化。一旦register完成注册,接下来接可以调用WatchService的如下三个方法来监听目录的文件变化事件。
(1)WatchKey poll():获取下一个WatchKey,如果没有WatchKey发生就立即返回null。
(2)WatchKey poll(long timeout,TimeUnit unit):尝试timeout事件去获取下一个WatchKey。
(3)WatchKey take():获取下一个WatchKey,如果没有WatchKey发生就一直等待。
如果一条程序需要一直监控,应该选用take()方法;如果程序只需要监控指定时间,则可以考虑使用poll()方法。下面程序示范了WatchService来监控C:盘更路径下文件的变化:
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
public class WatchServiceTest
{
public static void main(String[] args)
throws Exception
{
// 获取文件系统的WatchService对象
WatchService watchService = FileSystems.getDefault()
.newWatchService();
// 为C:盘根路径注册监听
Paths.get("C:/").register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
while (true)
{
// 获取下一个文件改动事件
WatchKey key = watchService.take(); // ①
for (WatchEvent<?> event : key.pollEvents())
{
System.out.println(event.context() +" 文件发生了 "
+ event.kind()+ "事件!");
}
// 重设WatchKey
boolean valid = key.reset();
// 如果重设失败,退出监听
if (!valid)
{
break;
}
}
}
}
新建文件夹 文件发生了 ENTRY_CREATE事件!
新建文件夹 文件发生了 ENTRY_DELETE事件!
上面程序使用了一个死循环重复获取C:盘根路径下文件的变化,程序在①处试图获取下一个WatchKey,如果没有发生,就等待,因此C:盘根路径下的每次文件的变化都会被该程序监听到。运行该程序,然后再C:盘下新建一个文件,再删除该文件,可以得到上面的输出。
三、访问文件属性
早期Java提供File类可以访问一些简单的文件属性,比如文件大小、修改时间、文件是否隐藏,是文件还是目录。如果程序需要获取或修改更多的文件属性,额必须利用运行所在平台特定代码来实现,这是一件非常困难的事。
Java 7的NIO2在java.nio.file.attribute包下提供了大量工具类,通过这些工具类,开发者可以很简单地读取、修改文件属性。这些工具类主要分为两类:
★XxxAttributeView:代表某种文件的“视图”。
★XxxAtrributes:代表某种文件属性的“集合”,程序一般通过XxxAttributeView对象来获取XxxAttributes。
这些工具类中,FileAttributeView是其他XxxAttributeView的父接口,下面简单介绍一下这些XxxAttributeView。
1、AclFileAttributeView:通过AclFileAttributeView,开发者可以为特定文件设置ACL(Access Control List)及文件所有者属性。它的getAcl()方法返回List
2、BasicFileAttributeView:它可以获取或修改文件的基本属性,包括文件最后修改时间、最后访问时间、创建时间、大小、是否为目录、是否为符号链接等。它的readAttributes()方法返回一个BasicFileAttributes对象,对文件夹基本属性的修改是通过BasicFileAttributes来完成的。
3、DosFileAttributeView:它主要获取或修改文件DOS相关的属性,比如文件是否可读、是否隐藏、是否为系统文件、是否是存档文件等。它的readAttributes()方法返回一个DosFileAttributes对象,对这些属性的修改其实是通过DosFileAttributes对象来完成的。
4、FileAttributeView:它主要用于获取或修改文件的所有者。它的getOwner()方法返回一个UserPrincipal对象来代替文件所有者;也可通过调用setOwer(UserPricipal owner)方法来改变文件的所有者。
5、PosixFileAttributeView:它主要用于获取或修改POSIX(Portable Operating System Interface of INIX)属性,它的readAttributes()方法返回一个PosixFileAttributes对象,该对象用于获取或修改文件的所有者、组所有者、访问权限信息/这个View只在UNIX、Linux等系统上有用。
6、UserDefinedFileAttributeView:它可以让开发者为文件设置一些自定义属性。
下面程序示范了如何读取、修改文件的属性:
import java.io.*;
import java.util.*;
import java.nio.file.*;
import java.nio.*;
import java.nio.charset.*;
import java.nio.file.attribute.*;
public class AttributeViewTest
{
public static void main(String[] args)
throws Exception
{
// 获取将要操作的文件
Path testPath = Paths.get("AttributeViewTest.java");
// 获取访问基本属性的BasicFileAttributeView
BasicFileAttributeView basicView = Files.getFileAttributeView(
testPath, BasicFileAttributeView.class);
// 获取访问基本属性的BasicFileAttributes
BasicFileAttributes basicAttribs = basicView.readAttributes();
// 访问文件的基本属性
System.out.println("创建时间:" + new Date(basicAttribs
.creationTime().toMillis()));
System.out.println("最后访问时间:" + new Date(basicAttribs
.lastAccessTime().toMillis()));
System.out.println("最后修改时间:" + new Date(basicAttribs
.lastModifiedTime().toMillis()));
System.out.println("文件大小:" + basicAttribs.size());
// 获取访问文件属主信息的FileOwnerAttributeView
FileOwnerAttributeView ownerView = Files.getFileAttributeView(
testPath, FileOwnerAttributeView.class);
// 获取该文件所属的用户
System.out.println(ownerView.getOwner());
// 获取系统中guest对应的用户
UserPrincipal user = FileSystems.getDefault()
.getUserPrincipalLookupService()
.lookupPrincipalByName("guest");
// 修改用户
ownerView.setOwner(user);
//获取访问自定义属性的FileOwnerAttributeView
UserDefinedFileAttributeView userView = Files.getFileAttributeView(
testPath, UserDefinedFileAttributeView.class);
List<String> attrNames = userView.list();
// 遍历所有的自定义属性
for (var name : attrNames)
{
ByteBuffer buf = ByteBuffer.allocate(userView.size(name));
userView.read(name, buf);
buf.flip();
String value = Charset.defaultCharset().decode(buf).toString();
System.out.println(name + "--->" + value);
}
// 添加一个自定义属性
userView.write("发行者", Charset.defaultCharset()
.encode("疯狂Java联盟"));
// 获取访问DOS属性的DosFileAttributeView
DosFileAttributeView dosView = Files.getFileAttributeView(testPath,
DosFileAttributeView.class);
// 将文件设置隐藏、只读
dosView.setHidden(true);
dosView.setReadOnly(true);
}
}