JAVA 7对原有的NIO进行了重大改进,JAVA 7把泽中改进称为NIO.2,改进主要包括如下两方面的内容:
提供了全面的文件IO和文件系统访问支持
基于异步Channel的IO
Path,Paths和Files类:
早期的时候,Java只提供一个File类来访问文件系统,但File类的功能比较有限,它不能利用特定文件系统的特性,File所提供的方法性能也不高。而且,其大多数方法在出错时仅返回失败,并不会提供异常信息。为了弥补这种不足,引入了一个Path接口,Path接口代表一个平台无关的平台路径。除此之外,Java还提供了Files,Paths两个工具类,其中Files包含了大量静态的工具来操作文件;Paths则包含了两个返回Path的静态工厂方法。
Paths接口的简单示范:
public class NIO {
public void pathTest(){
//以当前路径来创建Path对象
Path path = Paths.get(".");
System.out.println("path里包含的路径数量:"+path.getNameCount());
System.out.println("path的根路径:"+path.getRoot());
//获取path对应的绝对路径
Path absolutePath = path.toAbsolutePath();
System.out.println(absolutePath);
//获取绝对路径的根路径
System.out.println("absolutePath里的根路径:"+absolutePath.getRoot());
//获取绝对路径所包含的路径数量
System.out.println("absolutePath里所包含的路径数量:"+absolutePath.getNameCount());
System.out.println(absolutePath.getName(0));
//以多个String来构建Path对象
Path path_2 = Paths.get("g:", "publish","codes");
System.out.println(path_2);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
NIO nio = new NIO();
nio.pathTest();
}
}
其运行效果如下:
path里包含的路径数量:1
path的根路径:null
D:programmejavaEEprojectfzkjshop_projectfzkjshop_test.
absolutePath里的根路径:D:
absolutePath里所包含的路径数量:6
programme
g:publishcodes
其中Paths提供了两个参数不同的get()方法,其一是get(Url url),其二是get(Stirng first ,String...more)。关于第二个方法,Paths会将给定的多个字符串按顺序连缀成路径,就比如上述代码中的Paths.get("g:", "publish","codes")就返回g:publishcodes,尽管可以这个路径在计算机中可能不存在。Paths.getNameCount()方法是返回所包含的路径名的数量。
简单的示范Files类的用法:
public class NIO {
public void filesTest(){
try{
Path path = Paths.get("D:\data\file\book.txt");
//复制文件
Files.copy(path,new FileOutputStream("D:\data\file\books.txt"));
System.out.println("1. 当前目录下的out.txt文件是否为隐藏属性:"+Files.isHidden(Paths.get("out.txt")));
//一次性读取文件的所有行
List<String> lines = Files.readAllLines(Paths.get("out.txt"));
System.out.println("2."+lines);
System.out.println("3. path路径指向的文件大小为:"+Files.size(path));
//直接将多行字符串内容写入指定文件中
List<String> poem = new ArrayList<String>();
poem.add("床前明月光");
poem.add("疑是地上霜");
poem.add("举头望明月");
poem.add("低头思故乡");
Files.write(Paths.get("out.txt"),poem,Charset.forName("utf-8"));
System.out.println("4.写入完毕");
//使用Java 8新增的Stream API列出当前目录下所有文件和子目录
Files.list(Paths.get(".")).forEach(paths -> System.out.println("5."+paths));;
//使用Java 8新增的Stream API读取文件内容
Files.lines(Paths.get("out.txt"),Charset.forName("utf-8")).forEach(line -> System.out.println("6."+line));
//判断C盘的总空间和可用空间
FileStore cStore = Files.getFileStore(Paths.get("C:"));
System.out.println("7. c盘共有空间:"+cStore.getTotalSpace());
System.out.println("8. c盘可用空间:"+cStore.getUsableSpace());
System.out.println();
}catch(Exception e){}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
NIO nio = new NIO();
nio.filesTest();
}
}
运行效果如下:
1. 当前目录下的out.txt文件是否为隐藏属性:false
2.[床前明月光, 疑是地上霜, 举头望明月, 低头思故乡]
3. path路径指向的文件大小为:14105
4.写入完毕
5...classpath
5...project
5...settings
5..out.txt
5..pom.xml
5..src
5.. arget
6.床前明月光
6.疑是地上霜
6.举头望明月
6.低头思故乡
7. c盘共有空间:69793214464
8. c盘可用空间:24726188032
从上面程序不难看出,Files类是一个高度封装的工具类,它提供了大量工具方法来完成文件复制,读取文件内容,写入文件内容等功能——这些原本需要程序通过IO操作才能完成的功能,现在Files类只要一个工具方法即可。
使用FileVistor遍历文件和目录:
在以前Java版本中,如果程序要遍历指定目录下的所有文件和子目录,则只能使用递归进行遍历,但这种方法不仅复杂,而且灵活性不高。现有了Files工具类做帮助,可以使用更优雅的方式来遍历文件和子目录,Files提供了如下两个方法来遍历文件和子目录:
walkFileTree(Path start,FileVisitor<?super Path> visitor):遍历start目录路径下的所有文件和子目录
walkFileTree(Path start,Set<FileVisitOption> options,int maxDepth,FileVisitor<?super path> visitor):与上一个方法的功能类似。该方法最多遍历maxDepth深度的文件。
上述方法中都需要FileVisitor参数,FileVisitor代表一个文件访问器,walkFileTree()方法会自动遍历start路径下的所有文件和子目录,遍历文件和子目录都会"触发”FileVisitor中的相应方法。FileVistor中定义如下4个方法:
FileVisitResult postVisitDirectory(T dir,IOException exc):访问子目录之后触发该方法。
FileVisitResult preVisitDirectory(T dir,BasicFileAttributes attrs):访问子目录之前触发该方法。
FileVisitResult visitFile(T fileBasicFileAttributes attrs):访问file文件时触发该方法。
FileVisitResult visitFileFailed(T file,IOException exc):访问file文件失败时触发该方法。
以上4个方法都返回一个FileVisitResult对象,它是一个枚举类,代表了访问之后的后续行为。FileVisitResult定义了如下几种后续行为。
CONTINUE:代表“继续访问”的后续行为
SKIP_SUBLINGS:代表“继续访问”的后续行为,但不访问该文件或者目录的兄弟文件或者目录
SKIP_SUBTREE:代表“继续访问”的后续行为,但不访问该文件或目录的子目录树。
TERMINATE:代表“中止访问”的后续行为。
实际编程时没有必要为FileVisitor的4个方法都提供实现,可以通过继承SimpleFileVisitor(FileVisitor的实现类)来实现自己的“文件访问器”,这样就根据需要,选择性地重写指定方法了。
使用FileVisitor来遍历文件和子目录的简单示范:
public class NIO {
public void fileVisitorTest(){
//遍历当前目录下的所有文件和目录
try{
Files.walkFileTree(Paths.get("."), new SimpleFileVisitor<Path>(){
//访问文件触发该方法:
public FileVisitResult visitFile(Path file,BasicFileAttributes attrs){
System.out.println("正在访问"+file+"文件");
if(file.endsWith("NIO.java")){
System.out.println("---已经找到目标文件---");
return FileVisitResult.TERMINATE;
}
return FileVisitResult.CONTINUE;
}
//访问路径触发该方法
public FileVisitResult preVisitDirectory(Path dir,BasicFileAttributes attrs){
System.out.println("正在访问:"+dir+"路径");
return FileVisitResult.CONTINUE;
}
});
}catch(Exception e){}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
NIO nio = new NIO();
nio.fileVisitorTest();
}
}
上述代码实现了对指定目录进行搜索,直到找到指定文件为止。
使用WatchService监控文件变化:
在以前的Java版本中,如果程序需要监控文件的变化,则可以考虑启动一条后台线程,这条后台线程每隔一段时间去“遍历”一次指定目录的文件,如果发现此次遍历结果与上次遍历不同,则认为文件发生了变化。但这种方式不仅十分反锁,而且性能也不好。NIO的Path类提供了一个方法来监听文件系统的变化。
register(WatchService watcher,WatchEvent.Kind<?> ...events):用watcher监听该path代表的目录下文件的变化。events参数指定要监听那些类型的事件。
该方法中,WatchService代表一个文件系统监听服务,它负责监听path代表的目录下的文件变化。一旦使用register()方法完成注册之后,接下来就可调用WatchService的如下三个方法来获取被监听目录的文件变化事件。
WatchKey pol():获取下一个WatchKey,如果没有WatchKey发现就立即返回null。
WatchKey poll(long timeout,TimeUnit unit):尝试等待timeout时间去获取下一个WatchKey。
WatchKey take():获取下一个WatchKey,如果没有WatcKey发生就一直等待。
如果程序需要一直监控,则应该选择使用take()方法;如果程序只需要监控指定时间,则可考虑使用poll()方法。
下面程序示范了使用WatchService来监控C:盘根路径下文件的变化:
public class NIO {
public void watchServiceTest(){
//获取文件系统的WatchService对象
try{
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;
}
}
}catch(Exception e){}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
NIO nio = new NIO();
nio.watchServiceTest();
}
}
上面程序使用了一个死循环重复获取C:盘根路径下文件的变化,当C:盘文件发现变化时都会被程序监听到。下面运行结果是我在c盘跟路径创建一个新建文件夹的时候程序获取文件监听所运行的结果:
新建文件夹文件发生了ENTRY_CREATE事件!
访问文件属性:
NIO.2在Java.nio.file.attribute包下提供了大量的工具类,通过这些工具类,可以非常简单的读取,修改文件属性。这些工具类主要分为如下两类:
XxxAttributeView:代表某种文件属性的”试图“
XxxAttributes:代表某种文件属性的”集合“,程序一般通过XxxAttributeView对象来获取XxxAttributes。
在XxxAttributeView工具类中,FileAttributeView是其他XxxAttributeView的父接口,下面简单介绍一下这些XxxAttributeView:
AclFileAttributeView: 设置文件ACL(Access Control List)以及文件所有者属性。它的getAcl()方法返回List<AclEntry>对象,该返回值代表了文件的权限集。通过setAcl(List)方法可以修改该文件的ACL
BasicFileAttributeView: 它可以获取或者修改文件的基本属性,比如说文件的最后修改事件,最后访问事件,创建时间,大小,是否为目录,是否为符号连接等等。它的readAttributes()方法返回一个BasicFileAttributes对象,对文件基本属性的修改是通过BasicFileAttributes对象完成的。
DosFileAttributeView:它主要用于获取或修改文件DOS相关属性,比如说文件是否只读,是否隐藏,是否为系统文件,是否是存档文件等等。它的readAttributes()返回一个DosFileAttributes对象,对这些属性的修改由DosFileAttributes对象来完成
FileOwnerAttributeView: 它主要用于获取或修改文件的所有者。它的getOwner()方法返回一个UserPrincipal对象来代表文件所有者,也可以调用setOwner(UserPrincipal owner)方法来改变文件的所有者。
PosixFileAttributeView:它主要用于获取或修改POSIX(Portable Operationg System Interface of INIX)属性,它的readAttributes()方法返回一个PosixFileAttributes对象,该对象可用于获取或修改文件的所有者,组所有者,访问权限信息等等。它只在UNIX,Linux等系统上使用
UserDefiendFileAttributeView: 它可以让开发者为文件设置一些自定义属性。
下面是对访问文件属性的简单示范:
public class NIO {
public void attributeViewTest(){
try{
//获取将要操作的文件
Path testPath = Paths.get("out.txt");
//获取访问基本属性的BasicFileAttributeView
BasicFileAttributeView basicView =Files.getFileAttributeView(testPath,BasicFileAttributeView.class);
//获取文件基本属性的BasicFileAttributes
BasicFileAttributes basicAttributes = basicView.readAttributes();
System.out.println("访问文件的创建时间:"+new Date(basicAttributes.creationTime().toMillis()));
System.out.println("访问文件的最后访问时间:"+new Date(basicAttributes.lastAccessTime().toMillis()));
System.out.println("访问文件的最后修改时间:"+new Date(basicAttributes.lastModifiedTime().toMillis()));
System.out.println("文件大小:"+basicAttributes.size());
//获取访问文件属主信息的FileOwnerAttributeView
FileOwnerAttributeView fileOwnerAttributes = Files.getFileAttributeView(testPath,FileOwnerAttributeView.class);
System.out.println("该文件所属的用户:"+fileOwnerAttributes.getOwner());
//获取访问DOS属性的DosFileAttributeView
DosFileAttributeView dosView = Files.getFileAttributeView(testPath, DosFileAttributeView.class);
DosFileAttributes dosFileAttributes = dosView.readAttributes();
System.out.println("设置文件属性为隐藏与只读");
dosView.setReadOnly(true);//将文件设置只读
dosView.setHidden(true);//将文件设置隐藏
System.out.println("文件是否为隐藏:"+dosFileAttributes.isHidden());
System.out.println("文件是否为只读:"+dosFileAttributes.isReadOnly());
}catch(Exception e){}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
NIO nio = new NIO();
nio.attributeViewTest();
}
}
运行效果如下:
访问文件的创建时间:Fri Aug 30 11:49:57 CST 2019
访问文件的最后访问时间:Sat Aug 31 12:05:24 CST 2019
访问文件的最后修改时间:Sat Aug 31 12:04:48 CST 2019
文件大小:68
该文件所属的用户:DESKTOP-44GVSG8HJLのH (User)
设置文件属性为隐藏与只读
文件是否为隐藏:true
文件是否为只读:true