Path(Paths)、Files、FileSystem(FileSystems)三个主要类。
Paths、Files中提供了大量便捷的静态操作方法
Path
基本方法
Path path = Paths.get("/data/logs/web.log");
//属性
//获取路径中的文件名或者最后一个节点元素
System.out.printf("FileName:%s%n", path.getFileName());
//路径节点元素的格式
System.out.printf("NameCount:%s%n", path.getNameCount());
//遍历路径节点方法1
Iterator<Path> names = path.iterator();
int i = 0;
while (names.hasNext()) {
Path name = names.next();
System.out.printf("Name %s:%s%n",i,name.toString());
i++;
}
//方法2
for(int j = 0; j < path.getNameCount(); j++) {
System.out.printf("Name %s:%s%n",j,path.getName(j));
}
//父路径
System.out.printf("Parent:%s%n",path.getParent());
//跟路径,比如"/"、"C:";如果是相对路径,则返回null。System.out.printf("Root:%s%n",path.getRoot());
//子路径,结果中不包含root,前开后闭
System.out.printf("Subpath[0,2]:%s%n",path.subpath(0,2));
结果:
FileName:web.log
NameCount:3
Name 0:data
Name 1:logs
Name 2:web.log
Name 0:data
Name 1:logs
Name 2:web.log
Parent:/data/logs
Root:/
Subpath[0,2]:data/logs
路径转换
比如“/data/logs/./web.log”、“/data/logs/../db”这种包含“冗余”路径的
Path path = Paths.get("/data/logs/../web.log");
//输出结果:/data/web.log
System.out.printf("%s%n",path.normalize());
如果文件需要被外部资源访问(resource),你可以通过Path.toUri()来转换,path对应的文件或者目录可以不存在,此方法不会check异常:
Path path = Paths.get("/data/logs/web.log");
//表示本地文件的uri,输出结果:file:///data/logs/web.log
System.out.printf("%s%n",path.toUri());
toRealPath()是比较重要的方法,不过它会对文件是否存在、访问权限进行检测,需要捕获异常。首先检测文件是否存在、是否有权限;如果path为相对路径,则将会转换为绝对路径
resolve():路径合并,当前path与参数进行路径合并
relativize():获取相对路径,“/data”与“/data/logs/p1”的相对路径为“logs/p1”
Files
Files类中提供了大量静态方法,用于实现文件(目录)的创建、复制、迁移、删除以及访问文件数据等操作。
监测文件目录
Files.exists(Path)和notExists(Path)两个方法,这两个方法都会实际检测文件或者目录是否存在、以及是否有访问权限
exist可能有三种状态:如果不存在或者安全校验不通过则返回false,如果返回true则表示文件确实存在且有权限
notExists()检测类似,对于没有通过安全校验的也会返回false
当exists与notExists同时返回false时,说明文件不可以验证(即无权限)
判断文件(目录)具有读、写、执行的权限,可以通过如下方法:
Path path = Paths.get("data/logs/web.log");
boolean isRegularExecutableFile = Files.isRegularFile(path) & Files.isReadable(path) & Files.isExecutable(path);
删除
delete和deleteIfExists两个方法均可删除文件,前者尝试删除的文件如果不存在则会抛出异常
如果path为目录,则目录需要为空,否则删除失败(IOException)
在删除操作之前,最后做一些常规的检测,比如文件是否存在(有权限)、目录是否为空等
文件(目录)复制
copy(Path,Path,CopyOption…)方法可以复制文件
如下为CopyOption选项列表:
1)REPLACE_EXISTING:如果目标文件已经存在,则直接覆盖;如果目标文件是个软连接,则软连接文件本身被覆盖(而非连接文件的target文件);如果复制的是目录,且目标目录不为空时,则会抛出异常(DirectoryNotEmptyException),稍后介绍“递归复制目录树和文件”。此参数通常必选。复制目录时,目标目录会自动创建,源目录中如果有文件,则不会复制文件,只会创建空的目标目录。source和target,要么同时是目录、要么同时是文件。
2)COPY_ATTRIBUTES:复制文件时,也同时复制目标文件的属性(metadata),对于文件属性(FileAttribute)的支持依赖于文件系统(和平台),不过lastModifiedTime通常会被复制。
3)NOFOLLOW_LINKS:继承自LinkOption,表示如果文件是软连接,则不followed,即只复制连接文件,不复制其target实际文件内容。
4)ATOMIC_MOVE:只支持move操作,copy不支持。
移动
move(Path,Path,CopyIOption),基本原则同copy
如果是目录,目录中包含文件时也可以移动的(这可能依赖于平台),子目录也一起移动,但是目标目录必须为空(DirectoryNotEmptyException)
支持两种选项:
1)REPLACE_EXISTING:如果目标文件已存在,则覆盖;如果目标文件是软连接,则连接文件被覆盖但是其指向不会受影响。
2)ATOMIC_MOVE:原子复制,需要平台的文件系统支持(不支持则抛出异常),指定此参数时其他选项将被忽略;如果文件不能被原子复制(或者替换),则会抛出AtomicMoveNotSupportedException。
打开文件
Files类中提供了多个静态的方法,用于直接读写文件
1)WRITE: 打开文件用于write访问。
2)APPEND:在文件尾部追加数据,伴随用于WRITE或CREATE选项。
3)TRUNCATE_EXISTING:将文件truncate为空,伴随用于WRITE选项。比如,文件存在时,将文件数据清空并重新写入。
4)CREATE_NEW:创建新文件,如果文件已存在则抛出异常。
5)CREATE:如果文件已存在则直接打开,否则创建文件。
6)DELETE_ON_CLOSE:当文件操作关闭时则删除文件(close方法或者JVM关闭时),此选项适用于临时文件(临时文件不应该被其他进程并发访问)。
7)SPARSE:创建一个“稀疏”文件,伴随使用CREATE_NEW,适用于某些特殊的文件系统比如NTFS,这些大文件允许出现“gaps”(空洞)在某些情况下可以提高性能且这些gaps不消耗磁盘空间。
8)SYNC:对文件内容(data)或者metadata的修改,都会同步到底层存储。
9)DSYNC:对文件内容的修改,会同步到底层存储。
//全部读取小文件中的数据
//Files.readAllBytes(Paths.get("/data/web.log"));
List<String> lines = Files.readAllLines(Paths.get("/data/web.log"),Charset.forName("utf-8"));
//将准备好的数据,直接全部写入文件。(打开、写入)
Files.write(Paths.get("/data/web-1.log"),lines,Charset.forName("utf-8"),
StandardOpenOption.APPEND,
StandardOpenOption.CREATE));
//传统操作
try (BufferedReader reader = Files.newBufferedReader(Paths.get("/data/web.log"))) {
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
System.out.println(line);
}
} catch (IOException e) {
//
}
//传统操作
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("/data/web.log"),Charset.forName("utf-8"),StandardOpenOption.APPEND,
StandardOpenOption.CREATE)) {
for(String line : lines) {
writer.write(line);
}
} catch (IOException e) {
//
}
Metadata管理
1、BasicFileAttributes
BasicFileAttributes基本接口,提供了一些基本的文件metadata,比如lastAccessTime、lastModifiedTime等,它的实现类因平台而已有:DosFileAttributes、PosixFileAttribute、UnixFileAttribute等
Path path = Paths.get("/data/logs/web.log");
BasicFileAttributes attributes = Files.readAttributes(path,BasicFileAttributes.class);
System.out.println("regular file:" + attributes.isRegularFile());
System.out.println("directory:" + attributes.isDirectory());
System.out.println("symbolic link:" + attributes.isSymbolicLink());
System.out.println("modified time:" + attributes.lastModifiedTime().toMillis());
//修改系统更新属性
Files.setLastModifiedTime(path,FileTime.fromMillis(System.currentTimeMillis()));
//修改其他属性
Files.setAttribute(path,"dos:hidden",true);
2、PosixFileAttributes
Path path = Paths.get("/data/logs/web.log");
PosixFileAttributes attributes = Files.readAttributes(path,PosixFileAttributes.class);
//用户组和权限
UserPrincipal userPrincipal = attributes.owner();
System.out.println(userPrincipal.toString());
GroupPrincipal groupPrincipal = attributes.group();
System.out.println(groupPrincipal.toString());
Set<PosixFilePermission> permissions = attributes.permissions();
//将权限转换为文件属性,用于创建新的文件,目前文件权限也是一种属性
FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions.asFileAttribute(permissions);
Files.createFile(Paths.get("/data/test.log"),fileAttribute);
//修改文件权限,可以在permissions中增减权限列表,枚举
Files.setPosixFilePermissions(path,permissions);
构建权限列表:
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("-rw-r--r--");
Files.setPosixFilePermissions(path, permissions);
修改文件目录的所有者或所在组
Path path = Paths.get("/data/logs/web.log");
//首先找到系统中的其他用户,根据用户名
UserPrincipal userPrincipal = path.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByName("userName");
Files.setOwner(path,userPrincipal);
//或者
Files.getFileAttributeView(path,FileOwnerAttributeView.class).setOwner(userPrincipal);
//修改group
GroupPrincipal groupPrincipal = path.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByGroupName("groupName");
Files.getFileAttributeView(path,PosixFileAttributeView.class).setGroup(groupPrincipal);
FileSystem
FileStore是新增的API,用于描述底层存储系统,一个平台有多个FileStore,我们可以通过FileSystem获取FileStore的列表,以及每个store的存储状态、文件列表等
Path path = Paths.get("/data/logs/web.log");
path.getFileSystem().getFileStores();//获取文件所属的文件系统的所有的存储器。//当前文件系统所能支持的FileAttributeView,此后可以对文件使用相应的view获取或者修改属性
Set<String> viewNames = FileSystems.getDefault().supportedFileAttributeViews();
System.out.println(viewNames);//basic,unix,posix,owner,dos等
//或者,全局
//遍历所有的磁盘存储
Iterable<FileStore> fileStores = FileSystems.getDefault().getFileStores();//获取默认文件系统的所有存储器
for(FileStore store : fileStores) {
System.out.println("\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-");
System.out.println("className:" + store.getClass().getName());
System.out.println("name:" + store.name());//磁盘名称
System.out.println("type:" + store.type());//类型
System.out.println("readOnly:" + store.isReadOnly());//是否为只读
System.out.println("usableSpace:" + store.getUsableSpace() + "/" + store.getTotalSpace());
boolean supported = store.supportsFileAttributeView(BasicFileAttributeView.class);
//或者
//boolean supported = store.supportsFileAttributeView("basic");
//fileStore的属性,不同于FileAttributes,这些属性应该与FileStore的各个实现类对应。Long totalSpace = (Long)store.getAttribute("totalSpace");
System.out.println(totalSpace);
目录操作
列举根目录
//遍历文件系统的所有根目录
Iterable<Path> roots = FileSystems.getDefault().getRootDirectories();
for(Path root : roots) {
System.out.print(root);
}
创建、删除
Path dir = Paths.get("/data/xyz");
Files.createDirectories(dir);
Files.createDirectory(dir);
其中createDirectory()方法是一个“严格校验”的方法,如果父路径不存在则会抛出异常,如果路径已经存在或者同名文件存在则会抛出异常,简单来说此方法只能创建最后一级目录(且此前不存在)。对于createDirectories()方法,比较兼容,会逐层校验并创建父路径,如果不存在则创建。
创建目录时,也可以像文件那样,指定文件属性(包括权限)
Path dir = Paths.get("/data/xyz/12x/123x");
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("\-rw\-rw\-\-\-\-");
FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions.asFileAttribute(permissions);
Files.createDirectories(dir,fileAttribute);
遍历目录
Path dir = Paths.get("/data");
DirectoryStream<Path> stream = Files.newDirectoryStream(dir);
for (Path path : stream) {
System.out.println(path);
}
stream.close();
stream方式会遍历指定目录下的所有文件,包括文件。目录。连接、隐藏文件或者目录等。
此外,JDK还支持基于filter模式,在遍历时过滤“非必要”的文件或者目录。
Path dir = Paths.get("/data");
DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
@Override
public boolean accept(Path entry) throws IOException {
return Files.isRegularFile(entry);
}
};
DirectoryStream<Path> stream = Files.newDirectoryStream(dir,filter);
for (Path path : stream) {
System.out.println(path);
}
stream.close();
Glob文件过滤器
NIO2中新增支持了基于Glob的文件过滤器,一种类似于正则表达式的匹配语法
1、*:匹配任意多个任意字符,包括空字符(none)。比如“/data/*.log”匹配“data”目录下所有以“.log”结尾的文件。
2、**:类似于*,匹配任意多个字符,不过它可以越过目录分割符,此语法通常用于匹配全路径。比如:“/data/**”
3、?:只匹配一个字符。
4、\:转义符,用于转义特殊字符,比如“\”、“-”、“{”、“}”、“[”等等。比如需要匹配“{”那么其字面表达式为“\{”,“\\”用于匹配单斜杠。
5、!:非,不包含,通常配合[]使用。(貌似不支持^)
6、{}:指定一组子规则,比如:
1){sum,moon,stars}:匹配“sun”或者“moon”或者“starts”(其一即可),子规则使用“,”分割。
2){temp*,tmp*}:匹配以temp或者tmp开头的所有字符串。
7、[]:匹配一组字符串中的单个字符,如果字符串集中包含“-”则匹配区间中单个字符。比如[abc]匹配“a”或者“b”或者“c”,[a-z]匹配a到z小写字符中的一个,[0-9]匹配0~9任意一个数字。可以混合使用,比如[abce-g]匹配“a”、“b”、“c”、“e”、“f”、“g”,[!a-c]匹配除a、b、c之外的任意一个字符。复合子规则使用“,”分割,比如[a-z,0-9]匹配a~z或者0~9任意一个字符。
在[]中,“*”、“?”、“\”只匹配其自己(字面),如果“-”在[]内且是第一个字符或者在!之后,也匹配自己。
8、文件中的前导字符、“.”将作为普通字符对待。比如“*”可以匹配“.tmp”这个文件,FIles.isHidden()可以用来检测此文件为隐藏文件。
9、其他所有字符匹配其自己(取决于因平台而已的实现方式,包括路径分隔符等)。
示例:
1、*.html匹配任意“.html”结尾的文件。
2、???:匹配任意三个字符(包括数字字符)
3、*[0-9]*:匹配包含一个数字的任意多个字符。
4、*.{htm,html},匹配任意以“html”或者“htm”结尾的字符串。
GLobbing表达式,一种比较便捷的过滤策略,对于一些简单的操作(主要是只根据文件或者路径名特性匹配时),可以不使用Filter的情况下完成,当然glob的内部实现仍然是基于封装的Filter来实现(PathMatcher);但是glob语法相对简单,JDK NIO2有关文件过滤表达式,可以同时支持glob和正则表达式
Path dir = Paths.get("/data");
//内部,默认会对glob表达式增加前缀,glob,为了兼容PathMatcher
DirectoryStream<Path> stream = Files.newDirectoryStream(dir,"\*.txt");
for (Path path : stream) {
System.out.println(path);
}
stream.close();
查找文件
Path path = Paths.get("/data/test.log");
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:\*\*.log");
System.out.println(pathMatcher.matches(path));
//基于正则
pathMatcher = FileSystems.getDefault().getPathMatcher("regex:.\*\\\\.log");
System.out.println(pathMatcher.matches(path));
递归遍历目录树
主要API为FileVisitor,其简单实现类为SimpleFileVisitor:
1、preVisitDirectory:在浏览目录之前。前置操作。比如遍历并复制文件时,可以在进入目录之前,创建迁移的目标新目录。
2、postVisitDirectory:在浏览目录中所有文件之后(浏览其他目录之前)。后置操作。
3、visitFile:浏览文件,Path和BaseFileAttributes会传递入方法。
4、visitFileFailed:浏览文件失败时调用,比如文件属性无法获取、目录无法打开等异常时,调用此方法,同时传入Path和Exception。
简单的遍历(查找、筛选匹配)
Path dir = Paths.get("/data/redis");
Stream<Path> stream = Files.walk(dir);
stream.forEach(path \-> {
System.out.println(path);
});
stream.close();
复杂遍历(遍历查找、文件迁移校验)
public static void main(String\[\] args) throws Exception{
Path dir = Paths.get("/data/redis");
Files.walkFileTree(dir,new Finder());
}
public static class Finder implements FileVisitor<Path> {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("preVisitDirectory:" + dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("visitFile:" + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
System.out.println("visitFileFailed:" + file + ",exception:" + (exc != null ? exc.getMessage() : "\-"));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
System.out.println("postVisitDirectory:" + dir);
return FileVisitResult.CONTINUE;
}
}
FileVisitResult用于表示执行状态:
1)CONTINUE:表示继续执行(继续下一步操作)
2)TERMINATE:终止递归遍历,其他的后续方法不会被执行,尚未浏览的文件也将不会被访问。
3)SKIP_SUBTREE:跳过子树,即当前目录以及其子目录都将被跳过。适用在preVisitDirectory(),其他方法返回此值则等效于CONTINUE。
4)SKIP_SIBLINGS:跳过此文件(或者目录)的同级文件或者文件,适用在postVisitDirectory(),如果preVisitDirectory返回此值,则当前目录也会跳过,且postVisitDirectory()不会被执行。