Java基础 -- NIO

NIO(New I/O的缩写),其不是取代IO,而是对IO的一种补充,通过NIO有更多种的手段来操作套接字和文件,以及能得到更高的效率和伸缩性。

目录

核心包和概念:缓冲区 / 通道 / 选择器 / 字符集

    缓冲区:java.nio包

    通道:java.nio.channels包

    选择器:java.nio.channels包

    字符集:java.nio.charset包

重点关注的类和接口

    文件:Files工具类

    文件路径:Path接口

    文件属性:BasicFileAttributes文件属性接口

    文件目录:DirectoryStream目录流

    目录树:Files.walkFileTree();


核心包和概念:缓冲区 / 通道 / 选择器 / 字符集

    缓冲区:java.nio包

缓冲区Buffer类及其子类:浅显的理解就是封装了一个字节数组数据成员的类,其在内存中开辟出一块空间,用于缓冲存放IO流对象的内容。缓冲区核心的概念有三:位置position、容量capacity、界限limit。假若自己实现一个缓冲区类Buffer的伪码如下:

public class MyBuffer{

    private int position=0; //缓冲区的当前位置(/游标),随着get()/put()等方法的调用会改变位置

    private final int capacity; //缓冲区的容量,初始化后不可变

    private int limit; //有效界限:初始时等于capacity,以后可变化(例:flip()方法)

    private byte[] bs; //该缓冲区缓冲存放IO流对象内容的字节数组,需要初始化

    public byte get(){ //从缓冲区中逐个的读取出字节

    }

    public void put(){ //往缓冲区中逐个的写入进字节

    }

    ......

}

缓冲区图示如下:

nio

nio

nio buffer


    通道:java.nio.channels包

通道表示"IO源(如:文件)"和缓冲区之间的已经打开的连接。有了通道(也即:打开的连接),依托通道来互访缓冲区和IO源(如:文件)的字节数据,可以将缓冲区的数据给IO源,也可以将IO源的数据给缓冲区。

获取通道的办法有:

FileInputStream.getChannel()
FileOutputStream.getChannel()
RandomAccessFile.getChannel()
Files.newByteChannel()
FileChannel.open()

通道的常用方法有:

force(boolean metaData) //强制缓冲区的内容刷入硬盘文件
map(FileChannel.MapMode mode, long position, long size) //映射文件
truncate(long size) 
lock() //锁住文件的一部分
tryLock() //尝试锁住文件的一部分
position() //文件的当前位置(/游标)
size() //文件的大小
transferFrom()
transferTo()
read(buf) //从文件当中读出的字节内容存放到buf
write(buf) //往文件当中写入缓冲区buf里的字节内容

filechannel


    选择器:java.nio.channels包

选择器是基于键的,非锁定的多通道IO。换句话说,使用选择器可以通过多个通道执行IO。选择器适合用于基于套接字的通道。


    字符集:java.nio.charset包

字符集定义了字节和字符相互转换时依托的编码规范。例如:

/**
* 编码转换: 看以下示例说明
*
* @1 GBK 键盘输入设备
* @2 GBK-->UTF8(系统隐式转换)
* @3       UTF8-->ISO88591
*
* @4 ISO88591 字节流
* @5 ISO88591-->UTF8
* @6            UTF8-->GBK 屏显输出设备(系统隐式转换)
*/

//@1 GBK 键盘输入设备(windows简体中文,默认GBK)
String strGBK = "中国";
//@2 GBK-->UTF8(系统隐式转换)
byte[] bsUTF8 = strGBK.getBytes("UTF-8"); 
//@3       UTF8-->ISO88591 
String strISO88591 = new String(bsUTF8, "ISO-8859-1");

System.out.println(strISO88591); //6个问号,因为:一中文字符UTF8时占3字节,两中文占6字节,而ISO是单字节字符

//@4 ISO88591 字节流
byte[] bsISO88591 = strISO88591.getBytes("ISO-8859-1"); 
//@5 ISO88591-->UTF8
String strUTF8 = new String(bsISO88591, "UTF-8");
//@6            UTF8-->GBK 屏显输出设备(系统隐式转换,简体中文windows键盘输入和屏显输出时只认GBK)
System.out.println(strUTF8);

重点关注的类和接口

    文件:Files工具类

Files工具类提供了大量的访问文件的方法,比如支持文件的复制、移动、Stream流化等。常用方法有:

readAttributes(path,attriType,linkOption) 获取path的相关属性
createDirectories(path,fileAttributes) 创建拥有目录属性的所有目录
createDirectory(path,fileAttributes) 创建拥有目录属性的目录
createFile(path,fileAttributes) 创建拥有文件属性的文件
delete(path) 删除有path指定的文件
exists(path) path是否存在
isDirectory(path,linkOption) path是否是目录
isRegularFile(path,linkOption) path是否是文件
isWritable(path) 文件是否可写
isReadable(path) 文件是否可读
isExcutable(path) 文件是否可执行
isHidden(path) 文件是否隐藏
copy(srcPath,desPath,copyOption) 拷贝
move(srcPath,desPath,copyOption) 剪切
newByteChannel(path,openOption) 获取一个文件通道
newDirectoryStream(path) 罗列path目录下的直接孩子(不递归孩子的孩子...)
newDirectoryStream(path,filter) 罗列path目录下的直接孩子,有过滤器,Filter函数式接口(不递归孩子的孩子...)
newDirectoryStream(path,globStr) 罗列path目录下的直接孩子,有过滤器,一堆文件后缀名(不递归孩子的孩子...)
walkFileTree(path,fileVisitor) 访问path目录下的所有孩子,依托FileVisitor<Path>访问器
newBufferedReader(path,charset) 互通IO模式,获取指定字符集的缓冲读字符流reader
newInputStream(path,openOption) 互通IO模式,获取读字节流inputStream对象
newBufferdWriter(path,charset,openOption) 互通IO模式,获取指定字符集的缓冲写字符流writer
newOutputStream(path,openOption) 互通IO模式,获取写字节流outputStream对象

    文件路径:Path接口

Path是接口,所以不能new,用来获取Path对象:

Path path = Paths.get(String first, String... more) ;

Path path = Paths.get(URI uri) ;

Path接口封装了文件路径,提供了大量的操作文件路径的方法。常用方法有:

getRoot() 获取根目录
getNameCount() 获取路径中的文件夹个数
iterator() 迭代路径中的所有文件夹
getName(int idx) 获取哪一个文件夹(path封装的第一个文件夹索引为0)
subPath(int sidx, int eidx) 截取路径中的一部分,得到一个新path对象
getFileName() 获取路径中最右端的文件或目录的path对象
getParent() 获取除了getFileName()之前的整个路径的部分的path对象
startsWith(String startStr) 路径是否以给定的字符串开头的(给定的字符串需要有根目录)
endsWith(String endStr) 路径是否以给定的字符串结束的
isAbsolute() 判断是绝对路径还是相对路径
toAbsolutePath() 获取绝对路径
toString() 得到这个路径的字符串形式
toUri() 得到这个路径的URI对象,适合Path path = Paths.get(uri);的path对象
toFile() 互通IO模式,转换成IO模式下的File对象

    文件属性:BasicFileAttributes文件属性接口

用于获取文件的属性信息:

Usage Example:

           Path file = Paths.get("test.txt");
           BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class);

attrs.creationTime();//返回文件的创建时间

attrs.lastAccessTime() ;//返回文件的最后一次访问时间

attrs.lastModifiedTime() ;//返回文件的最后一次修改时间

一个关于Files,Path,BasicFileAttributes的小示例demo(假定文件在F:\demo\java\nio\pathexamples\test.txt,测试类在F:\demo\java\nio\PathTest.java)

import java.nio.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.*;

/**
* 文件在   F:\demo\java\nio\pathexamples\test\temp.txt
* 测试类在 F:\demo\java\nio\PathTest.java
*/

public class PathTest
{
	public static void main(String[] args) 
	{
		try{
			
			Path path = Paths.get("pathexamples/test/temp.txt");
			path = path.normalize();
			
			boolean existFlag = Files.exists(path) ;
			System.out.println( "pathexamples/test/temp.txt, path exists ? : " + existFlag ) ; 

			//path判断目录/文件 path创建目录/文件
			System.out.println("\r\npath判断目录/文件 path创建目录/文件");
			if( !existFlag ){

				Path p = path.getParent(); 
				
				if( null != p ) //path封装的是否至少两层(fileDir1/fileDir2 或者 fileDir/file)
				{
					Files.createDirectories(p); //创建目录pathexamples/test

					if( Files.isRegularFile( path.getFileName() ) ) //创建文件temp.txt
						Files.createFile(path);
				}

				else{ //path封装的就一层(fileDir 或者 file)

					p = path.getFileName();

					if( Files.isDirectory( path.getFileName() ) ){ //path封装的是fileDir
						Files.createDirectory(p);
					}
					else if( Files.isRegularFile( path.getFileName() ) ){ //path封装的是file
						Files.createFile(p);
					}
				}
			}

			//path的常规判断
			System.out.println("\r\npath的常规判断");
			System.out.println( "现在当前目录下,已有pathexamples/test/temp.txt, path exists ? : " + Files.exists(path) ) ; 
			System.out.println( "path is : " + path.toString() );
			System.out.println( "path is absolute ? : " + path.isAbsolute() );//false
			System.out.println( "path startsWith pathexamples is : " + path.startsWith("pathexamples") );
			System.out.println( "path endsWith temp.txt is : " + path.endsWith("temp.txt") );

			//path封装的元素(根目录/文件夹/文件)
			System.out.println("\r\npath封装的元素(根目录/文件夹/文件)");
			System.out.println( "path root is : " + path.getRoot() ); //null
			System.out.println( "path contains elements : " + path.getNameCount() );//3 表示path中有两个元素 pathexamples test temp.txt
			System.out.println( "path index0 is : " + path.getName(0) );//pathexamples
			System.out.println( "path index1 is : " + path.getName(1) );//test
			System.out.println( "path index2 is : " + path.getName(2) );//temp.txt
			Iterator<Path> ite = path.iterator();
			int index=0;
			while( ite.hasNext() )
				System.out.println( "path elements[" + (index++) + "] is: " + ite.next() );

			System.out.println( "path rightmost == path elements[path.getNameCount()-1] is : " + path.getFileName() );//temp.txt (path封装的最右端的文件/目录)
			System.out.println( "path except rightmost is : " + path.getParent() );// pathexamples/test (path封装的除了最右端的剩余的前面部分)

			//path转换成绝对路径
			System.out.println("\r\npath转换成绝对路径");
			Path pathAbsolute = path.toAbsolutePath() ;
			System.out.println( "absolutePath is : " + pathAbsolute );
			System.out.println( "absolutePath is absolute ? : " + pathAbsolute.isAbsolute() );//true
			System.out.println( "absolutePath root is : " + pathAbsolute.getRoot() ); //F:\
			System.out.println( "absolutePath startsWith : " + pathAbsolute.startsWith("F:/") );
			System.out.println( "absolutePath endsWith : " + pathAbsolute.endsWith("temp.txt") );

			//path封装的根目录的问题
			System.out.println("\r\n注意:path封装的根目录");
			Path p1 = Paths.get("./pathexamples/test/temp.txt"); // 有 . 表示当前目录
			Path p2 = Paths.get("/pathexamples/test/temp.txt"); // 有 / 表示根目录
			Path p2Absolute = p2.toAbsolutePath() ;

			System.out.println( "path ./pathexamples/test/temp.txt it's root is : " + p1.getRoot() ); // 输出null
			System.out.println( "path  /pathexamples/test/temp.txt it's root is : " + p2.getRoot() ); // 输出/
			System.out.println( "path  /pathexamples/test/temp.txt it's absolute is : " + p2Absolute.toString() ); 

			//文件拷贝
			System.out.println("\r\n文件拷贝\r\n copy pathSrc to pathDes...");
			Path pathSrc = Paths.get("pathexamples/test/temp.txt");
			Path pathDesCopy = Paths.get("pathexamples/test/tempcopy.txt");			
			//Files.copy(pathSrc, Files.newOutputStream(pathDesCopy) ) ;
			Files.copy(pathSrc, pathDesCopy, StandardCopyOption.REPLACE_EXISTING ) ;
			//Files.copy(Files.newInputStream(pathSrc), pathDesCopy, StandardCopyOption.REPLACE_EXISTING ) ;

			//文件移动
			System.out.println("\r\n文件移动\r\n move pathSrc to pathDes...");
			Path pathDesMove = Paths.get("pathexamples/testmove/tempmove.txt");
			Files.move(pathSrc, pathDesMove, StandardCopyOption.ATOMIC_MOVE) ;

			//文件的属性信息
			System.out.println("\r\n文件的属性");
			BasicFileAttributes attrs = Files.readAttributes(pathDesMove,BasicFileAttributes.class);
			System.out.println( "file isHidden : " + Files.isHidden(pathDesMove) );
			System.out.println( "file isReadable : " + Files.isReadable(pathDesMove) );
			System.out.println( "file isWritable : " + Files.isWritable(pathDesMove) );
			System.out.println( "file isExecutable : " + Files.isExecutable(pathDesMove) );
			System.out.println( "file createTime : " + attrs.creationTime() );
			System.out.println( "file lastAccessTime : " + attrs.lastAccessTime() );
			System.out.println( "file lastModifiedTime : " + attrs.lastModifiedTime() );


		}catch(Exception e){}

	}
}

    文件目录:DirectoryStream目录流

用于访问当前目录下的直接孩子(当前目录下的文件夹/文件,不递归)。请看下方小示例:

import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.*;

/**
* 本测试用例所使用的文件目录结构如下:
*
* F:\demo\java\nio\DirectoryStreamTest.java
*
* F:\demo\java\nio\pathexamples\emptydir 空文件夹
* F:\demo\java\nio\pathexamples\test\dindoa\dwei.txt size(2kb)
* F:\demo\java\nio\pathexamples\test\dindoa\fff.rtf size(2kb)
* F:\demo\java\nio\pathexamples\test\emptybak 空文件夹
* F:\demo\java\nio\pathexamples\test\tempcopy.txt size(0)
* F:\demo\java\nio\pathexamples\test\ttt.doc size(12kb)
* F:\demo\java\nio\pathexamples\testmove\tempmove.txt size(1kb)
* F:\demo\java\nio\pathexamples\sss.rtf size(2kb)
* F:\demo\java\nio\pathexamples\student.txt size(12kb)
*
*/
public class DirectoryStreamTest {

    public static void main(String[] args) throws IOException {

		Path dirPath = Paths.get("pathexamples");

		System.out.println("\r\n无过滤功能,仅仅显示当前目录dirPath下的直接孩子");
		dir(dirPath);

		
		System.out.println("\r\n有过滤功能,仅仅显示当前目录dirPath下的直接孩子,过滤器:Filter函数式接口,举例 Files.size(path)>1");
		DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
			public boolean accept(Path path) throws IOException {
				return Files.isRegularFile(path) && Files.size(path)>1; //返回文件size>1的文件
			}
		};
		dirByFilter(dirPath,filter);

		
		System.out.println("\r\n有过滤功能,仅仅显示当前目录dirPath下的直接孩子文件,过滤器:一堆文件后缀名,举例 \"*.{txt,rtf}\"");
		/**
		* glob文件名规则:
		*          *匹配任意N个字符
		*          ?匹配任意1个字符
		*          [x-z]匹配范围x-z中的任意一个字符
		*          {str1,str2,str3}文件后缀名是str1或str2或str3都能匹配
		*/
		dirByGlob(dirPath,"*.{txt,rtf}");


		System.out.println("\r\n递归:无过滤功能,显示当前目录dirPath下的所有孩子/孩子的孩子/...");
		ergodicDir(dirPath);


		System.out.println("\r\n递归:有过滤功能,显示当前目录dirPath下的所有孩子/孩子的孩子/...,过滤器Filter函数式接口");
		DirectoryStream.Filter<Path> filter2 = new DirectoryStream.Filter<Path>() {
			public boolean accept(Path path) throws IOException {				
				return Files.isRegularFile(path) && Files.size(path)>1; //返回文件size>1的文件
			}
		};
		ergodicDirByFilter(dirPath,filter2);


		System.out.println("\r\n递归:递归:有过滤功能,显示当前目录dirPath下的所有孩子文件/孩子的孩子文件/...,过滤器一堆文件后缀名,举例\"*.{txt,rtf}\"");
		ergodicDirByGlob(dirPath,"*.{txt,rtf}");	
   }

   
   //无过滤功能,仅仅显示当前目录dirPath下的直接孩子
   public static void dir(Path path){
		try( DirectoryStream<Path> dirStream = Files.newDirectoryStream(path) ){
			Iterator<Path> ite = dirStream.iterator();
			while( ite.hasNext() ){
				Path elementPath = ite.next(); 
				if( Files.isDirectory(elementPath) )
				{
					System.out.println("currentDir is:"+ elementPath.getFileName() + " parentDir is" + path.getFileName() );
				}
				else if( Files.isRegularFile(elementPath) )
				{
					System.out.println("currentFile is:"+ elementPath.getFileName() + " parentDir is" + path.getFileName() );
				}
			}
		}catch(Exception e){}
   }

	//有过滤功能,显示当前目录dirPath下的直接孩子,过滤器Filter函数式接口
	public static void dirByFilter(Path path, DirectoryStream.Filter<Path> filter){

		try( DirectoryStream<Path> dirStream = Files.newDirectoryStream(path,filter) ){
			Iterator<Path> ite = dirStream.iterator();
			while( ite.hasNext() ){
				Path elementPath = ite.next(); 
				System.out.println("currentFile is: "+ elementPath.getFileName() + " parentDir is: " + path.getFileName() );				
			}
		}catch(Exception e){}
	}


	//有过滤功能,显示当前目录dirPath下的直接孩子文件,过滤器一堆文件后缀名,举例"*.{txt,rtf}"
   	public static void dirByGlob(Path path, String glob){

		try( DirectoryStream<Path> dirStream = Files.newDirectoryStream(path,glob) ){
			Iterator<Path> ite = dirStream.iterator();
			while( ite.hasNext() ){
				Path elementPath = ite.next(); 
				System.out.println("currentFile is:"+ elementPath.getFileName() + " parentDir is" + path.getFileName() );				
			}
		}catch(Exception e){}
	}



   /**
   * 无过滤器的递归出当前目录下的所有孩子/孩子的孩子/...
   *
   * NLR前遍历目录树:根->左->右 的递归
   *
   * NLR前遍历算法:
   * ergodicLRN(Tree *node){
   *     while( null!=node ){
   *   		 visit(node);
   *   	   	 ergodicLRN(node's leftChild);
   *		 ergodicLRN(node's rightChild);
   *	 }
   * }
   */
   public static void ergodicDir(Path path){

		try( DirectoryStream<Path> dirStream = Files.newDirectoryStream(path) ){
			Iterator<Path> ite = dirStream.iterator();
			while( ite.hasNext() ){
				Path elementPath = ite.next(); 
				if( Files.isDirectory(elementPath) )
				{
					System.out.println("currentDir is: "+ elementPath.getFileName() + " parentDir is: " + path.getFileName() );
					ergodicDir(elementPath);//递归
				}
				else if( Files.isRegularFile(elementPath) )
				{
					System.out.println("currentFile is: "+ elementPath.getFileName() + " parentDir is: " + path.getFileName() );
				}
			}
		}catch(Exception e){}
   }

   /**
   * 有过滤器的递归出当前目录下的所有孩子/孩子的孩子/... 过滤器Filter函数式接口,举例 Files.size(path)>1
   *
   * LRN后遍历目录树:左->右->根 的递归
   *
   * LRN后遍历算法:
   * ergodicLRN(Tree *node){
   *     while( null!=node ){
   *   	   	 ergodicLRN(node's leftChild);
   *		 ergodicLRN(node's rightChild);
   *   		 visit(node);
   *	 }
   * }
   */
   public static boolean ergodicDirByFilter(Path path, DirectoryStream.Filter<Path> filter){

		boolean isEmptyDirFlag = true; // 是否空文件夹标志

		try( DirectoryStream<Path> dirStream = Files.newDirectoryStream(path) ){
			
			Iterator<Path> ite = dirStream.iterator();
			int iteSize = 0 ;

			int childDirectories = 0 ;
			int childFiles = 0 ;


			while( ite.hasNext() ){
				
				++iteSize;

				isEmptyDirFlag = false;

				Path elementPath = ite.next(); 
				if( Files.isDirectory(elementPath) )
				{
					if( ergodicDirByFilter(elementPath, filter) ) //递归
						++childFiles;
				}
				else if( Files.isRegularFile(elementPath) )
				{
					++childFiles;
				}
			}

			
			if( childFiles == iteSize ){
				dirByFilter(path,filter);
				isEmptyDirFlag = true; //消费过本目录后,这里假定该目录为空,以便给上级目录使用
			}

		}
		catch(Exception e){}
		finally{ return isEmptyDirFlag; }
   }


	/**
   * 有过滤器的递归出当前目录下的所有孩子/孩子的孩子/... 过滤器一堆文件后缀名,举例 "*.{txt,rtf}"
   *
   * LRN后遍历目录树:左->右->根 的递归
   *
   * LRN后遍历算法:
   * ergodicLRN(Tree *node){
   *     while( null!=node ){
   *   	   	 ergodicLRN(node's leftChild);
   *		 ergodicLRN(node's rightChild);
   *   		 visit(node);
   *	 }
   * }
   */
   public static boolean ergodicDirByGlob(Path path, String glob){

		boolean isEmptyDirFlag = true; // 是否空文件夹标志

		try( DirectoryStream<Path> dirStream = Files.newDirectoryStream(path) ){
			
			Iterator<Path> ite = dirStream.iterator();
			int iteSize = 0 ;

			int childDirectories = 0 ;
			int childFiles = 0 ;


			while( ite.hasNext() ){
				
				++iteSize;

				isEmptyDirFlag = false;

				Path elementPath = ite.next(); 
				if( Files.isDirectory(elementPath) )
				{
					if( ergodicDirByGlob(elementPath, glob) ) //递归
						++childFiles;
				}
				else if( Files.isRegularFile(elementPath) )
				{
					++childFiles;
				}
			}

			
			if( childFiles == iteSize ){
				dirByGlob(path,glob);
				isEmptyDirFlag = true; //消费过本目录后,这里假定该目录为空,以便给上级目录使用
			}

		}
		catch(Exception e){}
		finally{ return isEmptyDirFlag; }
   }


   
}

    目录树:Files.walkFileTree();

上方的小示例中,利用DirectoryStream自定义实现的目录树的遍历,还是有些繁琐的。虽然也能遍历出文件和目录,但是并没有更多额外的手段来处理遍历每个节点时的附加额外逻辑功能(比如:进入某个目录之前我不想递归下去了;进入某个目访问该目录的直接孩子之后返回时,上层不想继续递归该目录的兄弟节点了;符号链接(/文件夹快捷方式)需不需要在目录树遍历过程中参与进来呢?...)

JDK1.7之后,提供了如何操作目录树的API。依托 Files.walkFileTree()静态方法、FileVisitor接口、FileVisitResult枚举、FileVisitOption枚举 我们可以很容易的构建目录树的处理。Files.walkFileTree()遍历目录树这个强大的API。

遍历目录树提供了两个方法:不难发现,这两个方法其实是一个意思,不过只有两个参数的方法是有四个参数的一个简写形式而已。

/**
 * path 作为目录树的根节点的path
 * fileVisitOptionSet 符号链接节点是否参与遍历树的过程中
 * maxDepth 遍历的深度
 * fileVisitor 访问目录树中的每个节点都会调用此接口
 */

Files.walkFileTree(path, fileVisitOptionSet, maxDepth, fileVisitor);

Files.walkFileTree(path,fileVisitor){

      return walkFileTree(path, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, fileVisitor);

}

目录树的遍历方法,程序中要处理的逻辑主要体现在两个参数上。

Files.walkFileTree(path, fileVisitOptionSet, maxDepth, fileVisitor)方法中的参数说明(绝大多数程序经常只处理fileVisitor即可)
方法的参数 参数的业务含义 参数的用法
fileVisitOptionSet 目录树遍历的过程中是否处理符号链接(/文件夹快捷方式) 取值为EnumSet.of(FileVisitOption.FOLLOW_LINKS)时,表示处理符号链接
取值为EnumSet.noneOf(FileVisitOption.class)时,表示不处理符号链接,这个也是默认值
fileVisitor 是个接口,树的遍历过程中,每个节点都会调用该接口中的方法 FileVisitResult visitFile(T file, BasicFileAttributes attrs);//节点是文件并能读访问时,调用
FileVisitResult visitFileFailed(T file, IOException exc);//节点是文件但无法读访问,调用
FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs);//节点是目录,进入目录之前时,调用
FileVisitResult postVisitDirectory(T dir, IOException exc);//节点是目录,从该目录退出时,调用
FileVisitResult枚举值非常重要,在目录树的遍历过程当中
枚举值 作用
CONTINUE 处理完当前节点后,继续往后处理
SKIP_SUBTREE 处理完当前节点后,不在递归处理它的孩子树
SKIP_SIBLINGS 处理完当前节点后,不在递归处理它的兄弟节点
TERMINATE 处理完当前节点后,整棵树的遍历结束

其实,当我们使用walkFileTree()方法[采用树的前遍历算法]时,本质上就是实现好FileVisitor这个接口,这个接口有个默认实现类SimpleFileVisitor,我们根据自己的业务逻辑需要,继承SimpleFileVisitor类,覆盖相应的方法即可。请看下方的小示例:

import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.*;
import java.util.regex.Pattern;

/**
* 本测试用例所使用的文件目录结构如下:
*
* F:\demo\java\nio\DirectoryStreamTest.java
*
* F:\demo\java\nio\pathexamples\emptydir 空文件夹
* F:\demo\java\nio\pathexamples\test\dindoa\dwei.txt size(2kb)
* F:\demo\java\nio\pathexamples\test\dindoa\fff.rtf size(2kb)
* F:\demo\java\nio\pathexamples\test\emptybak 空文件夹
* F:\demo\java\nio\pathexamples\test\tempcopy.txt size(0)
* F:\demo\java\nio\pathexamples\test\ttt.doc size(12kb)
* F:\demo\java\nio\pathexamples\testmove\tempmove.txt size(1kb)
* F:\demo\java\nio\pathexamples\sss.rtf size(2kb)
* F:\demo\java\nio\pathexamples\student.txt size(12kb)
*
*/

//目录树的遍历算法NLR
class MyVisitor<Path> extends SimpleFileVisitor<Path>
{
	private int countvisitFile = 0;
	private int countpreVisitDirectory = 0;
	private int countpostVisitDirectory = 0;
	private int countvisitFileInvalid = 0;

	public FileVisitResult visitFile(Path path, BasicFileAttributes attr) throws IOException{
		System.out.println("visitFile is :" + (++countvisitFile) + " " + path);
		return FileVisitResult.CONTINUE;
	}

	public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attr) throws IOException{
		System.out.println("preVisitDirectory is :" + (++countpreVisitDirectory) + " " + path);
		return FileVisitResult.CONTINUE;
	}

	public FileVisitResult postVisitDirectory(Path path, IOException exc) throws IOException{
		System.out.println("postVisitDirectory is :" + (++countpostVisitDirectory) + " " + path);
		if (exc != null) throw exc;
		return FileVisitResult.CONTINUE;
	}

	public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException
    {
		System.out.println("visitFileFailed is :" + (++countvisitFileInvalid) + " " + path);
        throw exc;
    }
}


//罗列出目录下的所有文件
class MyVisitorAllFile extends SimpleFileVisitor<Path>
{
	public FileVisitResult visitFile(Path path, BasicFileAttributes attr) throws IOException{
		System.out.println(path);
		return FileVisitResult.CONTINUE;
	}
}


//罗列出目录下的所有文件夹
class MyVisitorAllDirectory extends SimpleFileVisitor<Path>
{
	public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attr) throws IOException{
		System.out.println(path);
		return FileVisitResult.CONTINUE;
	}
}


//罗列出目录下的文件名后缀为.txt和.rtf的所有文件
class MyVisitorQueryByFileExtension extends SimpleFileVisitor<Path>
{
	public FileVisitResult visitFile(Path path, BasicFileAttributes attr) throws IOException{
		String regex = "^.+(\\.txt|\\.rtf)$";
		if ( Pattern.matches(regex, path.getFileName().toString()) )
		{
			System.out.println(path);
		}
		
		return FileVisitResult.CONTINUE;
	}
}


//当我搜索到tempmove.txt文件时就退出目录树
class MyVisitorSearchFile extends SimpleFileVisitor<Path>
{
	public FileVisitResult visitFile(Path path, BasicFileAttributes attr) throws IOException{
		String regex = "^.+(\\.txt|\\.rtf)$";
		if ( Pattern.matches(regex, path.getFileName().toString()) )
		{
			System.out.println(path);
			return FileVisitResult.TERMINATE;
		}
		
		return FileVisitResult.CONTINUE;
	}
}


//当我搜索到dindoa目录时就退出目录树
class MyVisitorSearchDirectory extends MyVisitor<Path>
{
	public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attr) throws IOException{

		if ( "dindoa".equals(path.getFileName().toString()) )
		{
			System.out.println(path);
			return FileVisitResult.TERMINATE;
		}
		
		return FileVisitResult.CONTINUE;
	}
}


//当我处理完dindoa目录时就退出目录树
class MyVisitorDealSomeDirectory extends MyVisitor<Path>
{
	public FileVisitResult postVisitDirectory(Path path, IOException ex) throws IOException{

		if (ex != null) throw ex;

		if ( "dindoa".equals(path.getFileName().toString()) )
		{
			System.out.println(path);
			return FileVisitResult.TERMINATE;
		}
		
		return FileVisitResult.CONTINUE;
	}
}



public class DirectoryStreamByWalkFileTreeTest {

    public static void main(String[] args) throws IOException {

		Path dirPath = Paths.get("pathexamples");

		System.out.println("Files.walkFileTree()遍历树时的算法是NLR前序(根N->左L->右R)::");
		System.out.println("遍历树上的每个节点的逻辑时::");
		System.out.println("1:如果当前节点是文件File且能访问,则执行FileVisitor接口的visitFile()方法");
		System.out.println("2:如果当前节点是文件File但无法访问,则执行FileVisitor接口的visitFileInvalid()方法");
		System.out.println("3:如果当前节点是目录Directory且能访问,则执行FileVisitor接口的preVisitDirectory()方法");
		System.out.println("4:如果当前节点是目录Directory且能访问且其内的所有孩子都访问过后,则执行FileVisitor接口的postVisitDirectory()方法");
		Files.walkFileTree(dirPath,new MyVisitor());

		System.out.println("\r\n无过滤功能,递归显示当前目录dirPath下的所有文件");
		Files.walkFileTree(dirPath,new MyVisitorAllFile());

		System.out.println("\r\n无过滤功能,递归显示当前目录dirPath下的所有文件夹");
		Files.walkFileTree(dirPath,new MyVisitorAllDirectory());

		System.out.println("\r\n有过滤功能,递归显示当前目录dirPath下的后缀名为txt或rtf的所有文件");
		Files.walkFileTree(dirPath,new MyVisitorQueryByFileExtension());

		System.out.println("\r\n有过滤功能,当目录树内搜索到tempmove.txt文件时就退出");
		Files.walkFileTree(dirPath,new MyVisitorSearchFile());

		System.out.println("\r\n有过滤功能,当我搜索到dindoa目录时就退出");
		Files.walkFileTree(dirPath,new MyVisitorSearchDirectory());

		System.out.println("\r\n有过滤功能,当我处理完dindoa目录时就退出");
		Files.walkFileTree(dirPath,new MyVisitorDealSomeDirectory());
   }
}

NIO读写文件示例代码:请参考NIOdemo

猜你喜欢

转载自blog.csdn.net/mmlz00/article/details/85093379