建立网站兴田德润电话多少,本地南昌网站建设公司,温州手机网站开发,wordpress去掉google字体相信很多人对于java文件下载的过程都存在一些疑惑#xff0c;比如下载上传文件会不会占用vm内存#xff0c;上传/下载大文件会不会导致oom。下面从字节流的角度看下载/上传的实现#xff0c;可以更加深入理解文件的上传和下载功能。
文件下载
首先明确#xff0c;文件下载…相信很多人对于java文件下载的过程都存在一些疑惑比如下载上传文件会不会占用vm内存上传/下载大文件会不会导致oom。下面从字节流的角度看下载/上传的实现可以更加深入理解文件的上传和下载功能。
文件下载
首先明确文件下载不仅仅只有下载方还有服务端也就是返回文件的服务器 那么看一个简易文件服务器返回下载的文件。
服务端
这里是使用springMvc实现 GetMapping(download)public void downFile(HttpServletResponse response) throws IOException {response.setContentType(application/octet-stream);response.setHeader(Content-Disposition, attachment; filename test.jhprof);File file new File(D:\\heap\\heapDump.hprof);InputStream in new FileInputStream(file);OutputStream out response.getOutputStream();byte[] buffer new byte[1024];int len 0;while ((len in.read(buffer)) 0) {out.write(buffer, 0, len);}in.close();out.close();}这里每次从文件流中读取1024个字节输出是因为如果读取太多字节会给内存造成压力我们这里使用的是java的堆内存。如果直接完整读取整个文件那么可以导致oom。
java.lang.OutOfMemoryError: Java heap space客户端 URL url new URL(http://localhost:8062/fallback/download);URLConnection conn url.openConnection();InputStream in conn.getInputStream();FileOutputStream fileOutputStream new FileOutputStream(D:\\tmp\\a.hrpof);byte[] buffer new byte[1024];int len 0;while ((len in.read(buffer)) 0) {fileOutputStream.write(buffer, 0, len);}in.close();fileOutputStream.close();同样在下载文件时也需要注意不要一次读取特别多的字节数 测试过程发现由于TCP发送缓冲区和接受缓冲区有限当缓冲区满之后就会阻塞例如下载方的速度满服务端的文件不断写到缓冲区缓冲区满了就无法继续写入那么就会导致在执行write方法时暂时阻塞。等到接收端接受到数据了才能继续写入。
文件上传
服务端
http是支持多个文件进行上传的文件数据都在请求体中多个文件之间可以通过分隔符区分 例如上传两个文本文件 请求大概长这样
1.HTTP上传methodpost,enctypemultipart/form-data;
2.计算出所有上传文件的总的字节数作为Content-Length的值
3.设置
Content-Type:multipart/form-data; boundary----WebKitFormBoundaryJ9RUA0QCk13RaoAp
4.多个文件数据:请求体
------WebKitFormBoundaryJ9RUA0QCk13RaoAp
Content-Disposition: form-data; namepic; filenamethunder.gif
Content-Type: image/gif
这中间是文件的二进制数据------WebKitFormBoundaryJ9RUA0QCk13RaoAp
Content-Disposition: form-data; namepic; filenameuuu.gif
Content-Type: image/gif
这中间是文件的二进制数据
------WebKitFormBoundaryJ9RUA0QCk13RaoAp--
服务端使用springMvc接收上传文件的写法多个文件 RequestMapping(method RequestMethod.POST,value /uploadModel)public void uploadModel(RequestPart(value file, required true) ListMultipartFile file, RequestParam Integer type) {··········}这样就可以直接获取到上传的文件。 具体接受过程还要看springMvc的实现 在doDispatch方法中会是否按照文件进行处理 判断方式也很简单检查请求头的multipart
Overridepublic boolean isMultipart(HttpServletRequest request) {return StringUtils.startsWithIgnoreCase(request.getContentType(), multipart/);}如果是文件类型那么就要通过IO流将文件下载到本地 springMvc的大致实现如下 原代码位置org.apache.tomcat.util.http.fileupload.FileUploadBase#parseRequest FileItemIterator iter getItemIterator(ctx);FileItemFactory fileItemFactory Objects.requireNonNull(getFileItemFactory(), No FileItemFactory has been set.);final byte[] buffer new byte[Streams.DEFAULT_BUFFER_SIZE];//8KB的字节数组用于读取字节流while (iter.hasNext()) {final FileItemStream item iter.next();// Dont use getName() here to prevent an InvalidFileNameException.final String fileName ((FileItemStreamImpl) item).getName();FileItem fileItem fileItemFactory.createItem(item.getFieldName(), item.getContentType(),item.isFormField(), fileName);items.add(fileItem);try {Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);} catch (FileUploadIOException e) {throw (FileUploadException) e.getCause();} catch (IOException e) {throw new IOFileUploadException(String.format(Processing of %s request failed. %s,MULTIPART_FORM_DATA, e.getMessage()), e);}final FileItemHeaders fih item.getHeaders();fileItem.setHeaders(fih);}successful true;return items;根据分隔符找出上传的多个文件进行读取字节流同时创建本地文件写入到本地文件这里循环通过8kb数组读取到内存再写到文件是为了防止文件过大造成占用内存大。
public static long copy(InputStream inputStream,OutputStream outputStream, boolean closeOutputStream,byte[] buffer)throws IOException {OutputStream out outputStream;InputStream in inputStream;try {long total 0;for (;;) {int res in.read(buffer);if (res -1) {break;}if (res 0) {total res;if (out ! null) {out.write(buffer, 0, res);}}}if (out ! null) {if (closeOutputStream) {out.close();} else {out.flush();}out null;}in.close();in null;return total;} finally {IOUtils.closeQuietly(in);if (closeOutputStream) {IOUtils.closeQuietly(out);}}}从这里我们也能看出来http请求不并不是tomcat服务器接收进完全接收的而是先接收请求头进行就开始进行处理了至于后面要不要读取请求提如何读取就要看程序员的代码了这也是程序员可以控制的。
客户端
文件上传的客户端逻辑比较复杂
package javaio;import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;/*** author liuxishan 2023/9/3*/public class FileUpload {public static void main(String[] args) {String reslut null;MapString, File files new HashMap() {{put(0.png, new File(C:\\Users\\lxs\\Desktop\\0.png));put(1.jpg, new File(C:\\Users\\lxs\\Desktop\\1.png));put(big.herof, new File(D:\\heap\\heapDump.hprof));}};try {String BOUNDARY java.util.UUID.randomUUID().toString();String PREFIX --, LINEND \r\n;String MULTIPART_FROM_DATA multipart/form-data;String CHARSET UTF-8;URL uri new URL(http://localhost:8062/fallback/uploadModel?type1);HttpURLConnection conn (HttpURLConnection) uri.openConnection();// conn.setChunkedStreamingMode(0);conn.setReadTimeout(100 * 1000);conn.setDoInput(true);// 允许输入conn.setDoOutput(true);// 允许输出conn.setUseCaches(false);conn.setRequestMethod(POST); // Post方式conn.setRequestProperty(connection, keep-alive);conn.setRequestProperty(Charsert, UTF-8);conn.setRequestProperty(Content-Type, MULTIPART_FROM_DATA ;boundary BOUNDARY);conn.connect();// 首先组拼文本类型的参数StringBuilder sb new StringBuilder();OutputStream outStream conn.getOutputStream();outStream.flush();// 发送文件数据if (files ! null)
// for (Map.EntryString, File file : files.entrySet()) {for (String key : files.keySet()) {StringBuilder sb1 new StringBuilder();sb1.append(PREFIX);sb1.append(BOUNDARY);sb1.append(LINEND);sb1.append(Content-Disposition: form-data; name\file\; filename\ key \ LINEND);sb1.append(Content-Type: multipart/form-data; charset CHARSET LINEND);sb1.append(LINEND);outStream.write(sb1.toString().getBytes());File valuefile files.get(key);InputStream is new FileInputStream(valuefile);byte[] buffer new byte[1024];int len 0;while ((len is.read(buffer)) ! -1) {outStream.write(buffer, 0, len);}is.close();outStream.write(LINEND.getBytes());}// 请求结束标志byte[] end_data (PREFIX BOUNDARY PREFIX LINEND).getBytes();outStream.write(end_data);outStream.flush();// 得到响应码
// success conn.getResponseCode()200;InputStream in conn.getInputStream();InputStreamReader isReader new InputStreamReader(in);BufferedReader bufReader new BufferedReader(isReader);String line null;reslut ;while ((line bufReader.readLine()) ! null)reslut line;outStream.close();conn.disconnect();} catch (Exception e) {e.printStackTrace();}}}这里需要注意的是在使用HttpURLConnection 上传大文件时出现内存溢出的错误这让我产生了错觉输入和输出流咋会暂用内存不就是一个数据传送的管道么都没有把数据读取到内存中为撒会报错。。。然后就纠结了。。。 不过实在与原来的经验相违背然后写了一个示例直接从file中读出然后写入到输出流中发现并没有问题 查HttpURLConnection api发现其有缓存机制数据并没有实时发送到网络而是先缓存再发送导致内存溢出。 解决办法 httpConnection.setChunkedStreamingMode(0); //不使用HttpURLConnection的缓存机制直接将流提交到服务器上。 需要注意的是我们经常使用的hutool的http也存在这个问题 如果不指定chunkedStreamingMode也会出现oom的问题 对于大文件可以这么进行指定。 HttpResponse response HttpUtil.createPost(http://localhost:8062/fallback/uploadModel?type1).setChunkedStreamingMode(0).form(file, new File(D:\\heap\\heapDump.hprof)).execute();