FileNotFoundException
开发背景:系统需添加一个云盘模块,有文件上传功能,同步上传大文件传输速度很慢,用户等待时间过长,采取了异步上传。发现有时存在系统找不到指定的文件,导致上传失败的问题。
发现问题:网络查找发现是springboot文件上传,会创建临时目录保存临时文件,处理完后会清除。
报错信息如下:
java.io.FileNotFoundException: C:\Users\***\work\Catalina\localhost\ROOT\upload_22e2506c_3d38_4755_8ea1_f81b600f8c2e_00000005.tmp (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.getInputStream(DiskFileItem.java:194)
at org.apache.catalina.core.ApplicationPart.getInputStream(ApplicationPart.java:100)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile.getInputStream(StandardMultipartHttpServletRequest.java:296)
at cn.com.teacher.service.impl.MyCloudStorageServiceImpl.saveFile(MyCloudStorageServiceImpl.java:75)
at cn.com.teacher.service.impl.MyCloudStorageServiceImpl.lambda$saveCloudStorageFile$0(MyCloudStorageServiceImpl.java:64)
at java.util.concurrent.CompletableFuture$AsyncRun.run$$$capture(CompletableFuture.java:1626)
at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
之前代码:
CompletableFuture.runAsync(()->{
saveFile(MultipartFile);
},executor);
现在代码:
InputStream inputStream = multipartFile.getInputStream();
CompletableFuture.runAsync(()->{
//传递流
saveFile(inputStream);
},executor);
解决方案:改为传递流
IOException
出现地点:需要把文件上传到OSS上,流关闭,后面需生成文件的md5值,工具类,也需要InputStream,里面也把流关闭,出现了问题。
发现问题:InputStream只能读取一次,如果需要重复读取,需要转换或者存储。
报错信息:java.io.IOException: Stream Closed
解决方案:InputStream不支持重置,但是它的子类ByteArrayInputStream支持
//InputStream代码
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public boolean markSupported() {
return false;
}
//ByteArrayInputStream代码
protected int mark = 0;
//是否支持重置
public boolean markSupported() {
return true;
}
//定点,下次从哪里重新读取
public void mark(int readAheadLimit) {
mark = pos;
}
//重置到定点位置后,可重新读取
public synchronized void reset() {
pos = mark;
}
把InputStream转换未ByteArrayInputStream,第二次使用前使用重置方法即可
byteArrayInputStream.reset();
下面转换代码取自:InputStream重复使用小技巧_I_am_zz.的博客-CSDN博客_inputstream重复使用
public static ByteArrayInputStream toByteArrayInputStream(InputStream inputStream) throws IOException {
if (inputStream instanceof ByteArrayInputStream) {
return (ByteArrayInputStream) inputStream;
}
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
BufferedInputStream br = new BufferedInputStream(inputStream);
byte[] b = new byte[1024];
for (int c; (c = br.read(b)) != -1; ) {
bos.write(b, 0, c);
}
// 主动告知回收
b = null;
br.close();
inputStream.close();
return new ByteArrayInputStream(bos.toByteArray());
}
}