博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
day18_文件的上传和下载学习笔记
阅读量:6880 次
发布时间:2019-06-26

本文共 16067 字,大约阅读时间需要 53 分钟。

一、文件的上传和下载

1、文件上传的原理分析

什么是文件上传?

    要将客户端(浏览器)数据存储到服务器端,而不将数据直接存储到数据库中,而是要将数据存储到服务器所在的磁盘上,这就要使用文件上传。
为什么使用文件上传?
    通过文件上传,可以将浏览器端的数据直接保存到服务器端。不将数据保存到数据库中,而是保存到服务器磁盘上,这样减少了数据库服务器的压力,对数据的操作更加灵活。

1.1 文件上传的必要前提

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

1.2 enctype属性

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

    可选值

  • application/x-www-form-urlencoded(默认)

        请求消息正文:name=tom&photo=a.txt
        服务器获取数据:String name = request.getParameter("name");

  • multipart/form-data

        请求消息正文:
        服务器获取数据:request.getParameter(String)方法获取指定的表单字段字符内容,但文件上传表单已经不再是字符内容,而是字节内容,所以失效,所以需要字节流的方式。

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

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

2.1 fileupload概述

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

    使用步骤,导入commons-fileupload相关jar包
        commons-fileupload.jar,核心包。
        commons-io.jar,依赖包。

2.2 fileupload的核心类有

    DiskFileItemFactory、ServletFileUpload、FileItem。

    a、解析原理

2.3 fileupload简单应用

    使用fileupload组件的步骤如下:

    1. 创建工厂类DiskFileItemFactory对象
        DiskFileItemFactory factory = new DiskFileItemFactory();
    2. 使用工厂创建解析器对象
        ServletFileUpload fileUpload = new ServletFileUpload(factory);
    3. 使用解析器来解析request对象
        List< FileItem > list = fileUpload.parseRequest(request);


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

    FileItem接口的方法:

        boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,则说明是文件字段

        String getFieldName():获取字段名称,例如:< input type="text" name="username" />,返回的是username。
        String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件。

        String getName():获取文件字段的文件名称(如:a.txt)。

        String getContentType():获取上传的文件的MIME类型,例如:text/plain、image/pjpeg。
        int getSize():获取上传文件的大小
        InputStream getInputStream():获取上传文件对应的输入流

        void write(File file):把上传的文件保存到指定文件中。

        void delete();

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

a、保证服务器的安全

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

b、避免文件被覆盖

    让文件名唯一即可。

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

    方案一:按照日期进行打散存储目录

   
方案二:用文件名的hashCode计算打散的存储目录:二级目录

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

    设置单个文件大小

        ServletFileUpload.setFileSizeMax(字节);
    设置总文件大小:(多文件上传时)
        ServletFileUpload.setSizeMax(字节);

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

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

f、临时文件的问题

    DiskFileItemFactory

        作用:产生FileItem对象。
        DiskFileItemFactory内部有一个缓存,缓存大小默认是10Kb。如果上传的文件超过10Kb,就用磁盘作为缓存。
        存放缓存文件的目录在哪里?:默认是系统的临时目录。

    FileItem.delete();

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

    FileItem.write(File file);

        把上传的文件保存到指定文件,该方式会自动删除临时文件,注意:实际操作不能够自动删除临时文件(即:使用 FileItem自带的方法上传文件)。

    完整的示例代码如下:

package com.itheima.upload; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.UUID; 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; public class UploadServlet2 extends HttpServlet {
@Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// request.setCharacterEncoding("UTF-8"); // 解决服务器接收请求的编码问题,该方式的优先级不高,不好! // 要执行文件上传的操作 // 首先判断表单是否支持文件上传。即:判断 enctype="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("e:\\")); // 指定临时文件的存储目录 // 使用工厂创建解析器ServletFileUpload核心对象 ServletFileUpload sfu = new ServletFileUpload(factory); // 解决上传文件表单项出现乱码问题 sfu.setHeaderEncoding("UTF-8"); // 使用解析器来解析request对象 ,并得到一个表单项的List集合 try {
// 限制上传文件的大小 // sfu.setFileSizeMax(1024 * 1024 * 3); // 表示3M大小的文件 // sfu.setSizeMax(1024 * 1024 * 6); // 多个文件上传时 List
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 (FileUploadBase.SizeLimitExceededException e) {
System.out.println("总文件大小不能超过6M"); // 自定义异常 } catch (FileUploadException e) {
e.printStackTrace(); } } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response); } // 文本表单项,普通文本字段(字符串) private void processFormField(FileItem fileitem) {
try {
String fieldname = fileitem.getFieldName(); // 获取字段名 String fieldvalue = fileitem.getString("UTF-8"); // 获取字段值,并解决上传普通文本表单出的乱码问题 // 解决上传普通文本表单项出现的乱码问题 // fieldvalue = new String(fieldvalue.getBytes("iso-8859-1"), "utf-8"); System.out.println(fieldname + "=" + fieldvalue); } catch (UnsupportedEncodingException 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(); // 文件项框中的值 F:\图片素材\小清新\43.jpg 或者 43.jpg // 处理文件名问题 F:\apache-tomcat-7.0.52\webapps\day18_00_upload\ upload\F:\图片素材\小清新\43.jpg if (filename != null) {
// filename = filename.substring(filename.lastIndexOf(File.separator) + 1); filename = FilenameUtils.getName(filename); // 效果同上 } // 解决文件同名的问题 filename = UUID.randomUUID() + "_" + filename; // 法一:按日期打散(创建)目录 // String childDirectory = makeChildDirectory(storeDirectory); // 2015-10-19 // 法二:用文件名的hashCode计算打散的存储目录:即二级目录 String childDirectory = makeChildDirectory(storeDirectory, filename); // a/b // 在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(); // 如果自己用IO流实现的文件上传,则要在流关闭后,清除临时文件 // 把上传的文件保存到指定文件(使用 FileItem自带的方法上传文件) // fileitem.write(new File(storeDirectory, childDirectory + File.separator + filename)); // fileitem.delete(); } catch (IOException e) {
e.printStackTrace(); } catch (Exception e) {
e.printStackTrace(); } } // 用文件名的hashCode计算打散的存储目录:二级目录 private String makeChildDirectory(File storeDirectory, String filename) {
int hashcode = filename.hashCode(); // 返回字符串转换的32位hashcode码 System.out.println(hashcode); String code = Integer.toHexString(hashcode); // 把hashcode码转换为16进制的字符 System.out.println(code); // 例如:abdsaf2131safsd String childDirectory = code.charAt(0) + File.separator + code.charAt(1); // 取前两位,作为二级目录,例如:a/b // 创建指定目录 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; }*/ }

    一次上传一个文件的form表单代码upload.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>    
Insert title here

    一次上传两个文件的form表单代码upload2.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>    
Insert title here

    动态添加上传按钮上传多个文件upload3.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>    
Insert title here

4、文件的下载

    注意:在web开发中,不适合大的数据下载,通过浏览器进行大的数据下载,不合适,此时需要借助下载软件进行下载,比如:迅雷、电驴、百度网盘等等。

    web开发中,文件下载的应用场景是:从数据库表里面查找数据,动态的生成所需的文件。

    文件下载的示例代码如下:

package com.itheima.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 {
@Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置一个要下载的文件 String filename = "销售榜单.csv"; // csv文件可以用excle打开 // 设置文件名的编码 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"); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response); } }

    重构添加图书信息的示例代码:

package com.itheima.web.servlet; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.fileupload.FileItem; 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.itheima.domain.Book; import com.itheima.service.BookServiceImpl; import com.itheima.util.UUIDUtil; public class AddBookServlet extends HttpServlet {
@Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 创建一个DiskFileItemFactory工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); // 创建一个ServletFileUpload对象 ServletFileUpload sfu = new ServletFileUpload(factory); // 解决上传文件的乱码 sfu.setHeaderEncoding("UTF-8"); // 解析request对象,返回所有表单项 List
fileItems = new ArrayList
(0); // 用于封装普通表单项的数据 Map
map = new HashMap
(); try {
fileItems = sfu.parseRequest(request); // 迭代遍历fileItems表单项 for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()) {
// 普通表单项 String name = fileItem.getFieldName(); // 得到字段名 String value = fileItem.getString("UTF-8"); // 得到字段值 map.put(name, new String[] { value }); // 向map中赋值 } else {
// 文件表单项 InputStream inputStream = fileItem.getInputStream(); String filename = fileItem.getName(); // 得到上传的文件名 String extension = FilenameUtils.getExtension(filename); if (!("jsp".equals(extension) || "exe".equals(extension))) { // 上传的文件不能是jsp、exe // 创建一个文件存盘的目录 File storeDirectory = new File(this.getServletContext().getRealPath("/upload")); if (!storeDirectory.exists()) {
storeDirectory.mkdirs(); // 如果该目录不存在,就创建 } // 处理文件名问题 if (filename != null) {
filename = FilenameUtils.getName(filename); } // 目录打散 String childDirectory = makeChildDirectory(storeDirectory, filename); // a/b // 把上传的文件保存到指定文件(使用 FileItem自带的方法上传文件) filename = childDirectory + File.separator + filename; fileItem.write(new File(storeDirectory, filename)); fileItem.delete(); // 删除临时文件 } map.put(fileItem.getFieldName(), new String[] { filename }); // 将图片表单项的name和value保存到map中 } } Book book = new Book(); BeanUtils.populate(book, map); book.setId(UUIDUtil.getUUID()); // 设置图书编号 // 调用业务逻辑 BookServiceImpl bs = new BookServiceImpl(); bs.addBook(book); // 分发转向 // 不写/代表相对路径,相对于本类的路径 request.getRequestDispatcher("bookListServlet").forward(request, response); } catch (FileUploadException e) {
e.printStackTrace(); } catch (Exception e) {
e.printStackTrace(); } } // request.setCharacterEncoding("UTF-8"); // 获取表单数据 // Book book = new Book(); // try {
// BeanUtils.populate(book, request.getParameterMap()); // book.setId(UUIDUtil.getUUID()); // 手动生成一个随机ID // } catch (Exception e) {
// e.printStackTrace(); // } // // // 调用业务逻辑 // BookServiceImpl bs = newBookServiceImpl(); // bs.addBook(book); // // // 分发转向 // // 不写/代表相对路径,相对于本类的路径 // request.getRequestDispatcher("bookListServlet").forward(request, response); @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response); } // 目录打散 private String makeChildDirectory(File storeDirectory, String filename) {
int hashcode = filename.hashCode(); // 返回字符转换的32位hashcode码 System.out.println(hashcode); String code = Integer.toHexString(hashcode); // 把hashcode转换为16进制的字符 System.out.println(code); // abdsaf2131safsd String childDirectory = code.charAt(0) + File.separator + code.charAt(1); // a/b // 创建指定目录 File file = new File(storeDirectory, childDirectory); if (!file.exists()) {
file.mkdirs(); } return childDirectory; } }

product_list.jsp

我的GitHub地址:
我的博客园地址:
我的蚂蚁笔记博客地址:
Copyright ©2018 黑泽明军
【转载文章务必保留出处和署名,谢谢!】
你可能感兴趣的文章
ES6新特性总结之函数和扩展运算符...
查看>>
[译] Android 生命周期备忘录 — 第三部分:Fragments
查看>>
UICollectionView(集合视图学习笔记)
查看>>
Android TabLayout设置setupWithViewPager标题不显示
查看>>
Mobx入门和较佳实践
查看>>
展望2018,多媒体开发新趋势
查看>>
java callback
查看>>
移动端左滑右滑组件
查看>>
Android开发之AOP编程
查看>>
解决busuanzi_count突然失效的方法(hexo-theme-next)
查看>>
VUE中diff比较
查看>>
新零售技术和意义
查看>>
Coordinator Layout使用
查看>>
基于Mixin Network的C#语言比特币开发教程 : 用 Mixin Messenger 机器人接受和发送比特币...
查看>>
iOS开发之有间距的UITableViewCell
查看>>
Exception和Error的理解
查看>>
D2 日报 2019年2月27日
查看>>
parseInt parseFloat Number
查看>>
http数据协商
查看>>
Python骚操作 | 还原已撤回的微信消息
查看>>