21

近2万字详解JAVA NIO2文件操作,过瘾!

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzA4MTc4NTUxNQ%3D%3D&%3Bmid=2650520838&%3Bidx=1&%3Bsn=18b2be5d757f478cd3c0f7ee08a54c3f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

e6JBVrU.gif

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

从classpath中读取过文件的人,都知道需要写一些读取流的方法,很是繁琐。最近使用IDEA在打出 . 这个符号的时候,一行代码让人激动不已:竟然提供直接读出bytes字节的方法。

byte[] bytes = Test
                .class
                .getResourceAsStream("/test.txt")
                .readAllBytes();

这真是太让人振奋了,再也不用写一些丑陋的,还容易忘记关闭流的代码了。

可惜的是,代码提示给了当头一棒。这需要Java的9版本以上才能支持。这就像一个还有1年寿命的患者看到救命的药,还需要两年才能问世的感觉,是一样的。

v2eaQrR.jpg!web

废话少说,我们用不了这些函数,难道Java7+的也用不了么(有些可怜的宝贝们还真用不了)?话说回来,JAVA7+对NIO进行了增强,主要在对文件操作部分做了大量的改进。它体现在,将File操作进行分离、封装、改进,最终形成Path(Paths)、Files、FileSystem(FileSystems)三个主要类。

我们亲切的叫它NIO2。

其中,Paths、Files中提供了大量便捷的静态操作方法;NIO2还提供了有关文件权限(属性)操作、软连接、文件查找等高级API,使得NIO2具有更全面的文件系统操作接口。

新入行的小鲜肉可能一开始就接触这个了,但对我们一些老程序员来说,突然看到这些东西,就像打开了一个新大陆。所以本文面向的是还不知道这些个操作的菜鸟,以及懒癌晚期的老Java程序员。

1、Path

文件系统都是Tree或者层级结构来组织文件的,任何一个节点可以是一个目录或者一个文件,在NIO2中称为Path,这和原来的File有很多相似之处,只是Path具有更多的表述语义。

1.1、基本属性

以下代码展示了它的基本属性。

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

1.2、路径转换

1)比如“/data/logs/./web.log”、“/data/logs/../db”这种包含“冗余”路径的,有时候我们需要转换为正常路径语句,则使用“Path.normalize()”方法,无比的清爽清洁。

Path path = Paths.get("/data/logs/../web.log");
//输出结果:/data/web.log
System.out.printf("%s%n",path.normalize());

2)如果文件需要被外部资源访问(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());

3)toAbsolutePath():如果路径为相对路径,则转换为绝对路径,对于JAVA程序而言,起始路径为classpath。此方法不会检测文件是否真的存在或者有权限。

4)其中toRealPath()是比较重要的方法,不过它会对文件是否存在、访问权限进行检测,需要捕获异常。首先检测文件是否存在、是否有权限;如果path为相对路径,则将会转换为绝对路径,同“3)”;如果是“符号连接”文件(软连接),则获取其实际target路径(除非指定了NO_FOLLOW_LINKS);如果路径中包含“冗余”,则移除,同1)。这个方法,通常用于对“用户输入的path”进行校验和转换,使用比较多。

5)resolve():路径合并,当前path与参数进行路径合并,append。

6)relativize():获取相对路径,“/data”与“/data/logs/p1”的相对路径为“logs/p1”,反之为“../../”。

2、Files

Files类中提供了大量静态方法,用于实现文件(目录)的创建、复制、迁移、删除以及访问文件数据等操作。

2.1、检测文件或目录

Files.exists(Path)和notExists(Path)两个方法,这两个方法都会实际检测文件或者目录是否存在、以及是否有访问权限。注意:!exist() 与notExists()并不完全相等,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);

有时候,两个不同的path,会指向同一个文件,比如当一个path是软连接时,此时可以使用Files.isSameFile(p1,p2)来检测,当然你可以通过Path + LinkOption相关组合获取target实际path来比较。

2.2、删除

delete和deleteIfExists两个方法均可删除文件,前者尝试删除的文件如果不存在则会抛出异常。如果文件是软连接,则只删除连接文件而不会删除target文件,如果path为目录,则目录需要为空,否则删除失败(IOException)。在删除操作之前,最后做一些常规的检测,比如文件是否存在(有权限)、目录是否为空等。稍后我们再介绍“递归删除目录树和文件”。

2.3、文件(目录)复制

copy(Path,Path,CopyOption…)方法可以复制文件,不过,需要注意CopyOption的相关选项。

当copy一个软连接文件时,默认将会复制target文件,如果只想复制软连接文件而不是target内容,可以指定NOFOLLOW_LINKS选项。CopyOption的实现类为StandardCopyOption,此外CopyOption也扩展了LinkOption,即包含NOFOLLOW_LINKS选项。如下为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不支持。

Path source = Paths.get("/data/logs/web.log");
Path target = Paths.get("/data/logs/web.log.copy");
Files.copy(source,target,REPLACE_EXISTING,COPY_ATTRIBUTES,NOFOLLOW_LINKS);

2.4、移动

move(Path,Path,CopyIOption),基本原则同copy。需要注意,如果是目录,目录中包含文件时也可以移动的(这可能依赖于平台),子目录也一起移动,但是目标目录必须为空(DirectoryNotEmptyException)。基本语义同“mv -rf”,目标目录不需要提前创建,move结束后,源目录将不存在。支持两种选项:

1) REPLACE_EXISTING :如果目标文件已存在,则覆盖;如果目标文件是软连接,则连接文件被覆盖但是其指向不会受影响。

2) ATOMIC_MOVE :原子复制,需要平台的文件系统支持(不支持则抛出异常),指定此参数时其他选项将被忽略;如果文件不能被原子复制(或者替换),则会抛出AtomicMoveNotSupportedException。

2.5、打开文件

Files类中提供了多个静态的方法,用于直接读写文件。如下为文件打开的几个选项参数(StandardOpenOptions):

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 :对文件内容的修改,会同步到底层存储。

对于一些小文件(M级别),通常我们希望一次全部读取所有内容,而不再使用传统的方式迭代读取。

//全部读取小文件中的数据
//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) {
    //
}

此外Files中还提供了基于buffer的channel操作,返回类型为SeekableByteChannel,这种操作通常适用于读或者写,以及数据可以基于buffer进行拆封包,其他特性类似“随机访问文件”。(Files.newByteChannel(),功能同File.open())

Path path = Paths.get("/data/web.log");
//创建一个普通文件,如果已存在则抛出异常,文件属性为默认。Files.createFile(path);

//创建临时文件,临时文件的目录和前缀可以为null,后缀如果为null时默认使用".tmp";
Files.createTempFile(null,null,".tmp");
//创建一个软连接文件
Files.createSymbolicLink(Paths.get("/data/link.log"),Paths.get("/data/web.log"));

2.6、Metadata管理

2.6.1、BasicFileAttributes

BasicFileAttributes基本接口,提供了一些基本的文件metadata,比如lastAccessTime、lastModifiedTime等,它的实现类因平台而已有:DosFileAttributes、PosixFileAttribute、UnixFileAttribute等;不同平台所能支持的属性有所不同。(在跨平台场景下,你可能需要使用FileStore来判断当前文件系统是否支持相应的FileAttributeView)

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);

属性名格式为“view-name:attribute-name”,比如“dos:hidden”;其中合法的view-name目前有“basic”、“posix”、“unix”、“owner”(所有者信息,权限),属性的列表需要根据自己的平台对应相应的Attributes类,否则会导致设置异常。

2.6.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);

2.6.3、UserDefinedFileAttributeView

用户自定义文件属性(即扩展文件属性),以及属性值的长度,这取决于底层文件系统或者平台是否支持。目前,主流的平台和文件系统都支持扩展文件属性,比如FreeBSD(ZFS)、Linux(ext3、ext4、ZFS)、MacOS等。默认情况下,此操作是开启的,如果已关闭,可以通过“sudo mount -o remount,user_xattr {你的文件系统挂载路径}”,否则也会抛出UnsupportedOperationException。

Path path = Paths.get("/data/logs/web.log");
UserDefinedFileAttributeView view = Files.getFileAttributeView(path,UserDefinedFileAttributeView.class);
String name = "user.mimetype";
int size = view.size(name);//我个人认为JDK应该直接支持获取属性值,而不是再周折一番
ByteBuffer buffer = ByteBuffer.allocate(size);
view.read(name,buffer);
buffer.flip();
String value = Charset.defaultCharset().decode(buffer).toString();
System.out.println(value);
//其他操作,比如list获取所有属性的列表等。//写入或者跟新自定义属性
view.write(name,Charset.defaultCharset().encode("text/html"));

3、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);

可以获取每个FileStore支持的FileAttributeView,此后即可通过Files.getFileAttributeView()来获取相应的视图类。每个view都有简写名称,比如BasicFileAttributeView.name()返回其简写名称为“basic”。目前JDK支持很多FileAttributeView,比如BasicFileAttributeView、UnixFileAttributeView等等,以及允许自定义的UserDefinedFileAttributeView等。JDK将获取文件属性、修改文件属性的操作全部基于对应的FileAttributeView类来实现。

PosixFileAttributeView view = Files.getFileAttributeView(path,PosixFileAttributeView.class);
PosixFileAttributes fileAttributes = view.readAttributes();

此外,根据FileStore的底层存储不同,有多种实现,可以参看FileStore的实现类,比如UnixFileStore、BsfFileStore(Mac)等,每个FileStore所能支持的attribute也不太相同,需要根据其对应的实现类获取,也可以使用FileStore.getAttribute()来获取,但是属性名需要与类中支持的属性名对应。

4、目录操作

JAVA中目录也用Path表示,其基本属性与File一样。

4.1、列举根目录

//遍历文件系统的所有根目录
Iterable<Path> roots = FileSystems.getDefault().getRootDirectories();
for(Path root : roots) {
    System.out.print(root);
}

4.2、创建、删除

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);

4.3、遍历目录

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();

4.4、Glob文件过滤器

NIO2中新增支持了基于Glob的文件过滤器,一种类似于正则表达式的匹配语法;glob是来自unlix(shell指令)用于文件匹配的表达式,很多主流语言和平台(dos、window)都支持,不过不同语言对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和正则表达式。稍后介绍如何使用PathMatcher来遍历文件或者目录树。

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();

4.5、操作链接文件

硬连接(或者连接):

1)文件有相同的 inode 及 data block;

2)只能对已存在的文件进行创建;

3)不能交叉文件系统进行硬链接的创建;

4)不能对目录进行创建,只可对文件创建;

5)删除一个硬链接文件并不影响其他有相同 inode 号的文件。

软连接(符号连接):软链接与硬链接不同,若文件用户数据块中存放的内容是另一文件的路径名的指向,则该文件就是软连接。软链接就是一个普通文件,只是数据块内容有点特殊。软链接有着自己的 inode 号以及用户数据块。因此软链接的创建与使用没有类似硬链接的诸多限制:

1)软链接有自己的文件属性及权限等;

2)可对不存在的文件或目录创建软链接;

3)软链接可交叉文件系统;

4)软链接可对文件或目录创建;

5)创建软链接时,链接计数 i_nlink 不会增加;

6)删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接被称为死链接(即 dangling link,若被指向路径文件被重新创建,死链接可恢复为正常的软链接)。

##创建连接
ln 目标 连接名
## 创建软连接
ln \-s 目标 连接名

##查看软连接目标指向,对于硬连接是不显示。ls \-l 软连接文件

##通过stat指令可以查看软硬连接的inode和block信息
##发现硬连接与目标文件的信息完全一致。##软连接文件有单独的inode和block。

创建连接

Path target = Paths.get("/data/test.log");
//target必须存在
Files.createLink(Paths.get("/data/hard\-link.log"),target);
Files.createSymbolicLink(Paths.get("/data/soft\-link.log"),target);

//检测文件是否为软连接问题
boolean isSymbolicLink = Files.isSymbolicLink(Paths.get("/data/soft\-link.log"));

Path target2 = Files.readSymbolicLink(Paths.get("/data/soft\-link.log"));
//检测是否为同一个文件
System.out.println(Files.isSameFile(target,target2));//true
System.out.println(Files.isSameFile(target,Paths.get("/data/hard\-link.log")));//true
System.out.println(Files.isSameFile(target,Paths.get("/data/soft\-link.log")));//true

通过Files.isSameFile()比较,我们会发现,无论是软连接、硬链接,都与目标文件是同一个文件。

4.6、查找文件

前文中介绍了有关PathMatcher,在JAVA NIO2中用于匹配文件的表达式,可以支持glob和正则表达式(regex)两种方式。其中glob的语法更接近linux shell,regex是更广泛、更丰富的一种方式。比如文件:/data/test.log

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));

表达式规则:syntax:pattern,其中合法的syntax为“glob”和“regex”;需要注意这两种表达式的区别。内部实现也比较简单,对于glob字符串将会转化为正则表达式字符串,然后统一使用正则匹配。

4.7、递归遍历目录树

曾经,使用JAVA遍历文件数是一件比较繁琐的事情,在NIO2中增加了原生提供了此操作。主要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()不会被执行。

End

好了,关于NIO2新增的API,就已经介绍完毕了。

是不是松了口气?

:)感谢牛草儿的投稿。

近期热门文章

996的乐趣,你是无法想象的

魔幻现实主义,关爱神经衰弱

一切荒诞的傲慢,皆来源于认知

不要被标题给骗了,画面感十足的消遣文章

《必看!java后端,亮剑诛仙》

后端技术索引,中肯火爆。全网转载上百次。

《学完这100多技术,能当架构师么?(非广告)》

精准点评100多框架,帮你选型

作者简介: 小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。

EnUneen.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK