com.yixan.tools.common.util.FileUtil Maven / Gradle / Ivy
package com.yixan.tools.common.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.LinkedList;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 文件工具类
*
* @author zhaohuihua
*/
public class FileUtil {
/** 日志对象 **/
private static Logger log = LoggerFactory.getLogger(FileUtil.class);
/** 斜杠/ **/
public static final String SLASH = "/";
/** 反斜杠\ **/
public static final String BSLASH = "\\";
/**
* 获取文件扩展名
* /image/abc.def.png --> .png
* /image/abcdef/png --> null
* /image/abc.def/png --> null
*
* @param fileName
* @return
*/
public static String getExtension(String fileName) {
return getExtension(fileName, true);
}
/**
* 获取文件扩展名
*
* @param path
* @param dot 带不带点, 如true=.png, false=png
* @return
*/
public static String getExtension(String path, boolean dot) {
if (path == null) {
return null;
}
int i = path.lastIndexOf('.');
if (i < 0 || i < path.lastIndexOf('/') || i < path.lastIndexOf('/')) {
return null;
}
return path.substring(i + (dot ? 0 : 1));
}
/**
* 清除文件扩展名
* /image/abc.def.png --> /image/abc.def
* /image/abcdef/png --> /image/abcdef/png
* /image/abc.def/png --> /image/abc.def/png
*
* @param path
* @return
*/
public static String removeExtension(String path) {
if (path == null) {
return null;
}
int i = path.lastIndexOf('.');
if (i < 0 || i < path.lastIndexOf('/') || i < path.lastIndexOf('/')) {
return null;
}
return path.substring(0, i);
}
/**
* 替换文件扩展名
* /image/abc.def.png, .jpg --> /image/abc.def.jpg
* /image/abcdef/xxx, .jpg --> /image/abcdef/xxx.jpg
* /image/abc.def/xxx, .jpg --> /image/abc.def/xxx.jpg
*
* @param path
* @return
*/
public static String replaceExtension(String path, String extension) {
String newpath = removeExtension(path);
if (newpath == null || extension == null) {
return newpath;
} else if (extension.startsWith(".")) {
return newpath + extension;
} else {
return newpath + "." + extension;
}
}
/**
* 获取类路径
*
* @author 赵卉华
* @return 绝对路径
*/
public static String getClassPath() {
return getClassPathResource(null);
}
/**
* 获取类路径下的资源
*
* @author 赵卉华
* @param path 相对路径
* @return 绝对路径
*/
public static String getClassPathResource(String path) {
URL root = FileUtil.class.getResource(path == null ? "/" : concat("/", path));
if (root == null) {
// String classPath = getAbsolutePathOfLocalFileUrl(FileUtil.class.getResource("/"));
// log.error("File not found: {}, ClassPath: {}", path, classPath);
return null;
} else {
return getAbsolutePathOfLocalFileUrl(root);
}
}
/**
* 获取类文件夹路径
*
* @author 赵卉华
* @param clazz 类文件
* @return 绝对路径
*/
public static String getClassFolder(Class> clazz) {
return getClassFolderResource(clazz, null);
}
/**
* 获取与指定类文件夹路径下的资源
*
* @author 赵卉华
* @param clazz 类文件
* @param path 相对路径
* @return 绝对路径
*/
public static String getClassFolderResource(Class> clazz, String path) {
URL root = clazz.getResource(path == null ? "./" : concat("./", path));
if (root == null) {
// String classPath = getAbsolutePathOfLocalFileUrl(FileUtil.class.getResource("/"));
// log.error("File not found: {}, ClassPath: {}", path, classPath);
return null;
} else {
return getAbsolutePathOfLocalFileUrl(root);
}
}
private static String getAbsolutePathOfLocalFileUrl(URL url) {
try {
// root.getPath(), 文件路径中有空格的话会出问题, 被替换为%20
// root.toURI(), 支持文件路径中有空格的情况
return new File(url.toURI()).getAbsolutePath();
} catch (URISyntaxException e) {
return new File(url.getPath()).getAbsolutePath();
}
}
/**
* 通过网络下载文件
*
* @param url URL
* @return 文件内容
* @throws IOException 失败
*/
public static byte[] download(String url) throws IOException {
try (InputStream input = new URL(url).openStream();
ByteArrayOutputStream output = new ByteArrayOutputStream();) {
// Files.copy(Paths.get(URI.create(url)), output); // 不支持HTTP协议
copy(input, output);
return output.toByteArray();
}
}
/**
* 通过网络下载文件
*
* @param url URL
* @return 文件内容
* @throws IOException 失败
*/
public static String downloadString(String url) throws IOException {
return downloadString(url, "UTF-8");
}
/**
* 通过网络下载文件
*
* @param url URL
* @param charset 字符编码格式
* @return 文件内容
* @throws IOException 失败
*/
public static String downloadString(String url, String charset) throws IOException {
return new String(download(url), charset);
}
/**
* 通过网络下载文件
*
* @param url URL
* @param saveAs 保存的文件路径
* @throws IOException 失败
*/
public static void downloadSave(String url, String saveAs) throws IOException {
try (InputStream input = new URL(url).openStream();
OutputStream output = new FileOutputStream(new File(saveAs))) {
// Files.copy(Paths.get(URI.create(url)), output); // 不支持HTTP协议
copy(input, output);
}
}
public static void copy(InputStream input, OutputStream output) throws IOException {
int length;
int bz = 2048;
byte[] buffer = new byte[bz];
while ((length = input.read(buffer, 0, buffer.length)) > 0) {
output.write(buffer, 0, length);
}
}
/**
* 将byte数据保存到文件
*
* @param data 数据
* @param path 文件路径
* @throws IOException 失败
*/
public static void saveFile(byte[] data, String path) throws IOException {
Path target = Paths.get(path);
mkdirsIfNotExists(target);
try (InputStream in = new ByteArrayInputStream(data)) {
Files.copy(in, target);
}
}
/**
* 将InputStream数据保存到文件
*
* @param data 数据
* @param path 文件路径
* @throws IOException 失败
*/
public static void saveFile(InputStream input, String path) throws IOException {
Path target = Paths.get(path);
mkdirsIfNotExists(target);
Files.copy(input, target);
}
/**
* 如果文件的文件夹不存在则创建
*
* @param file 必须是文件, 不能是文件夹
*/
public static void mkdirsIfNotExists(File file) {
mkdirsIfNotExists(file.toPath());
}
/**
* 如果文件夹不存在则创建
*
* @param file
* @param toParent 是判断file自身还是判断file的父级文件夹
*/
public static void mkdirsIfNotExists(File file, boolean toParent) {
mkdirsIfNotExists(file.toPath(), toParent);
}
private static void mkdirsIfNotExists(Path path) {
mkdirsIfNotExists(path, true);
}
private static void mkdirsIfNotExists(Path path, boolean toParent) {
Path folder = toParent ? path.getParent() : path;
if (!Files.exists(folder)) {
folder.toFile().mkdirs();
}
}
/**
* 移动文件或文件夹
*
* @param source
* @param destination
* @throws IOException
*/
public static void move(String source, String destination) throws IOException {
move(Paths.get(source), Paths.get(destination));
}
/**
* 移动文件或文件夹
*
* @param source
* @param destination
* @throws IOException
*/
public static void move(Path source, Path destination) throws IOException {
// normalize()清除路径中的.和..
Path spath = source.normalize();
Path dpath = destination.normalize();
if (!Files.exists(spath)) {
// 源文件不存在
return;
}
if (spath.toString().equals(dpath.toString())) {
return; // 源路径和目标路径相同
}
if (Files.isDirectory(spath) && (Files.exists(dpath) || dpath.startsWith(spath))) {
// 是文件夹, 目标路径已经存在或目标路径在源路径之中, 则只能遍历一个个的移动
File sfile = spath.toFile();
File[] files = sfile.listFiles();
for (File next : files) {
move(next.toPath(), dpath.resolve(next.getName()));
}
if (sfile.exists() && sfile.listFiles().length == 0) {
// 删除空文件夹
sfile.delete();
}
} else {
// 目标路径的上级文件夹必须存在, 否则会报错
mkdirsIfNotExists(dpath, true);
// 开始移动
try {
Files.move(spath, dpath);
} catch (IOException e) {
log.error("Move file failed: {} --> {}\n\t{}", spath, dpath, e.toString());
throw e;
}
}
}
/**
* 复制文件或文件夹
*
* @param source
* @param destination
* @throws IOException
*/
public static void copy(String source, String destination) throws IOException {
copy(Paths.get(source), Paths.get(destination));
}
/**
* 复制文件或文件夹
*
* @param source
* @param destination
* @throws IOException
*/
public static void copy(Path source, Path destination) throws IOException {
if (!Files.exists(source)) {
// 源文件不存在
throw new FileNotFoundException(source.toString());
}
// Files.copy目标路径的上级文件夹必须存在, 否则会报错, 所以先创建上级文件夹
mkdirsIfNotExists(destination, true);
if (Files.isDirectory(source)) { // 复制文件夹
Files.walkFileTree(source, new CopyFileVisitor(source, destination));
} else { // 复制文件
Files.copy(source, destination);
}
}
/**
* 删除文件或文件夹
*
* @param source
* @throws IOException
*/
public static void delete(String source) throws IOException {
delete(Paths.get(source));
}
/**
* 删除文件或文件夹
*
* @param source
* @throws IOException
*/
public static void delete(Path source) throws IOException {
if (!Files.exists(source)) {
return; // 源文件不存在
}
if (Files.isDirectory(source)) { // 删除文件夹
Files.walkFileTree(source, new DeleteFileVisitor());
} else { // 删除文件
try {
Files.deleteIfExists(source);
} catch (IOException e) {
log.error("Delete file failed: {}\n\t{}", source, e.toString());
}
}
}
/**
* 文件Visitor
*
* @author zhaohuihua
* @version V1.0 2016年12月24日
*/
private static abstract class AllFileVisitor implements FileVisitor {
@Override
public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException {
onPreVisitDirectory(directory);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
onPostVisitDirectory(directory);
if (e != null) {
log.error("Visit directory failed: {}\n\t{}", directory.toString(), e.toString());
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
onVisitFile(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException e) {
log.error("Visit file failed: {}\n\t{}", file.toString(), e.toString());
return FileVisitResult.CONTINUE;
}
protected void onVisitFile(Path file) {
}
protected void onPreVisitDirectory(Path directory) {
}
protected void onPostVisitDirectory(Path directory) {
}
}
private static class CopyFileVisitor extends AllFileVisitor {
private Path source;
private Path destination;
public CopyFileVisitor(Path source, Path destination) {
this.source = source;
this.destination = destination;
}
protected void onVisitFile(Path file) {
Path relative = source.relativize(file);
Path absolute = destination.resolve(relative);
try {
mkdirsIfNotExists(absolute);
Files.copy(file, absolute);
} catch (IOException e) {
log.error("Copy file failed: {} --> {}\n\t{}", file, absolute, e.toString());
}
}
}
private static class DeleteFileVisitor extends AllFileVisitor {
protected void onVisitFile(Path file) {
try {
Files.deleteIfExists(file);
} catch (IOException e) {
log.error("Delete file failed: {}\n\t{}", file, e.toString());
}
}
protected void onPostVisitDirectory(Path directory) {
try {
Files.deleteIfExists(directory);
} catch (IOException e) {
log.error("Delete directory failed: {}\n\t{}", directory, e.toString());
}
}
}
/**
* 连接
*
* @param folder 文件夹
* @param paths 文件路径
* @return
*/
public static String concat(String folder, String... paths) {
return concat(false, folder, paths);
}
/**
* 连接
*
* @param format 要不要格式化
* @param folder 文件夹
* @param paths 文件路径
* @return
*/
public static String concat(boolean format, String folder, String... paths) {
StringBuilder buffer = new StringBuilder();
if (VerifyUtil.isNotBlank(folder)) {
buffer.append(folder);
}
for (String path : paths) {
if (VerifyUtil.isBlank(path)) {
continue;
}
if (!endsWithSeparator(buffer) && !startsWithSeparator(path)) {
buffer.append(SLASH).append(path);
} else if (endsWithSeparator(folder) && startsWithSeparator(path)) {
buffer.append(path.substring(1));
} else {
buffer.append(path);
}
}
return format ? formatPath(buffer.toString()) : buffer.toString();
}
private static boolean endsWithSeparator(CharSequence string) {
return string.toString().endsWith(SLASH) || string.toString().endsWith(BSLASH);
}
private static boolean startsWithSeparator(CharSequence string) {
return string.toString().startsWith(SLASH) || string.toString().startsWith(BSLASH);
}
private static Pattern SCHEMA = Pattern.compile("(file|ftp|jar|https?)\\:/+");
private static String replaceProtocol(String path) {
int index = 0;
StringBuilder buffer = new StringBuilder();
Matcher m = SCHEMA.matcher(path);
while (m.find()) {
buffer.append(path.substring(index, m.start(1)));
String schema = m.group(1);
buffer.append(schema).append("://");
if ("file".equals(schema)) {
// file:/D:/domain/index.html file:/D:/还是file://D:/还是file:///D:/
// chrome打开本地文件显示为三个斜杠;
// new URL()只能识别一个或三个斜杠, 两个斜杠就报错UnknownHostException: D
// 统一处理成三个斜杠
buffer.append("/");
}
index = m.end();
}
buffer.append(path.substring(index));
return buffer.toString();
}
/**
* 格式化路径, \替换为/, 并处理../和./的情况
* 路径作为URL时不能使用\, 但不论windows还是linux, Java环境都支持/
* 因此将所有的\替换为/
* 如 \\site\\home\\../include/head.tpl 处理为 /site/include/head.tpl
*
* @author 赵卉华
* @param path 原路径
* @return 格式化之后的路径
*/
public static String formatPath(String path) {
if (VerifyUtil.isBlank(path)) {
return path;
}
StringTokenizer tokenizer = new StringTokenizer(path, "\\/");
LinkedList list = new LinkedList();
while (tokenizer.hasMoreTokens()) {
String string = tokenizer.nextToken();
if (VerifyUtil.isBlank(string)) {
continue;
} else if (".".equals(string)) {
continue;
} else if ("..".equals(string)) {
if (list.isEmpty()) {
continue;
} else {
list.removeLast();
}
} else {
list.addLast(string);
}
}
StringBuilder buffer = new StringBuilder();
for (String string : list) {
if (buffer.length() > 0) {
buffer.append(SLASH);
} else {
if (path.startsWith(SLASH) || path.startsWith(BSLASH)) {
buffer.append(SLASH);
}
}
buffer.append(string);
}
return replaceProtocol(buffer.toString());
}
/**
* 将path转换为相对于root的路径
* 不用Files.relativize()是为了兼容URL
*
*
* String root = "D:/domain/biz/"
* relativize(root, "D:/domain/biz/index.html") -- index.html
* relativize(root, "D:/domain/biz/html/homepage.html") -- html/homepage.html
* relativize(root, "D:/domain/assets/libs/mui/mui.js") -- ../assets/libs/mui/mui.js
* relativize(root, "D:/static/assets/libs/mui/mui.js") -- ../../static/assets/libs/mui/mui.js
*
*
* @param root 根路径
* @param path 绝对路径
* @return 相对路径
*/
public static String relativize(String root, String path) {
String[] roots = splitPath(root);
String[] paths = splitPath(path);
// 先找到第一个不相同的文件夹
int len = Math.min(roots.length, paths.length);
int idx = len;
for (int i = 0; i < len; i++) {
if (!roots[i].equals(paths[i])) {
idx = i;
break;
}
}
StringBuilder buffer = new StringBuilder();
// 从第一个不相同的开始, roots还有几级就加几个../
for (int i = idx; i < roots.length; i++) {
buffer.append("..").append("/");
}
// 从第一个不相同的开始, 加上paths剩下的路径
for (int i = idx; i < paths.length; i++) {
if (i > idx) {
buffer.append("/");
}
buffer.append(paths[i]);
}
return replaceProtocol(buffer.toString());
}
private static Pattern separator = Pattern.compile("/+");
private static Pattern trim = Pattern.compile("^/+|/+$");
public static String[] splitPath(String path) {
path = formatPath(path);
path = trim.matcher(path).replaceAll("");
return separator.split(path);
}
/** 判断是不是绝对路径 **/
public static boolean isAbsolutePath(String path) {
// 绝对路径: linux的/home/或window的D:/xxx/或http://url
return path.startsWith("/") || path.startsWith("\\") || path.indexOf(':') >= 0;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy