All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.hutool.core.io.file.PathUtil Maven / Gradle / Ivy

Go to download

Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。

There is a newer version: 5.8.34
Show newest version
package cn.hutool.core.io.file;

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.visitor.CopyVisitor;
import cn.hutool.core.io.file.visitor.DelVisitor;
import cn.hutool.core.io.resource.FileResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharsetUtil;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

/**
 * NIO中Path对象操作封装
 *
 * @author looly
 * @since 5.4.1
 */
public class PathUtil {
	/**
	 * 目录是否为空
	 *
	 * @param dirPath 目录
	 * @return 是否为空
	 * @throws IORuntimeException IOException
	 */
	public static boolean isDirEmpty(Path dirPath) {
		try (DirectoryStream dirStream = Files.newDirectoryStream(dirPath)) {
			return false == dirStream.iterator().hasNext();
		} catch (IOException e) {
			throw new IORuntimeException(e);
		}
	}

	/**
	 * 递归遍历目录以及子目录中的所有文件
* 如果提供path为文件,直接返回过滤结果 * * @param path 当前遍历文件或目录 * @param fileFilter 文件过滤规则对象,选择要保留的文件,只对文件有效,不过滤目录,null表示接收全部文件 * @return 文件列表 * @since 5.4.1 */ public static List loopFiles(Path path, FileFilter fileFilter) { return loopFiles(path, -1, fileFilter); } /** * 递归遍历目录以及子目录中的所有文件
* 如果提供path为文件,直接返回过滤结果 * * @param path 当前遍历文件或目录 * @param maxDepth 遍历最大深度,-1表示遍历到没有目录为止 * @param fileFilter 文件过滤规则对象,选择要保留的文件,只对文件有效,不过滤目录,null表示接收全部文件 * @return 文件列表 * @since 5.4.1 */ public static List loopFiles(Path path, int maxDepth, FileFilter fileFilter) { return loopFiles(path, maxDepth, false, fileFilter); } /** * 递归遍历目录以及子目录中的所有文件
* 如果提供path为文件,直接返回过滤结果 * * @param path 当前遍历文件或目录 * @param maxDepth 遍历最大深度,-1表示遍历到没有目录为止 * @param isFollowLinks 是否跟踪软链(快捷方式) * @param fileFilter 文件过滤规则对象,选择要保留的文件,只对文件有效,不过滤目录,null表示接收全部文件 * @return 文件列表 * @since 5.4.1 */ public static List loopFiles(final Path path, final int maxDepth, final boolean isFollowLinks, final FileFilter fileFilter) { final List fileList = new ArrayList<>(); if (!exists(path, isFollowLinks)) { return fileList; } else if (!isDirectory(path, isFollowLinks)) { final File file = path.toFile(); if (null == fileFilter || fileFilter.accept(file)) { fileList.add(file); } return fileList; } walkFiles(path, maxDepth, isFollowLinks, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) { final File file = path.toFile(); if (null == fileFilter || fileFilter.accept(file)) { fileList.add(file); } return FileVisitResult.CONTINUE; } }); return fileList; } /** * 遍历指定path下的文件并做处理 * * @param start 起始路径,必须为目录 * @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作 * @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor) * @since 5.5.2 */ public static void walkFiles(Path start, FileVisitor visitor) { walkFiles(start, -1, visitor); } /** * 遍历指定path下的文件并做处理 * * @param start 起始路径,必须为目录 * @param maxDepth 最大遍历深度,-1表示不限制深度 * @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作 * @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor) * @since 4.6.3 */ public static void walkFiles(Path start, int maxDepth, FileVisitor visitor) { walkFiles(start, maxDepth, false, visitor); } /** * 遍历指定path下的文件并做处理 * * @param start 起始路径,必须为目录 * @param maxDepth 最大遍历深度,-1表示不限制深度 * @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作 * @param isFollowLinks 是否追踪到软链对应的真实地址 * @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor) * @since 5.8.23 */ public static void walkFiles(final Path start, int maxDepth, final boolean isFollowLinks, final FileVisitor visitor) { if (maxDepth < 0) { // < 0 表示遍历到最底层 maxDepth = Integer.MAX_VALUE; } try { Files.walkFileTree(start, getFileVisitOption(isFollowLinks), maxDepth, visitor); } catch (final IOException e) { throw new IORuntimeException(e); } } /** * 删除文件或者文件夹,不追踪软链
* 注意:删除文件夹时不会判断文件夹是否为空,如果不空则递归删除子文件或文件夹
* 某个文件删除失败会终止删除操作 * * @param path 文件对象 * @return 成功与否 * @throws IORuntimeException IO异常 * @since 4.4.2 */ public static boolean del(Path path) throws IORuntimeException { if (Files.notExists(path)) { return true; } try { if (isDirectory(path)) { Files.walkFileTree(path, DelVisitor.INSTANCE); } else { delFile(path); } } catch (IOException e) { throw new IORuntimeException(e); } return true; } /** * 通过JDK7+的 {@link Files#copy(InputStream, Path, CopyOption...)} 方法拷贝文件 * * @param src 源文件流 * @param target 目标文件或目录,如果为目录使用与源文件相同的文件名 * @param options {@link StandardCopyOption} * @return 目标Path * @throws IORuntimeException IO异常 * @since 5.8.27 */ public static Path copyFile(Resource src, Path target, CopyOption... options) throws IORuntimeException { Assert.notNull(src, "Source is null !"); if(src instanceof FileResource){ return copyFile(((FileResource) src).getFile().toPath(), target, options); } try(InputStream stream = src.getStream()){ return copyFile(stream, target, options); } catch (IOException e) { throw new RuntimeException(e); } } /** * 通过JDK7+的 {@link Files#copy(InputStream, Path, CopyOption...)} 方法拷贝文件 * * @param src 源文件流,使用后不闭流 * @param target 目标文件或目录,如果为目录使用与源文件相同的文件名 * @param options {@link StandardCopyOption} * @return 目标Path * @throws IORuntimeException IO异常 * @since 5.8.27 */ public static Path copyFile(InputStream src, Path target, CopyOption... options) throws IORuntimeException { Assert.notNull(src, "Source is null !"); Assert.notNull(target, "Destination File or directory is null !"); // 创建级联父目录 mkParentDirs(target); try { Files.copy(src, target, options); } catch (IOException e) { throw new IORuntimeException(e); } return target; } /** * 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件
* 此方法不支持递归拷贝目录,如果src传入是目录,只会在目标目录中创建空目录 * * @param src 源文件路径,如果为目录只在目标中创建新目录 * @param dest 目标文件或目录,如果为目录使用与源文件相同的文件名 * @param options {@link StandardCopyOption} * @return 目标Path * @throws IORuntimeException IO异常 */ public static Path copyFile(Path src, Path dest, StandardCopyOption... options) throws IORuntimeException { return copyFile(src, dest, (CopyOption[]) options); } /** * 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件
* 此方法不支持递归拷贝目录,如果src传入是目录,只会在目标目录中创建空目录 * * @param src 源文件路径,如果为目录只在目标中创建新目录 * @param target 目标文件或目录,如果为目录使用与源文件相同的文件名 * @param options {@link StandardCopyOption} * @return 目标Path * @throws IORuntimeException IO异常 * @since 5.4.1 */ public static Path copyFile(Path src, Path target, CopyOption... options) throws IORuntimeException { Assert.notNull(src, "Source File is null !"); Assert.notNull(target, "Destination File or directory is null !"); final Path targetPath = isDirectory(target) ? target.resolve(src.getFileName()) : target; // 创建级联父目录 mkParentDirs(targetPath); try { return Files.copy(src, targetPath, options); } catch (IOException e) { throw new IORuntimeException(e); } } /** * 拷贝文件或目录,拷贝规则为: * *
    *
  • 源文件为目录,目标也为目录或不存在,则拷贝整个目录到目标目录下
  • *
  • 源文件为文件,目标为目录或不存在,则拷贝文件到目标目录下
  • *
  • 源文件为文件,目标也为文件,则在{@link StandardCopyOption#REPLACE_EXISTING}情况下覆盖之
  • *
* * @param src 源文件路径,如果为目录会在目标中创建新目录 * @param target 目标文件或目录,如果为目录使用与源文件相同的文件名 * @param options {@link StandardCopyOption} * @return Path * @throws IORuntimeException IO异常 * @since 5.5.1 */ public static Path copy(Path src, Path target, CopyOption... options) throws IORuntimeException { Assert.notNull(src, "Src path must be not null !"); Assert.notNull(target, "Target path must be not null !"); if (isDirectory(src)) { return copyContent(src, target.resolve(src.getFileName()), options); } return copyFile(src, target, options); } /** * 拷贝目录下的所有文件或目录到目标目录中,此方法不支持文件对文件的拷贝。 *
    *
  • 源文件为目录,目标也为目录或不存在,则拷贝目录下所有文件和目录到目标目录下
  • *
  • 源文件为文件,目标为目录或不存在,则拷贝文件到目标目录下
  • *
* * @param src 源文件路径,如果为目录只在目标中创建新目录 * @param target 目标目录,如果为目录使用与源文件相同的文件名 * @param options {@link StandardCopyOption} * @return Path * @throws IORuntimeException IO异常 * @since 5.5.1 */ public static Path copyContent(Path src, Path target, CopyOption... options) throws IORuntimeException { Assert.notNull(src, "Src path must be not null !"); Assert.notNull(target, "Target path must be not null !"); try { Files.walkFileTree(src, new CopyVisitor(src, target, options)); } catch (IOException e) { throw new IORuntimeException(e); } return target; } /** * 判断是否为目录,如果file为null,则返回false
* 此方法不会追踪到软链对应的真实地址,即软链被当作文件 * * @param path {@link Path} * @return 如果为目录true * @since 5.5.1 */ public static boolean isDirectory(Path path) { return isDirectory(path, false); } /** * 判断是否为目录,如果file为null,则返回false * * @param path {@link Path} * @param isFollowLinks 是否追踪到软链对应的真实地址 * @return 如果为目录true * @since 3.1.0 */ public static boolean isDirectory(Path path, boolean isFollowLinks) { if (null == path) { return false; } return Files.isDirectory(path, getLinkOptions(isFollowLinks)); } /** * 获取指定位置的子路径部分,支持负数,例如index为-1表示从后数第一个节点位置 * * @param path 路径 * @param index 路径节点位置,支持负数(负数从后向前计数) * @return 获取的子路径 * @since 3.1.2 */ public static Path getPathEle(Path path, int index) { return subPath(path, index, index == -1 ? path.getNameCount() : index + 1); } /** * 获取指定位置的最后一个子路径部分 * * @param path 路径 * @return 获取的最后一个子路径 * @since 3.1.2 */ public static Path getLastPathEle(Path path) { return getPathEle(path, path.getNameCount() - 1); } /** * 获取指定位置的子路径部分,支持负数,例如起始为-1表示从后数第一个节点位置 * * @param path 路径 * @param fromIndex 起始路径节点(包括) * @param toIndex 结束路径节点(不包括) * @return 获取的子路径 * @since 3.1.2 */ public static Path subPath(Path path, int fromIndex, int toIndex) { if (null == path) { return null; } final int len = path.getNameCount(); if (fromIndex < 0) { fromIndex = len + fromIndex; if (fromIndex < 0) { fromIndex = 0; } } else if (fromIndex > len) { fromIndex = len; } if (toIndex < 0) { toIndex = len + toIndex; if (toIndex < 0) { toIndex = len; } } else if (toIndex > len) { toIndex = len; } if (toIndex < fromIndex) { int tmp = fromIndex; fromIndex = toIndex; toIndex = tmp; } if (fromIndex == toIndex) { return null; } return path.subpath(fromIndex, toIndex); } /** * 获取文件属性 * * @param path 文件路径{@link Path} * @param isFollowLinks 是否跟踪到软链对应的真实路径 * @return {@link BasicFileAttributes} * @throws IORuntimeException IO异常 */ public static BasicFileAttributes getAttributes(Path path, boolean isFollowLinks) throws IORuntimeException { if (null == path) { return null; } try { return Files.readAttributes(path, BasicFileAttributes.class, getLinkOptions(isFollowLinks)); } catch (IOException e) { throw new IORuntimeException(e); } } /** * 获得输入流 * * @param path Path * @return 输入流 * @throws IORuntimeException 文件未找到 * @since 4.0.0 */ public static BufferedInputStream getInputStream(Path path) throws IORuntimeException { final InputStream in; try { in = Files.newInputStream(path); } catch (IOException e) { throw new IORuntimeException(e); } return IoUtil.toBuffered(in); } /** * 获得一个文件读取器 * * @param path 文件Path * @return BufferedReader对象 * @throws IORuntimeException IO异常 * @since 4.0.0 */ public static BufferedReader getUtf8Reader(Path path) throws IORuntimeException { return getReader(path, CharsetUtil.CHARSET_UTF_8); } /** * 获得一个文件读取器 * * @param path 文件Path * @param charset 字符集 * @return BufferedReader对象 * @throws IORuntimeException IO异常 * @since 4.0.0 */ public static BufferedReader getReader(Path path, Charset charset) throws IORuntimeException { return IoUtil.getReader(getInputStream(path), charset); } /** * 读取文件的所有内容为byte数组 * * @param path 文件 * @return byte数组 * @since 5.5.4 */ public static byte[] readBytes(Path path) { try { return Files.readAllBytes(path); } catch (IOException e) { throw new IORuntimeException(e); } } /** * 获得输出流 * * @param path Path * @return 输入流 * @throws IORuntimeException 文件未找到 * @since 5.4.1 */ public static BufferedOutputStream getOutputStream(Path path) throws IORuntimeException { final OutputStream in; try { in = Files.newOutputStream(path); } catch (IOException e) { throw new IORuntimeException(e); } return IoUtil.toBuffered(in); } /** * 修改文件或目录的文件名,不变更路径,只是简单修改文件名
* *
	 * FileUtil.rename(file, "aaa.jpg", false) xx/xx.png =》xx/aaa.jpg
	 * 
* * @param path 被修改的文件 * @param newName 新的文件名,包括扩展名 * @param isOverride 是否覆盖目标文件 * @return 目标文件Path * @since 5.4.1 */ public static Path rename(Path path, String newName, boolean isOverride) { return move(path, path.resolveSibling(newName), isOverride); } /** * 移动文件或目录到目标中,例如: *
    *
  • 如果src和target为同一文件或目录,直接返回target。
  • *
  • 如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。
  • *
  • 如果src为文件,target为文件,则按照是否覆盖参数执行。
  • *
  • 如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。
  • *
  • 如果src为目录,target为文件,抛出{@link IllegalArgumentException}
  • *
  • 如果src为目录,target为目录,则将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"
  • *
  • 如果src为目录,target为不存在的路径,则重命名src到target,如move("/a/b", "/c/d"),结果为"/c/d/",相当于b重命名为d
  • *
* * @param src 源文件或目录路径 * @param target 目标路径,如果为目录,则移动到此目录下 * @param isOverride 是否覆盖目标文件 * @return 目标文件Path */ public static Path move(Path src, Path target, boolean isOverride) { return PathMover.of(src, target, isOverride).move(); } /** * 移动文件或目录内容到目标中,例如: *
    *
  • 如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。
  • *
  • 如果src为文件,target为文件,则按照是否覆盖参数执行。
  • *
  • 如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。
  • *
  • 如果src为目录,target为文件,抛出{@link IllegalArgumentException}
  • *
  • 如果src为目录,target为目录,则将源目录下的内容移动到目标路径目录中,源目录不删除。
  • *
  • 如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录下的内容移动到目标路径目录中,源目录不删除。
  • *
* * @param src 源文件或目录路径 * @param target 目标路径,如果为目录,则移动到此目录下 * @param isOverride 是否覆盖目标文件 * @return 目标文件Path */ public static Path moveContent(Path src, Path target, boolean isOverride) { return PathMover.of(src, target, isOverride).moveContent(); } /** * 检查两个文件是否是同一个文件
* 所谓文件相同,是指Path对象是否指向同一个文件或文件夹 * * @param file1 文件1 * @param file2 文件2 * @return 是否相同 * @throws IORuntimeException IO异常 * @see Files#isSameFile(Path, Path) * @since 5.4.1 */ public static boolean equals(Path file1, Path file2) throws IORuntimeException { try { return Files.isSameFile(file1, file2); } catch (IOException e) { throw new IORuntimeException(e); } } /** * 判断是否为文件,如果file为null,则返回false * * @param path 文件 * @param isFollowLinks 是否跟踪软链(快捷方式) * @return 如果为文件true * @see Files#isRegularFile(Path, LinkOption...) */ public static boolean isFile(Path path, boolean isFollowLinks) { if (null == path) { return false; } return Files.isRegularFile(path, getLinkOptions(isFollowLinks)); } /** * 判断是否为符号链接文件 * * @param path 被检查的文件 * @return 是否为符号链接文件 * @since 4.4.2 */ public static boolean isSymlink(Path path) { return Files.isSymbolicLink(path); } /** * 判断文件或目录是否存在 * * @param path 文件 * @param isFollowLinks 是否跟踪软链(快捷方式) * @return 是否存在 * @since 5.5.3 */ public static boolean exists(Path path, boolean isFollowLinks) { return Files.exists(path, getLinkOptions(isFollowLinks)); } /** * 判断是否存在且为非目录 *
    *
  • 如果path为{@code null},返回{@code false}
  • *
  • 如果path不存在,返回{@code false}
  • *
* * @param path {@link Path} * @param isFollowLinks 是否追踪到软链对应的真实地址 * @return 如果为目录true * @since 5.8.14 */ public static boolean isExistsAndNotDirectory(final Path path, final boolean isFollowLinks) { return exists(path, isFollowLinks) && false == isDirectory(path, isFollowLinks); } /** * 判断给定的目录是否为给定文件或文件夹的子目录 * * @param parent 父目录 * @param sub 子目录 * @return 子目录是否为父目录的子目录 * @since 5.5.5 */ public static boolean isSub(Path parent, Path sub) { return toAbsNormal(sub).startsWith(toAbsNormal(parent)); } /** * 将Path路径转换为标准的绝对路径 * * @param path 文件或目录Path * @return 转换后的Path * @since 5.5.5 */ public static Path toAbsNormal(Path path) { Assert.notNull(path); return path.toAbsolutePath().normalize(); } /** * 获得文件的MimeType * * @param file 文件 * @return MimeType * @see Files#probeContentType(Path) * @since 5.5.5 */ public static String getMimeType(Path file) { try { return Files.probeContentType(file); } catch (IOException ignore) { // issue#3179,使用OpenJDK可能抛出NoSuchFileException,此处返回null return null; } } /** * 创建所给目录及其父目录 * * @param dir 目录 * @return 目录 * @since 5.5.7 */ public static Path mkdir(Path dir) { if (null != dir && false == exists(dir, false)) { try { Files.createDirectories(dir); } catch (IOException e) { throw new IORuntimeException(e); } } return dir; } /** * 创建所给文件或目录的父目录 * * @param path 文件或目录 * @return 父目录 * @since 5.5.7 */ public static Path mkParentDirs(Path path) { return mkdir(path.getParent()); } /** * 获取{@link Path}文件名 * * @param path {@link Path} * @return 文件名 * @since 5.7.15 */ public static String getName(Path path) { if (null == path) { return null; } return path.getFileName().toString(); } /** * 创建临时文件
* 创建后的文件名为 prefix[Random].suffix From com.jodd.io.FileUtil * * @param prefix 前缀,至少3个字符 * @param suffix 后缀,如果null则使用默认.tmp * @param dir 临时文件创建的所在目录 * @return 临时文件 * @throws IORuntimeException IO异常 * @since 6.0.0 */ public static Path createTempFile(final String prefix, final String suffix, final Path dir) throws IORuntimeException { int exceptionsCount = 0; while (true) { try { if(null == dir){ return Files.createTempFile(prefix, suffix); }else{ return Files.createTempFile(mkdir(dir), prefix, suffix); } } catch (final IOException ioex) { // fixes java.io.WinNTFileSystem.createFileExclusively access denied if (++exceptionsCount >= 50) { throw new IORuntimeException(ioex); } } } } /** * 删除文件或空目录,不追踪软链 * * @param path 文件对象 * @throws IOException IO异常 * @since 5.7.7 */ protected static void delFile(Path path) throws IOException { try { Files.delete(path); } catch (AccessDeniedException e) { // 可能遇到只读文件,无法删除.使用 file 方法删除 if (false == path.toFile().delete()) { throw e; } } } /** * 构建是否追踪软链的选项 * * @param isFollowLinks 是否追踪软链 * @return 选项 * @since 5.8.23 */ public static LinkOption[] getLinkOptions(final boolean isFollowLinks) { return isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; } /** * 构建是否追踪软链的选项 * * @param isFollowLinks 是否追踪软链 * @return 选项 * @since 5.8.23 */ public static Set getFileVisitOption(final boolean isFollowLinks) { return isFollowLinks ? EnumSet.of(FileVisitOption.FOLLOW_LINKS) : EnumSet.noneOf(FileVisitOption.class); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy