File.mkdirs()现网并发创建失败

本文分析了现网环境中同一文件分片上传时并发创建目录导致的异常问题,通过代码分析定位到具体原因,并提供了解决方案及并发验证的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.现网问题描述

现网在同一文件分片上传的过程中发生创建目录失败,抛出异常

二,定位问题

从日志中能够看出上传分片的顺序是1,2,0,3,4

第二个分片请求上传首先到达进行创建目录

第三个分片请求上传进行创建目录失败

 

看代码进行分析

已经很明显啦,

导致的问题是分片上传的过程中并发上传分片,同时创建相同目录,导致在下面代码红色标记的地方发生并发。

file.mkdirs方法如果目录已经存在,则返回false;如果目录不存在,创建成功,返回false

三.修改验证

修改代码为

并发验证

 package testJVm;

import java.io.File;
import java.util.concurrent.CountDownLatch;

public class TestDir
{
    
    public static void main(String[] args)
        throws InterruptedException
    {
        CountDownLatch countDown = new CountDownLatch(1);
        CountDownLatch await = new CountDownLatch(50); // 依次创建并启动处于等待状态的5个MyRunnable线程
        for (int i = 0; i < 50; ++i)
        {
            new Thread(new MyRunnable(countDown, await)).start();
        }
        System.out.println("用于触发处于等待状态的线程开始工作......");
        System.out.println("用于触发处于等待状态的线程工作完成,等待状态线程开始工作......");
        countDown.countDown();
        await.await();
        System.out.println("Bingo!");
    }
    
    public static String creatDIRS(String path)
    {
        File file = new File(path);
        if (file.exists())
        {
            System.out.println("file is exist, path = " + path);
            return path;
        }
        
        if (!file.mkdirs())
        {
            System.out.println("file mkdirs is fail, path = " + path);
            if (!file.exists())
            {
                System.out.println("file make director error");
            }
            else
            {
                System.out.println("OK");
            }
        }
        return path;
    }
    
}

package testJVm;

import java.util.concurrent.CountDownLatch;

public class MyRunnable implements Runnable
{
    private final CountDownLatch countDown;
    
    private final CountDownLatch await;
    
    private final String path ="c://opt//hua//wei"; 
    
    public MyRunnable(CountDownLatch countDown, CountDownLatch await)
    {
        this.countDown = countDown;
        this.await = await;
    }
    
    public void run()
    {
        try
        {
            countDown.await();// 等待主线程执行完毕,获得开始执行信号...
            System.out.println("处于等待的线程开始自己预期工作......");
            TestDir.creatDIRS(path);
            await.countDown();// 完成预期工作,发出完成信号...
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

经过验证,并发创建文件时,不会出现抛异常的场景

 

 

public boolean unzip(String zipFilePath, String outputDir) { boolean success = true; //检查zipfilePath文件的大小 File zipFiletest = new File(zipFilePath); // 2. 文件大小稳定监测逻辑 long lastSize = -1; LogUtils.i("开始检测ZIP文件大小稳定性..."); while (true) { long currentSize = zipFiletest.length(); LogUtils.i("当前ZIP文件大小: " + currentSize + " bytes"); if (currentSize == lastSize) { LogUtils.i("ZIP文件大小稳定,开始解压"); break; } lastSize = currentSize; try { Thread.sleep(1000); // 每秒检测一次 } catch (InterruptedException e) { LogUtils.e("文件大小监测被中断: " + e.getMessage()); return false; } } LogUtils.i("开始解压ZIP文件: " + zipFilePath + " 到目录: " + outputDir); // 创建输出目录(如果不存在) File dir = new File(outputDir); if (!dir.exists()) { LogUtils.d("尝试创建输出目录: " + outputDir); if (!dir.mkdirs()) { LogUtils.e("无法创建输出目录: " + outputDir); return false; } LogUtils.i("成功创建输出目录: " + outputDir); } // 获取输出目录的规范路径用于安全检查 String canonicalOutputDir; try { canonicalOutputDir = dir.getCanonicalPath(); LogUtils.d("输出目录规范路径: " + canonicalOutputDir); } catch (IOException e) { LogUtils.e("获取输出目录规范路径失败: " + e.getMessage()); return false; } try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFilePath)))) { LogUtils.d("打开ZIP输入流成功"); ZipEntry entry; int entryCount = 0; while ((entry = zis.getNextEntry()) != null) { entryCount++; String originalEntryName = entry.getName(); LogUtils.d("处理条目 #" + entryCount + ": " + originalEntryName + " | 目录: " + entry.isDirectory() + " | 大小: " + entry.getSize() + " bytes"); // 规范化路径处理 String entryName = normalizePath(originalEntryName); if (entryName.isEmpty()) { LogUtils.e("跳过无效条目: " + originalEntryName); continue; } LogUtils.d("规范化后路径: " + entryName); File outputFile = new File(outputDir, entryName); // 详细路径安全日志 try { String canonicalPath = outputFile.getCanonicalPath(); LogUtils.d("目标文件规范路径: " + canonicalPath); // 路径遍历安全检查 if (!canonicalPath.startsWith(canonicalOutputDir + File.separator)) { LogUtils.e("安全违规: 条目 " + originalEntryName + " 试图逃逸到 " + canonicalPath); success = false; continue; } } catch (IOException e) { LogUtils.e("路径解析错误: " + outputFile.getAbsolutePath() + " | 错误: " + e.getMessage()); success = false; continue; } // 创建父目录(如果需要) File parentDir = outputFile.getParentFile(); if (parentDir != null && !parentDir.exists()) { LogUtils.d("尝试创建父目录: " + parentDir.getAbsolutePath()); if (!parentDir.mkdirs()) { LogUtils.e("无法创建父目录: " + parentDir.getAbsolutePath()); success = false; continue; } LogUtils.i("成功创建父目录: " + parentDir.getAbsolutePath()); } if (!entry.isDirectory()) { LogUtils.d("解压文件到: " + outputFile.getAbsolutePath()); try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFile))) { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; long totalBytes = 0; while ((bytesRead = zis.read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); totalBytes += bytesRead; // 安全检查逻辑(每秒最多1次) long currentTime = System.currentTimeMillis(); if (currentTime - lastCheckTime > 1000) { synchronized (this) { if (currentTime - lastCheckTime > 1000) { LogUtils.d("执行安全检测..."); if (!HmiJNIImpl.getInstance().mapControl()) { LogUtils.e("安全检测失败,终止解压"); return false; } lastCheckTime = currentTime; } } } } LogUtils.i("成功解压文件: " + outputFile.getName() + " | 大小: " + totalBytes + " bytes"); } catch (IOException e) { LogUtils.e("写入文件失败: " + outputFile.getAbsolutePath() + " | 错误: " + e.getMessage()); success = false; // 删除部分写入的文件 if (outputFile.exists() && !outputFile.delete()) { LogUtils.e("无法删除部分写入的文件: " + outputFile.getAbsolutePath()); } } } else { LogUtils.d("创建目录: " + outputFile.getAbsolutePath()); if (!outputFile.exists()) { if (!outputFile.mkdirs()) { LogUtils.e("无法创建目录: " + outputFile.getAbsolutePath()); success = false; } else { LogUtils.i("成功创建目录: " + outputFile.getAbsolutePath()); } } else { LogUtils.d("目录已存在: " + outputFile.getAbsolutePath()); } } zis.closeEntry(); LogUtils.d("完成处理条目: " + originalEntryName); } LogUtils.i("处理完成所有条目,共 " + entryCount + " 个"); } catch (IOException e) { LogUtils.e("解压ZIP文件时发生错误: " + e.getMessage()); success = false; } // 结果处理 if (success) { LogUtils.i("解压成功,尝试删除原始ZIP文件: " + zipFilePath); File zipFile = new File(zipFilePath); if (zipFile.delete()) { LogUtils.i("成功删除原始ZIP文件: " + zipFilePath); } else { LogUtils.e("无法删除原始ZIP文件: " + zipFilePath); success = false; } } else { LogUtils.e("解压过程中遇到错误,保留原始ZIP文件"); } LogUtils.i("解压结果: " + (success ? "成功" : "失败")); return success; } 解压时,目标路径出什么情况会报出2025-07-28 09:58:56.962 20477-20509 ISA_NAVI_ com.isa.navi E [ZipUtils.java:unzip:68] 解压ZIP文件时发生错误: /data/data/com.isa.navi/KVM_EU_202502_T1.0_S31N.zip: open failed: ENOENT (No such file or directory)的错误?
最新发布
07-29
@RestController @RequestMapping("/common/file") @Slf4j public class CommonController { @Value("${file.path}") private String filePath; @PostMapping("/singleUploadFile") public Result singleUploadFile(@RequestParam(name = "file",required = true) MultipartFile file, HttpServletRequest request) { String fileName = ""; if (!file.isEmpty()) { try { //图片命名 // fileName = file.getOriginalFilename(); File newFile = new File(filePath); if (!newFile.exists()) { newFile.mkdirs(); } // 获取原始文件名 String originalFilename = file.getOriginalFilename(); // 获取文件扩展名 assert originalFilename != null; String fileExt = originalFilename.substring(originalFilename.lastIndexOf(".")); // 生成新的文件名 fileName = TimeUtil.dateRandom18()+"_"+originalFilename; File imageFile = new File(newFile,fileName); BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream(imageFile)); out.write(file.getBytes()); out.flush(); out.close(); } catch (FileNotFoundException e) { e.printStackTrace(); return ResultUtil.error(-1,"路径为空"); } catch (IOException e) { e.printStackTrace(); log.info("上传底图接口/uploadBaseImage出异常,异常信息如下====>{}",e.getMessage()); return ResultUtil.error(-1,"上传文件异常"); } } // return ResultUtil.success(1,"图片上传成功!","文件名称"+fileName+"==="+"文件路径==>/search/compare/"+ fileName); return ResultUtil.successForDataAndImage(1,"上传文件成功",fileName, IpUtils.getBaseUrl(request)); }这段代码什么意思
03-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值