1、文件上传的原理分析

1.1文件上传的必要前提:

  • a、提供form表单,method必须是post
  • b、form表单的enctype必须是multipart/form-data
  • c、提供input type="file"类的上传输入域

1.2enctype属性

作用:告知服务器请求正文的MIME类型。(请求消息头:Content-Type作用是一致的)

可选值:application/x-www-form-urlencoded(默认):

正文:name=admin&password=123

服务器获取数据:String name = request.getParameter("name");

multipart/form-data:

正文

服务器获取数据:request.getParameter(String)方法获取指定的表单字段字符内容,但文件上传表单已经不在是字符内容,而是字节内容,所以失效。

文件上传:解析请求正文的每部分的内容。

2、借助第三方的上传组件实现文件上传

2.1 fileupload概述

fileupload是由apache的commons组件提供的上传组件。它最主要的工作就是帮我们解析request.getInputStream()。

导入commons-fileupload相关jar包

  1. commons-fileupload.jar,核心包;
  2. commons-io.jar,依赖包。

2.2 fileupload的核心类有:

  1. DiskFileItemFactory  和磁盘打交道(工厂模式)
  2. ServletFileUpload   核心对象  操作fileitem  
  3. FileItem         表单对象

a、解析原理

2.3 fileupload简单应用

使用fileupload组件的步骤如下:

创建工厂类DiskFileItemFactory对象:  DiskFileItemFactory factory = new DiskFileItemFactory()

使用工厂创建解析器对象:         ServletFileUpload fileUpload = new ServletFileUpload(factory)

使用解析器来解析request对象:      List<FileItem> list = fileUpload.parseRequest(request)

FileItem对象对应一个表单项(表单字段)。可以是文件字段或普通字段

  • boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,说明是文件字段;
  • String getFieldName():获取字段名称,例如:<input type=”text” name=”username”/>,返回的是username;
  • String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件;
  • String getName():获取文件字段的文件名称;(a.txt)
  • String getContentType():获取上传的文件的MIME类型,例如:text/plain。
  • int getSize():获取上传文件的大小;
  • InputStream getInputStream():获取上传文件对应的输入流;
  • void write(File):把上传的文件保存到指定文件中。
  • delete();

3、文件上传时要考虑的几个问题(经验分享)

a、保证服务器的安全

  把保存上传文件的目录放在用户直接访问不到的地方。

b、避免文件被覆盖

  让文件名唯一即可

c、避免同一个文件夹中的文件过多

  • 方案一:按照日期进行打散存储目录
  • 方案二:用文件名的hashCode计算打散的存储目录:二级目录

d、限制文件的大小:web方式不适合上传大的文件

  • 单个文件大小:ServletFileUpload.setFileSizeMax(字节)
  • 总文件大小:(多文件上传)ServletFileUpload.setSizeMax(字节)

e、上传字段用户没有上传的问题

  通过判断文件名是否为空即可

f、临时文件的问题

DiskFileItemFactory:  作用:产生FileItem对象

内部有一个缓存,缓存大小默认是10Kb。如果上传的文件超过10Kb,用磁盘作为缓存。

存放缓存文件的目录在哪里?默认是系统的临时目录。

如果自己用IO流实现的文件上传,要在流关闭后,清理临时文件。FileItem.delete();

 package com.upload;

 import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.logging.SimpleFormatter; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils; import com.util.UUIDUtil; public class UploadServlet2 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//执行文件上传的操作
//判断是否写了那个multipart/form-data 即是否支持文件上传
boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);
if(!isMultipartContent){
throw new RuntimeException("your form is not multipart/form-data ");
}
//创建一个DiskFileItemfactory工厂类
DiskFileItemFactory factory = new DiskFileItemFactory();
//保存临时文件的目录
factory.setRepository(new File("f:\\"));
//创建一个servletFileUpload核心对象(表单解析器)
ServletFileUpload sfu = new ServletFileUpload(factory); //解析request对象,并得到一个表单项的集合
try {
//sfu.setFileSizeMax(1024*1024*3);//3M sfu.setFileSizeMax(1024*1024*36); List<FileItem> fileItems = sfu.parseRequest(request);
//遍历表单项
for (FileItem fileItem : fileItems) {
if(fileItem.isFormField()){
//普通表单项
processFormField(fileItem);
}else{
//上传表单项
processUploadField(fileItem);
}
} }catch (FileUploadBase.FileSizeLimitExceededException e) {
//throw new RuntimeException("文件过大,不能超过3M");
System.out.println("文件过大,不能超过3M");
}
catch (FileUploadException e) {
e.printStackTrace();
}
}
//上传表单项
private void processUploadField(FileItem fileItem) { try {
//得到文件输入流
InputStream is = fileItem.getInputStream();
//创建一个文件存盘的目录
String directoryRealPath = this.getServletContext().getRealPath("/WEB-INF/upload");
//既代表文件有代表目录
File storeDirectory = new File(directoryRealPath);
if(!storeDirectory.exists()){
storeDirectory.mkdirs();
}
//得到文件名
String filename = fileItem.getName();
//filename = filename.substring(filename.lastIndexOf(File.separator)+1);
if(filename!=null){
filename = FilenameUtils.getName(filename);
if(filename=="")
System.out.println("空文件");
}
//解决重名问题 UUID亦可以
filename = UUIDUtil.getUUID()+"_"+filename;
//目录打散
//String childDirectory = makeChildDirectory(storeDirectory); String childDirectory = makeChildDirectory(storeDirectory,filename); //在storeDirectory目录下构建一个完整目录下的文件
File file = new File(storeDirectory,childDirectory+File.separator+filename);
//通过文件输出流将上传的文件保存到磁盘
FileOutputStream fos = new FileOutputStream(file);
int len = 0;
byte[] b = new byte[1024];
while((len = is.read(b))!= -1){
fos.write(b,0,len);
}
fos.close();
is.close();
fileItem.delete();
} catch (IOException e) {
e.printStackTrace();
} }
//按目录打散
private String makeChildDirectory(File storeDirectory, String filename) {
int hascode = filename.hashCode();//返回字符串转换的32位hascode码 int值 String code = Integer.toHexString(hascode);//把hascode转换成十六进制的字符 assdas3234af String childDirectory = code.charAt(0)+File.separator+code.charAt(1);
File file = new File(storeDirectory,childDirectory);
if(!file.exists()){
file.mkdirs();
}
return childDirectory;
}
/*//按日期打散
private String makeChildDirectory(File storeDirectory) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateDirectory = sdf.format(new Date());
//只管创建目录
File file = new File(storeDirectory,dateDirectory);
if(!file.exists()){
file.mkdirs();
}
return dateDirectory;
}*/
//普通表单项
private void processFormField(FileItem fileItem) {
String fieldname = fileItem.getFieldName();
String fieldvalue = fileItem.getString();
System.out.println(fieldname+"="+fieldvalue); } public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
} }

中文编码问题

 private void processUploadField(FileItem fileItem) {
// 得到文件输入流
try {
InputStream is = fileItem.getInputStream();
// 创建一个文件存盘的目录
String directoryRealPath = this.getServletContext().getRealPath(
"/WEB-INF/upload");
// 既代表文件有代表目录
File storeDirectory = new File(directoryRealPath);
if (!storeDirectory.exists()) {
storeDirectory.mkdirs();
}
// 得到文件名
String filename = fileItem.getName();
if (filename != null) {
filename = FilenameUtils.getName(filename);
if (filename == "")
System.out.println("空文件");
}
// 解决重名问题 UUID亦可以
filename = UUIDUtil.getUUID() + "_" + filename;
String childDirectory = makeChildDirectory(storeDirectory, filename);
// 上传文件自动删除临时文件
/*
* fileItem.write(new
* File(storeDirectory,childDirectory+File.separator+filename));
* fileItem.delete();
*/
// 上传文件,自动删除临时文件
fileItem.write(new File(storeDirectory, childDirectory
+ File.separator + filename));
fileItem.delete();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}

注:这里32行的fileItem.delete();是多余的,请看fileItem.write的帮助文档:(意思大概是说只想去上传,不会保留临时文件)

而有个问题是在所设置的目录下却还会出现临时文件这是为什么呢??

原来是在List<FileItem> fileItems = sfu.parseRequest(request);就创建了 在write里可以自动删除掉temp。

下面放一张大佬给的示例(佐证 在有inputstream时 file无法delete和renameTo操作)

4、文件的下载

 package com.servlet;

 import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class DownloadServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//设置一个要下载的文件
String filename = "销售榜单.csv"; //设置文件名的编码
if(request.getHeader("user-agent").toLowerCase().contains("msie")){
filename = URLEncoder.encode(filename, "UTF-8");//将不安全的文件名改为UTF-8格式
}else{
filename = new String(filename.getBytes("UTF-8"),"iso-8859-1");//火狐浏览器
}
//告知浏览器要下载文件
response.setHeader("content-disposition", "attachment;filename="+filename);
//response.setHeader("content-type", "image/jpeg");
//根据文件名自动获得文件类型
response.setContentType(this.getServletContext().getMimeType(filename));
//告知服务器使用什么编码
response.setCharacterEncoding("UTF-8");
//创建一个文件输出流
PrintWriter out = response.getWriter();
out.write("电视机,20\n");
out.write("洗衣机,10\n");
out.write("冰箱,8\n");
} public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
} }

javaweb 之 文件上传与下载的更多相关文章

  1. JavaWeb:实现文件上传与下载

    JavaWeb:实现文件上传与下载 文件上传前端处理 本模块使用到的前端Ajax库为Axio,其地址为GitHub官网. 关于文件上传 上传文件就是把客户端的文件发送给服务器端. 在常见情况(不包含文 ...

  2. 深入分析JavaWeb Item40 -- 文件上传和下载

    在Web应用系统开发中,文件上传和下载功能是很经常使用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传.浏览器在上传的过程中是将文件以流的形式提交到server端的.假设 ...

  3. JavaWeb之文件上传和下载

    在如今的互联网时代,人们越来越喜欢将自己的数据存放到互联网上,于是便诞生了很多类型的软件,比如360网盘,百度网盘,云盘之类的.所以说,文件上传和下载的功能是现在非常主流的一个功能,应用十分广泛. 那 ...

  4. javaWeb实现文件上传与下载 (转)

    文件上传概述 实现web开发中的文件上传功能,需完成如下二步操作: 在web页面中添加上传输入项 在servlet中读取上传文件的数据,并保存到本地硬盘中. 如何在web页面中添加上传输入项? < ...

  5. JavaWeb之文件上传、下载

    时间:2016-12-17 18:07 --文件上传概述上传不能使用BaseServlet1.文件上传的作用    例如网络硬盘,就是用来上传和下载文件的.2.文件上传对表单的限制    1)必须使用 ...

  6. JavaWeb:实现文件上传

    JavaWeb:实现文件上传 理解文件上传: 1.上传文件就是把客户端的文件发送给服务器端. 2.HTTP响应的正文部分最常见的是HTML文档,但是也可以是其他任意格式的数据,如图片和声音文件中的数据 ...

  7. (转载)JavaWeb学习总结(五十)——文件上传和下载

    源地址:http://www.cnblogs.com/xdp-gacl/p/4200090.html 在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传 ...

  8. JavaWeb学习总结,文件上传和下载

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...

  9. JavaWeb学习总结(五十)——文件上传和下载

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...

随机推荐

  1. Verilog之openMSP430(1)

    openMSP430_IO interrupt Verilog file: omsp_gpio.v //================================================ ...

  2. DIV+CSS左右两列自适应高度的方法

    我们在用DIV+CSS布局网页的时候,必然会遇到左右两列自适应高度的问题,就是左边列的背景会随着右边列内容的增加也相应的增加高度,下面就教大家DIV+CSS左右两列自适应高度的方法. 下面给出最终的效 ...

  3. css3实现动画滚动条

    先给大家一张效果图,看似简单,其实实现起来....那也是非常简单的~简单又实用 黑框里面的字体会自动滚动,形成滚动条,可以用于展示和提示,首先我们先要在body里面写上自己想要的文字,比如我想写:感觉 ...

  4. box-shadow 阴影剖析

    box-shadow的四个值分别是左右偏移,上下偏移,向四周模糊扩算,距离四周边缘的距离,最后一个是阴影的颜色值,如图示例: 上代码,复制代码,就可以看到上图的效果 <!DOCTYPE html ...

  5. 应用四:Vue之VUEX状态管理

    (注:本文适用于有一定Vue基础或开发经验的读者,文章就知识点的讲解不一定全面,但却是开发过程中很实用的) 概念:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应 ...

  6. 微信App支付:微信支付的appid,appsecret,商户号mch_id,微信交易支付密钥(mch_key)在哪里查看

    1-1) 查看微信支付 appid 的方法 微信支付使用的 appid, 是微信服务号的 appid, 需要你登录微信服务号后台, 在 开发-基本配置/开发者ID(AppID) 中查看微信支付 app ...

  7. spring重点一:处理对象创建时间 个数以及方式

    /** * 1) 对象创建: 单例/多例(个数) * scope="singleton", 默认值, 即 默认是单例 [service/dao/工具类] *  scope=&quo ...

  8. TensorFlow+实战Google深度学习框架学习笔记(12)------Mnist识别和卷积神经网络LeNet

    一.卷积神经网络的简述 卷积神经网络将一个图像变窄变长.原本[长和宽较大,高较小]变成[长和宽较小,高增加] 卷积过程需要用到卷积核[二维的滑动窗口][过滤器],每个卷积核由n*m(长*宽)个小格组成 ...

  9. C++进阶 STL(2) 第二天 一元/二元函数对象、一元/二元谓词、stack容器、queue容器、list容器(双向链表)、set容器、对组、map容器

    01 上次课程回顾 昨天讲了三个容器 string  string是对char*进行的封装 vector 单口容器 动态数组 deque(双端队列) 函数对象/谓词: 一元函数对象: for_each ...

  10. SpringBoot2整合activiti6环境搭建

    SpringBoot2整合activiti6环境搭建 依赖 <dependencies> <dependency> <groupId>org.springframe ...