自己正在模仿SpringMVC封装Servlet, 在处理文件上传时, 之前一直都用的是开源框架实现, 于是很好奇他们是怎么实现的, 就网上查资料动手实现一下.
结果万万没想到上传的文件中间多出了一段奇怪的数据
过程如下:
1. 前端发送表单数据, 一张图片和几个name-value, F12可以看到数据发送的格式
------WebKitFormBoundary 加上一些随机字符作为分隔标志, 两个分隔之间是具体的数据, 图片二进制数据就没有显示出来
2. 后端直接request.getInputStream把数据读出来再写到文件里, 大概的代码如下:
DataInputStream formDataReader = new DataInputStream(request.getInputStream());
String savePath = request.getServletContext().getRealPath("/files/");
new File(savePath).mkdirs();
String fileName = String.valueOf(System.currentTimeMillis() + (int)(Math.random() * 100000));
File outputFile = new File(savePath + fileName);
boolean createResult = outputFile.createNewFile();
if(!createResult){
throw new Exception("无法上传文件");
}
DataOutputStream formDataWriter = new DataOutputStream(new FileOutputStream(outputFile));
byte[] chunk = new byte[1024];
while(formDataReader.read(chunk) != -1){
formDataWriter.write(chunk);
}
formDataWriter.flush();
formDataWriter.close();
这个时候用01Editor打开文件, 对比原来的图片, 发现头尾多了------WebKitFormBoundaryN1Nz7fpJA9ObLth4之类的表单信息, 去除即可, 之后改成图片后缀打开, 发现图片的内容显示不完整
用Beyond Compare比较后发现, 文件比原文件在中间部分多出了一部分奇怪的数据
于是乎百思不得其解
对比其他人的代码中发现问题所在
将代码
byte[] chunk = new byte[1024];
while(formDataReader.read(chunk) != -1){
formDataWriter.write(chunk);
}
改成
byte[] chunk = new byte[1024];
int len = -1;
while((len = formDataReader.read(chunk)) != -1){
formDataWriter.write(chunk, 0, len);
}
formDataWriter.flush();
formDataWriter.close();
之后上传图片, 将头尾的表单数据删去, 图片与原图一模一样
由此可见, InputStream读取数据还未读到结尾前, 每次并非读取完整的byte[] 数组, 因此写数据时, 最好用
OutputStream.write(byte[] data, 0, length);
而非
OutputStream.write(byte[] data);
否则可能上次读取1024个byte的数据, 这次读取512个byte, 结果把这次的512个byte和上次存数组后面的512个byte一起写出去, 就会导致出错.
这个问题困扰了我一天, 换了很多张图片都有这种情况, 且恰巧是中间一小段数据异常
看来等有空得好好看下Java源码好好研究下 😋