云笔记项目- 上传文件报错"java.lang.IllegalStateException: File has been moved - cannot be read again"
在做文件上传时,当写入上传的文件到文件时,会报错“java.lang.IllegalStateException: File has been moved - cannot be read again”,网上一般说需要配置maxInMemorySize,自己测试发现,maxInMemorySize确实会影响结果,并且项目还跟写入文件到文件夹的transferTo(File dest)方法也有关系,以下是报错截图:
问题说明
通过debug和浏览器提示,浏览器端代码没有问题,问题跟服务端专门处理上传代码并写出到文件夹的部分有关系。以下为代码,其中分别使用了transferTo(),getBytes()和getInputStream()来向文件系统写入文件。
package Web; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile; /**
* 上传文件的控制器
* @author yangchaolin
*
*/
@Controller
@RequestMapping("/file")
public class UploadController { @RequestMapping("/uploadFile.do")
@ResponseBody
public Object uploadFile(MultipartFile userfile1,MultipartFile userfile2) throws IllegalStateException, IOException{
/**
* Spring MVC中可以使用MultipartFile接受上载的文件,文件中的一切数据都可以从此对象中获取
* 比如可以获取文件原始名,文件类型等
*/ //比如获取上载文件的原始文件名,就是文件系统中的文件名
String filename1=userfile1.getOriginalFilename();
String filename2=userfile2.getOriginalFilename();
System.out.println("文件1原始文件名为:"+filename1);
System.out.println("文件2原始文件名为:"+filename2); Map<String,String> map=new HashMap<String,String>(); /**
* 保存上传的文件有三种方法:
* 1 MultipartFile接口的transferTo(File dest)方法
* 将文件直接保存到目标文件,适用于大文件
* 2 MultipartFile接口的getBytes()方法
* 将文件全部读取,返回byte数组,保存在内存,适用于小文件,大文件有爆内存风险
* 3 MultipartFile接口的getInputStream()方法,将文件读取后返回一个InputStream
* 获取上载文件的流,适用于大文件
*/ //mac中保存文件地址 /Users/yangchaolin
//window中保存地址 D:/yangchaolin
//linux中保存地址 /home/soft01/yangchaolin //1 使用transferTo(File dest)
// 创建目标文件夹
File dir=new File("/Users/yangchaolin");
boolean makeDirectoryResult=dir.mkdirs();
System.out.println("文件夹路径是否建立:"+makeDirectoryResult);
//往文件夹放第一个文件
File file=new File(dir,filename1);
userfile1.transferTo(file); /**
* transferTo方法如果不注释掉,后面执行第二种方法写入文件到硬盘会报错
* 报错内容:java.lang.IllegalStateException: File has been moved - cannot be read again
* 原因为transferTo方法底层在执行时,会检查需要写入到OutputStream的文件字节数是否超过MultipartResolver配置的大小,
* 默认设置为10kib,如果超过了,执行完这个方法后会从内存中删除上传的文件,后面再想读取就会报错
*/ //2 使用getInputStream()方法
File file1=new File(dir,filename1);
InputStream isWithoutBuff=userfile1.getInputStream();
//使用FileoutputStream写出到文件
FileOutputStream fosWithoutbuff=new FileOutputStream(file1);
//InputStream一个字节一个字节的读取,将读取到的结果写入到FileOutputStream
int b;//读取一个byte后,以int类型显示数值,范围0~255
while((b=isWithoutBuff.read())!=-1) {
//read()方法每次只读取文件的一个byte
fosWithoutbuff.write(b);
}
isWithoutBuff.close();
fosWithoutbuff.close(); //同样使用InputStream读取,将读取到的结果写入到FileOutputStream,但使用了缓冲字节数组
File file2=new File(dir,filename2);
InputStream isWithBuff=userfile2.getInputStream();
FileOutputStream fosWithBuff=new FileOutputStream(file2);
int n;//保存返回读取到的字节数, 一次8192个字节,当不够时就是实际读取到的字节数
byte[] buff=new byte[8*1024];//8kib的缓冲字节数组
while((n=isWithBuff.read(buff))!=-1) {
System.out.println("读取后的字节数:"+n);
fosWithBuff.write(buff, 0, n);
}
isWithBuff.close();
fosWithBuff.close(); //3 使用getBytes()方法
byte[] data=userfile2.getBytes();
//写出byte数组到文件
File file3=new File(dir,filename2);
FileOutputStream fosWithByte=new FileOutputStream(file3);
fosWithByte.write(data,0,data.length);
fosWithByte.close(); map.put("Result", "upload Success"); return map;//需要导入jackson的三个核心包,否则无法正常转换成JSON } }
此外还跟解析器的属性maxInMemorySize配置也有关系,以下是解析器配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org /schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> <!-- 配置组件扫描 -->
<context:component-scan base-package="Web"></context:component-scan>
<!-- 添加注解驱动 -->
<mvc:annotation-driven></mvc:annotation-driven> <!-- 配置文件上载解析器MultipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<!-- 通过set方法设置以下两个父类属性,父类为CommonsFileUploadSupport.class -->
<property name="maxUploadSize" value="10000000"/> <!--10M大小 -->
<property name="defaultEncoding" value="UTF-8" /> <!-- 文件名编码,可以适用于中文名 --> <!-- <property name="maxInMemorySize" value="10000000" />-->
<!-- 上传文件大小默认小于10kib时,会将文件写入硬盘前保存在内存中,否则就不会保存在内存中 -->
</bean> </beans>
下面简单测试下代码和配置对上传结果的影响:
(1)保留transferTo()代码,并对maxInMemorySize配置10M大小
(2)保留transferTo()代码,对maxInMemorySize不进行配置
(3)注释transferTo()代码,对maxInMemorySize不进行配置
保留transferTo()代码,并对maxInMemorySize配置10M大小
测试结果:可以上传,并且两张图片大小都25KB以上
保留transferTo()代码,对maxInMemorySize不进行配置
测试结果:服务端报错"File has been moved - cannot be read again" ,页面显示和文件查看表明没有上传成功。
注释transferTo()代码,对maxInMemorySize不进行配置
测试结果:
可以上传,并且两张图片大小都25KB以上
问题分析
从测试结果如下:
(1)当maxInMemorySize配置足够大时,就算有transferTo()方法执行也能正常上传
(2)当maxInMemorySize不配置,当文件比较大时,有transferTo()方法执行会报错
(3)当maxInMemorySize不配置,没有transferTo()方法执行,将正常上传文件
影响报错的主要原因为transferTo()方法和maxInMemorySize两者,因此需要查看transferTo方法的源码。
源码查看
transferTo()方法是MultipartFile接口方法,需要查看其实现类方法具体实现,查看发现其实现类为CommonsMultipartFile,查看其具体实现方法,发现其需要确认isAvailable()方法返回的结果,根据其抛出报警内容,发现刚好是项目抛出的异常内容,因此需要继续查看isAvailable()方法的执行。
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
if (!isAvailable()) {
throw new IllegalStateException("File has already been moved - cannot be transferred again");
} if (dest.exists() && !dest.delete()) {
throw new IOException(
"Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted");
} try {
this.fileItem.write(dest);
if (logger.isDebugEnabled()) {
String action = "transferred";
if (!this.fileItem.isInMemory()) {
action = isAvailable() ? "copied" : "moved";
}
logger.debug("Multipart file '" + getName() + "' with original filename [" +
getOriginalFilename() + "], stored " + getStorageDescription() + ": " +
action + " to [" + dest.getAbsolutePath() + "]");
}
}
catch (FileUploadException ex) {
throw new IllegalStateException(ex.getMessage());
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
logger.error("Could not transfer to file", ex);
throw new IOException("Could not transfer to file: " + ex.getMessage());
}
}
isAvailable()方法的代码,发现其需要检查上传的文件是否在内存中,当只有在内存中时,才返回true,否则返回false后抛出异常,因此继续查看isInMemory()方法。
/**
* Determine whether the multipart content is still available.
* If a temporary file has been moved, the content is no longer available.
*/
protected boolean isAvailable() {
// If in memory, it's available.
if (this.fileItem.isInMemory()) {
return true;
}
// Check actual existence of temporary file.
if (this.fileItem instanceof DiskFileItem) {
return ((DiskFileItem) this.fileItem).getStoreLocation().exists();
}
// Check whether current file size is different than original one.
return (this.fileItem.getSize() == this.size);
}
查看发现isInMemory()方法是commons-fileupload.jar包下接口FileItem中定义的,因此继续查看接口实现类,发现为DiskFileItem,并且查看实现类,发现其首先需要检查缓存文件是否存在,如果不存在调用DeferredFileOutputStream的isInMemory方法继续查询。
/**
* Provides a hint as to whether or not the file contents will be read
* from memory.
*
* @return <code>true</code> if the file contents will be read
* from memory; <code>false</code> otherwise.
*/
public boolean isInMemory() {
if (cachedContent != null) {
return true;
}
return dfos.isInMemory();
}
isInMemory方法还会继续调用DeferredFileOutputStream对象dfos的isInMemory方法。
/**
* Determines whether or not the data for this output stream has been
* retained in memory.
*
* @return <code>true</code> if the data is available in memory;
* <code>false</code> otherwise.
*/
public boolean isInMemory()
{
return !isThresholdExceeded();
}
最后发现调用了ThresholdingOutputStream的isThresholdExceeded()方法,具体代码如下,其会检查准备写出到输出流的文件大小,是否超过设定的阈值,这个阈值通过debug发现,就是我们前面配置的参数maxInMemorySize,其默认是10Kib。在本项目中,由于上传的图片都在10Kib大小以上,其都超过了阈值,方法执行返回为true,参数传入到isInMemory方法后,返回false,最终传入到最上层会返回false,从而抛出本次记录的异常。后面将maxInMemorySize设置为10M后,就算有transferTo()方法执行,因上传文件大小分别为20多Kib均为超过阈值,所以能正常上传。
/**
* Determines whether or not the configured threshold has been exceeded for
* this output stream.
*
* @return <code>true</code> if the threshold has been reached;
* <code>false</code> otherwise.
*/
public boolean isThresholdExceeded()
{
return written > threshold;
}
再次验证
为了验证上面的结论,准备了两个文件大小在10Kib以下的文件进行文件上传测试,并且测试不配置maxInMemorySize,同时执行transferTo()方法。测试结果如下:
总结
(1)如果上传文件准备将文件写入到文件夹,抛出异常"java.lang.IllegalStateException: File has been moved - cannot be read again",很有可能跟解析器MultipartResolver的属性maxInMemorySize配置太小有关,由于其默认配置只有10Kib,当上传文件足够大,并且使用了MultipartFile的transferTo()方法写入文件到文件夹时,就会抛出异常。
(2)文件上传时,最好将maxInMemorySize属性配置更大一点。
云笔记项目- 上传文件报错"java.lang.IllegalStateException: File has been moved - cannot be read again"的更多相关文章
- Azkban上传文件报错installation Failed.Error chunking
azkaban 上传文件报错Caused by: java.sql.SQLException: The size of BLOB/TEXT data inserted in one transacti ...
- Tomcat上传文件报错:returned a response status of 403 Forbidden
出现这样的错误是没有权限对服务器进行写操作.需要在这个项目所在的tomcat中配置可写操作即可: 在tomcat的web.xml添加下面代码: <init-param><param- ...
- Linux - xshell上传文件报错乱码
xshell上传文件报错乱码,解决方法 rz -be 回车 下载sz filename
- rz上传文件报错:rpm Read Signature failed: sigh blob(1268): BAD, read returned 0
上传文件报错: [root@www localdisk]# rpm -ivh cobbler* error: cobbler-2.8.4-4.el7.x86_64.rpm: rpm Read Si ...
- eclipse启动报错java.lang.IllegalStateException: LifecycleProcessor not initialized - call 'refresh' befo
报错: java.lang.IllegalStateException: LifecycleProcessor not initialized - call 'refresh' before invo ...
- springboot测试启动报错java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test
springboot测试启动报错: java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you ne ...
- SecureCRT sftp上传文件报错:put: failed to upload xxx 拒绝访问
1.问题 使用sftp上传文件时报错:put: failed to upload xxx 拒绝访问.类似下图所示: 2.原因 造成这个问题的原因可能有两个,一是要上到的那个目录剩余磁盘空间不足,二是打 ...
- servlet上传文件报错(三)
1.具体报错如下 null null Exception in thread "http-apr-8686-exec-5" java.lang.OutOfMemoryError: ...
- Spring使用mutipartFile上传文件报错【Failed to instantiate [org.springframework.web.multipart.MultipartFile]】
报错场景: 使用SSM框架实现文件上传时报“Failed to instantiate [org.springframework.web.multipart.MultipartFile]”错,控制器源 ...
随机推荐
- 采用Anaconda平台调用pymc3时出现错误的解决方法
提示:(1)module 'theano' has no attribute 'gof',c++编辑出现错误 (2)stdio.h file not found 解决方法:(1)在终端中输入 xcod ...
- C++指针速记
基本原则:指针类型变量存储的就是地址! 1.数组名就是数组首元素的地址** int age[3]; int* p = age; 2.使用new操作符实际上是向操作系统申请一块内存(包含类型信息),返回 ...
- bzoj5108: [CodePlus2017]可做题
Description qmqmqm希望给sublinekelzrip出一道可做题.于是他想到了这么一道题目:给一个长度为n的非负整数序列ai,你需 要计算其异或前缀和bi,满足条件b1=a1,bi= ...
- Java核心-多线程-并发控制器-Exchanger交换器
1.基本概念 Exchanger,从名字上理解就是交换.Exchanger用于在两个线程之间进行数据交换,注意也只能在两个线程之间进行数据交换. 线程会阻塞在Exchanger的exchange方法上 ...
- EL表达式取Map,List值的总结
EL表达式取Map中的值:后台action 中: Map map = new HashMap(); map.put(key1,value1); map.put(key2,value2); map.pu ...
- SSM框架-MyBatis框架数据库的增删查改操作
话不多说,在User.xml文件中主要写一下操作数据库的sql语句,增,删,查,改是最常见的数据库操作 User.xml文件下:
- homestead安装
所谓Homestead,其实就是一个虚拟机镜像. 为什么用它?它的优点可以去自行百度.(虽然我还是用了集成环境 xampp,但是我还是不推荐的,特别是统一开发环境.或者去使用诸如Laravel的框架, ...
- Linux下搭建jmeter
最近做性能测试,Windows下跑jmeter,并发跑不到100,CPU就100%,这还是在命令行模式下,真心头大.没办法,只好搞个Linux来跑了,下面说下如何玩转的. 1.下载Ubuntu操作系统 ...
- Redis深入学习笔记(五)Redis阻塞原因
在实际使用Redis中,有时会碰到客户端timeout异常,或者没有可用连接异常等等异常,总结大概有如下原因: 内部阻塞原因: 1)大对象存取. 2)Fork阻塞. 3)Aof刷盘阻塞(距离上次刷盘大 ...
- <<让你自己的APP成为系统应用>>所遇到的问题及解决方法
1.adb connect 10.100.1.772.adb -s 10.100.1.77 shell remount3.让你自己的APP成为系统应用 adb push xxx.apk system/ ...