This commit is contained in:
Looly
2019-09-19 10:15:04 +08:00
parent 4d46555b2d
commit 04830bf06b

View File

@@ -32,14 +32,7 @@ import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.CRC32; import java.util.zip.CRC32;
@@ -68,24 +61,37 @@ import cn.hutool.core.util.ZipUtil;
* 文件工具类 * 文件工具类
* *
* @author xiaoleilu * @author xiaoleilu
*
*/ */
public class FileUtil { public class FileUtil {
/** 类Unix路径分隔符 */ /**
* 类Unix路径分隔符
*/
private static final char UNIX_SEPARATOR = CharUtil.SLASH; private static final char UNIX_SEPARATOR = CharUtil.SLASH;
/** Windows路径分隔符 */ /**
* Windows路径分隔符
*/
private static final char WINDOWS_SEPARATOR = CharUtil.BACKSLASH; private static final char WINDOWS_SEPARATOR = CharUtil.BACKSLASH;
/** Windows下文件名中的无效字符 */ /**
* Windows下文件名中的无效字符
*/
private static Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]"); private static Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]");
/** Class文件扩展名 */ /**
* Class文件扩展名
*/
public static final String CLASS_EXT = ".class"; public static final String CLASS_EXT = ".class";
/** Jar文件扩展名 */ /**
* Jar文件扩展名
*/
public static final String JAR_FILE_EXT = ".jar"; public static final String JAR_FILE_EXT = ".jar";
/** 在Jar中的路径jar的扩展名形式 */ /**
* 在Jar中的路径jar的扩展名形式
*/
public static final String JAR_PATH_EXT = ".jar!"; public static final String JAR_PATH_EXT = ".jar!";
/** 当Path为文件形式时, path会加入一个表示文件的前缀 */ /**
* 当Path为文件形式时, path会加入一个表示文件的前缀
*/
public static final String PATH_FILE_PRE = URLUtil.FILE_URL_PREFIX; public static final String PATH_FILE_PRE = URLUtil.FILE_URL_PREFIX;
/** /**
@@ -131,9 +137,7 @@ public class FileUtil {
if (file.isDirectory()) { if (file.isDirectory()) {
String[] subFiles = file.list(); String[] subFiles = file.list();
if (ArrayUtil.isEmpty(subFiles)) { return ArrayUtil.isEmpty(subFiles);
return true;
}
} else if (file.isFile()) { } else if (file.isFile()) {
return file.length() <= 0; return file.length() <= 0;
} }
@@ -156,7 +160,7 @@ public class FileUtil {
* *
* @param dirPath 目录 * @param dirPath 目录
* @return 是否为空 * @return 是否为空
* @exception IORuntimeException IOException * @throws IORuntimeException IOException
*/ */
public static boolean isDirEmpty(Path dirPath) { public static boolean isDirEmpty(Path dirPath) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dirPath)) { try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dirPath)) {
@@ -198,7 +202,7 @@ public class FileUtil {
* @return 文件列表 * @return 文件列表
*/ */
public static List<File> loopFiles(File file, FileFilter fileFilter) { public static List<File> loopFiles(File file, FileFilter fileFilter) {
final List<File> fileList = new ArrayList<File>(); final List<File> fileList = new ArrayList<>();
if (null == file || false == file.exists()) { if (null == file || false == file.exists()) {
return fileList; return fileList;
} }
@@ -243,7 +247,7 @@ public class FileUtil {
walkFiles(file.toPath(), maxDepth, new SimpleFileVisitor<Path>() { walkFiles(file.toPath(), maxDepth, new SimpleFileVisitor<Path>() {
@Override @Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
final File file = path.toFile(); final File file = path.toFile();
if (null == fileFilter || fileFilter.accept(file)) { if (null == fileFilter || fileFilter.accept(file)) {
fileList.add(file); fileList.add(file);
@@ -261,8 +265,8 @@ public class FileUtil {
* @param start 起始路径,必须为目录 * @param start 起始路径,必须为目录
* @param maxDepth 最大遍历深度,-1表示不限制深度 * @param maxDepth 最大遍历深度,-1表示不限制深度
* @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作 * @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作
* @since 4.6.3
* @see Files#walkFileTree(Path, Set, int, FileVisitor) * @see Files#walkFileTree(Path, Set, int, FileVisitor)
* @since 4.6.3
*/ */
public static void walkFiles(Path start, int maxDepth, FileVisitor<? super Path> visitor) { public static void walkFiles(Path start, int maxDepth, FileVisitor<? super Path> visitor) {
if (maxDepth < 0) { if (maxDepth < 0) {
@@ -415,7 +419,7 @@ public class FileUtil {
/** /**
* 通过多层目录创建文件 * 通过多层目录创建文件
* * <p>
* 元素名(多层目录名) * 元素名(多层目录名)
* *
* @return the file 文件 * @return the file 文件
@@ -507,7 +511,7 @@ public class FileUtil {
* @return 如果存在返回true * @return 如果存在返回true
*/ */
public static boolean exist(String path) { public static boolean exist(String path) {
return (path == null) ? false : file(path).exists(); return (null != path) && file(path).exists();
} }
/** /**
@@ -517,7 +521,7 @@ public class FileUtil {
* @return 如果存在返回true * @return 如果存在返回true
*/ */
public static boolean exist(File file) { public static boolean exist(File file) {
return (file == null) ? false : file.exists(); return (null != file) && file.exists();
} }
/** /**
@@ -833,6 +837,7 @@ public class FileUtil {
} }
final File[] files = directory.listFiles(); final File[] files = directory.listFiles();
if (null != files) {
boolean isOk; boolean isOk;
for (File childFile : files) { for (File childFile : files) {
isOk = del(childFile); isOk = del(childFile);
@@ -841,6 +846,7 @@ public class FileUtil {
return false; return false;
} }
} }
}
return true; return true;
} }
@@ -863,10 +869,11 @@ public class FileUtil {
if (ArrayUtil.isEmpty(files)) { if (ArrayUtil.isEmpty(files)) {
// 空文件夹则删除之 // 空文件夹则删除之
directory.delete(); directory.delete();
} } else {
for (File childFile : files) { for (File childFile : files) {
cleanEmpty(childFile); cleanEmpty(childFile);
} }
}
return true; return true;
} }
@@ -1137,7 +1144,7 @@ public class FileUtil {
* <pre> * <pre>
* FileUtil.rename(file, "aaa", true) xx/xx.png =》xx/aaa.png * FileUtil.rename(file, "aaa", true) xx/xx.png =》xx/aaa.png
* </pre> * </pre>
* * <p>
* 2、isRetainExt为false时不保留原扩展名需要在newName中 * 2、isRetainExt为false时不保留原扩展名需要在newName中
* *
* <pre> * <pre>
@@ -1218,7 +1225,7 @@ public class FileUtil {
} }
// 资源不存在的情况下使用标准化路径有问题,使用原始路径拼接后标准化路径 // 资源不存在的情况下使用标准化路径有问题,使用原始路径拼接后标准化路径
return normalize(classPath.concat(path)); return normalize(classPath.concat(Objects.requireNonNull(path)));
} }
/** /**
@@ -1263,11 +1270,8 @@ public class FileUtil {
return false; return false;
} }
if (StrUtil.C_SLASH == path.charAt(0) || path.matches("^[a-zA-Z]:[/\\\\].*")) {
// 给定的路径已经是绝对路径了 // 给定的路径已经是绝对路径了
return true; return StrUtil.C_SLASH == path.charAt(0) || path.matches("^[a-zA-Z]:[/\\\\].*");
}
return false;
} }
/** /**
@@ -1277,7 +1281,7 @@ public class FileUtil {
* @return 如果为目录true * @return 如果为目录true
*/ */
public static boolean isDirectory(String path) { public static boolean isDirectory(String path) {
return (path == null) ? false : file(path).isDirectory(); return (null != path) && file(path).isDirectory();
} }
/** /**
@@ -1287,7 +1291,7 @@ public class FileUtil {
* @return 如果为目录true * @return 如果为目录true
*/ */
public static boolean isDirectory(File file) { public static boolean isDirectory(File file) {
return (file == null) ? false : file.isDirectory(); return (null != file) && file.isDirectory();
} }
/** /**
@@ -1313,7 +1317,7 @@ public class FileUtil {
* @return 如果为文件true * @return 如果为文件true
*/ */
public static boolean isFile(String path) { public static boolean isFile(String path) {
return (path == null) ? false : file(path).isFile(); return (null != path) && file(path).isFile();
} }
/** /**
@@ -1323,7 +1327,7 @@ public class FileUtil {
* @return 如果为文件true * @return 如果为文件true
*/ */
public static boolean isFile(File file) { public static boolean isFile(File file) {
return (file == null) ? false : file.isFile(); return (null != file) && file.isFile();
} }
/** /**
@@ -1355,12 +1359,10 @@ public class FileUtil {
Assert.notNull(file1); Assert.notNull(file1);
Assert.notNull(file2); Assert.notNull(file2);
if (false == file1.exists() || false == file2.exists()) { if (false == file1.exists() || false == file2.exists()) {
// 两个文件都不存在判断其路径是否相同 // 两个文件都不存在判断其路径是否相同 对于一个存在一个不存在的情况,一定不相同
if (false == file1.exists() && false == file2.exists() && pathEquals(file1, file2)) { return false == file1.exists()//
return true; && false == file2.exists()//
} && pathEquals(file1, file2);
// 对于一个存在一个不存在的情况,一定不相同
return false;
} }
try { try {
return Files.isSameFile(file1.toPath(), file2.toPath()); return Files.isSameFile(file1.toPath(), file2.toPath());
@@ -1420,6 +1422,7 @@ public class FileUtil {
} }
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
/** /**
* 比较两个文件内容是否相同<br> * 比较两个文件内容是否相同<br>
* 首先比较长度,长度一致再比较内容,比较内容采用按行读取,每行比较<br> * 首先比较长度,长度一致再比较内容,比较内容采用按行读取,每行比较<br>
@@ -1544,7 +1547,7 @@ public class FileUtil {
* <li>3. 去除两边空格</li> * <li>3. 去除两边空格</li>
* <li>4. .. 和 . 转换为绝对路径,当..多于已有路径时,直接返回根路径</li> * <li>4. .. 和 . 转换为绝对路径,当..多于已有路径时,直接返回根路径</li>
* </ol> * </ol>
* * <p>
* 栗子: * 栗子:
* *
* <pre> * <pre>
@@ -1602,15 +1605,15 @@ public class FileUtil {
} }
List<String> pathList = StrUtil.split(pathToUse, StrUtil.C_SLASH); List<String> pathList = StrUtil.split(pathToUse, StrUtil.C_SLASH);
List<String> pathElements = new LinkedList<String>(); List<String> pathElements = new LinkedList<>();
int tops = 0; int tops = 0;
String element; String element;
for (int i = pathList.size() - 1; i >= 0; i--) { for (int i = pathList.size() - 1; i >= 0; i--) {
element = pathList.get(i); element = pathList.get(i);
if (StrUtil.DOT.equals(element)) { // 只处理非.的目录,既只处理非当前目录
// 当前目录,丢弃 if (false == StrUtil.DOT.equals(element)) {
} else if (StrUtil.DOUBLE_DOT.equals(element)) { if (StrUtil.DOUBLE_DOT.equals(element)) {
tops++; tops++;
} else { } else {
if (tops > 0) { if (tops > 0) {
@@ -1622,13 +1625,14 @@ public class FileUtil {
} }
} }
} }
}
return prefix + CollUtil.join(pathElements, StrUtil.SLASH); return prefix + CollUtil.join(pathElements, StrUtil.SLASH);
} }
/** /**
* 获得相对子路径 * 获得相对子路径
* * <p>
* 栗子: * 栗子:
* *
* <pre> * <pre>
@@ -1650,7 +1654,7 @@ public class FileUtil {
/** /**
* 获得相对子路径,忽略大小写 * 获得相对子路径,忽略大小写
* * <p>
* 栗子: * 栗子:
* *
* <pre> * <pre>
@@ -1744,6 +1748,7 @@ public class FileUtil {
} }
// -------------------------------------------------------------------------------------------- name start // -------------------------------------------------------------------------------------------- name start
/** /**
* 返回文件名 * 返回文件名
* *
@@ -1810,7 +1815,7 @@ public class FileUtil {
*/ */
public static String mainName(String fileName) { public static String mainName(String fileName) {
if (null == fileName) { if (null == fileName) {
return fileName; return null;
} }
int len = fileName.length(); int len = fileName.length();
if (0 == len) { if (0 == len) {
@@ -1823,20 +1828,18 @@ public class FileUtil {
int begin = 0; int begin = 0;
int end = len; int end = len;
char c; char c;
for (int i = len - 1; i > -1; i--) { for (int i = len - 1; i >= 0; i--) {
c = fileName.charAt(i); c = fileName.charAt(i);
if (len == end && CharUtil.DOT == c) { if (len == end && CharUtil.DOT == c) {
// 查找最后一个文件名和扩展名的分隔符:. // 查找最后一个文件名和扩展名的分隔符:.
end = i; end = i;
} }
if (0 == begin || begin > end) {
if (CharUtil.isFileSeparator(c)) {
// 查找最后一个路径分隔符(/或者\),如果这个分隔符在.之后,则继续查找,否则结束 // 查找最后一个路径分隔符(/或者\),如果这个分隔符在.之后,则继续查找,否则结束
if (CharUtil.isFileSeparator(c)) {
begin = i + 1; begin = i + 1;
break; break;
} }
} }
}
return fileName.substring(begin, end); return fileName.substring(begin, end);
} }
@@ -1893,11 +1896,10 @@ public class FileUtil {
/** /**
* 根据文件流的头部信息获得文件类型 * 根据文件流的头部信息获得文件类型
* *
* @see FileTypeUtil#getType(File)
*
* @param file 文件 {@link File} * @param file 文件 {@link File}
* @return 类型,文件的扩展名,未找到为<code>null</code> * @return 类型,文件的扩展名,未找到为<code>null</code>
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
* @see FileTypeUtil#getType(File)
*/ */
public static String getType(File file) throws IORuntimeException { public static String getType(File file) throws IORuntimeException {
return FileTypeUtil.getType(file); return FileTypeUtil.getType(file);
@@ -1926,6 +1928,7 @@ public class FileUtil {
} }
// -------------------------------------------------------------------------------------------- in start // -------------------------------------------------------------------------------------------- in start
/** /**
* 获得输入流 * 获得输入流
* *
@@ -2470,7 +2473,7 @@ public class FileUtil {
* @since 4.5.2 * @since 4.5.2
*/ */
public static void readLines(RandomAccessFile file, Charset charset, LineHandler lineHandler) { public static void readLines(RandomAccessFile file, Charset charset, LineHandler lineHandler) {
String line = null; String line;
try { try {
while ((line = file.readLine()) != null) { while ((line = file.readLine()) != null) {
lineHandler.handle(CharsetUtil.convert(line, CharsetUtil.CHARSET_ISO_8859_1, charset)); lineHandler.handle(CharsetUtil.convert(line, CharsetUtil.CHARSET_ISO_8859_1, charset));
@@ -2506,7 +2509,7 @@ public class FileUtil {
* @since 4.5.18 * @since 4.5.18
*/ */
public static String readLine(RandomAccessFile file, Charset charset) { public static String readLine(RandomAccessFile file, Charset charset) {
String line = null; String line;
try { try {
line = file.readLine(); line = file.readLine();
} catch (IOException e) { } catch (IOException e) {
@@ -2609,6 +2612,7 @@ public class FileUtil {
} }
// -------------------------------------------------------------------------------------------- out start // -------------------------------------------------------------------------------------------- out start
/** /**
* 获得一个输出流对象 * 获得一个输出流对象
* *
@@ -2799,7 +2803,6 @@ public class FileUtil {
/** /**
* 将String写入文件覆盖模式 * 将String写入文件覆盖模式
* *
*
* @param content 写入的内容 * @param content 写入的内容
* @param file 文件 * @param file 文件
* @param charset 字符集 * @param charset 字符集
@@ -2813,7 +2816,6 @@ public class FileUtil {
/** /**
* 将String写入文件覆盖模式 * 将String写入文件覆盖模式
* *
*
* @param content 写入的内容 * @param content 写入的内容
* @param file 文件 * @param file 文件
* @param charset 字符集 * @param charset 字符集
@@ -3326,7 +3328,7 @@ public class FileUtil {
* @since 3.3.1 * @since 3.3.1
*/ */
public static boolean containsInvalid(String fileName) { public static boolean containsInvalid(String fileName) {
return StrUtil.isBlank(fileName) ? false : ReUtil.contains(FILE_NAME_INVALID_PATTERN_WIN, fileName); return (false == StrUtil.isBlank(fileName)) && ReUtil.contains(FILE_NAME_INVALID_PATTERN_WIN, fileName);
} }
/** /**