mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
!527 [5.7.21] [hutool-core] 新增copySafely方法与mkdirsSafely方法
Merge pull request !527 from z8g/v5-dev
This commit is contained in:
commit
03c491b963
@ -13,6 +13,7 @@ import cn.hutool.core.io.file.Tailer;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.io.unit.DataSizeUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
@ -537,6 +538,8 @@ public class FileUtil extends PathUtil {
|
||||
}
|
||||
|
||||
if (file.isDirectory()) {
|
||||
// TODO: 是否需要统计目录本身的大小呢?
|
||||
// size += file.length();
|
||||
long size = 0L;
|
||||
File[] subFiles = file.listFiles();
|
||||
if (ArrayUtil.isEmpty(subFiles)) {
|
||||
@ -607,6 +610,7 @@ public class FileUtil extends PathUtil {
|
||||
return null;
|
||||
}
|
||||
if (false == file.exists()) {
|
||||
// TODO: {@see mkdirsSafely} 确保并发环境下的安全创建
|
||||
mkParentDirs(file);
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
@ -827,6 +831,39 @@ public class FileUtil extends PathUtil {
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全地级联创建目录 (确保并发环境下能创建成功)
|
||||
*
|
||||
* <pre>
|
||||
* 并发环境下,假设 test 目录不存在,如果线程A mkdirs "test/A" 目录,线程B mkdirs "test/B"目录,
|
||||
* 其中一个线程可能会失败,进而导致以下代码抛出 FileNotFoundException 异常
|
||||
*
|
||||
* file.getParentFile().mkdirs(); // 父目录正在被另一个线程创建中,返回 false
|
||||
* file.createNewFile(); // 抛出 IO 异常,因为该线程无法感知到父目录已被创建
|
||||
* </pre>
|
||||
*
|
||||
* @param dir 待创建的目录
|
||||
* @return true表示创建成功,false表示创建失败
|
||||
* @since 2022-01-29
|
||||
* @author z8g
|
||||
*/
|
||||
public static boolean mkdirsSafely(File dir) {
|
||||
if (dir == null) {
|
||||
return false;
|
||||
}
|
||||
if (dir.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 1; i <= 5; i++) { // 高并发场景下,可以看到 i 处于 1 ~ 3 之间
|
||||
dir.mkdirs(); // 如果文件已存在,也会返回 false,所以该值不能作为是否能创建的依据,因此不对其进行处理
|
||||
if (dir.exists()) {
|
||||
return true;
|
||||
}
|
||||
ThreadUtil.sleep(1);
|
||||
}
|
||||
return dir.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建临时文件<br>
|
||||
* 创建后的文件名为 prefix[Randon].tmp
|
||||
|
@ -87,12 +87,49 @@ public class NioUtil {
|
||||
Assert.notNull(outChannel, "Out channel is null!");
|
||||
|
||||
try {
|
||||
return inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||
return copySafely(inChannel, outChannel);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件拷贝实现
|
||||
*
|
||||
* <pre>
|
||||
* FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
|
||||
* 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
|
||||
* 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes,不处理将导致文件内容缺失
|
||||
*
|
||||
* // 错误写法,dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
|
||||
* try (InputStream srcStream = zipFile.getInputStream(zipEntry);
|
||||
* ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
|
||||
* FileOutputStream fos = new FileOutputStream(saveFile);
|
||||
* FileChannel dstChannel = fos.getChannel()) {
|
||||
* dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param inChannel 输入通道
|
||||
* @param outChannel 输出通道
|
||||
* @return 输入通道的字节数
|
||||
* @throws IOException 发生IO错误
|
||||
* @link http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/java/nio/FileChannelImpl.java
|
||||
* @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java
|
||||
* @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/native/FileChannelImpl.c
|
||||
* @since 2022-01-29
|
||||
* @author z8g
|
||||
*/
|
||||
private static long copySafely(FileChannel inChannel, FileChannel outChannel) throws IOException {
|
||||
long totalBytes = inChannel.size();
|
||||
for (long pos = 0, remaining = totalBytes; remaining > 0; ) { // 确保文件内容不会缺失
|
||||
long writeBytes = inChannel.transferTo(pos, remaining, outChannel); // 实际传输的字节数
|
||||
pos += writeBytes;
|
||||
remaining -= writeBytes;
|
||||
}
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝流,使用NIO,不会关闭channel
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user