文件上传与下载

文件上传

  • 文件上传:就是将客户端资源,通过网络传递到服务器端
  • 原因:因为数据比较大,必须通过文件上传才可以完成将数据保存到服务器端操作。
  • 文件上传的本质:IO流的操作
<form action = "${pageContext.request.contextPath}/upload1" method="post">
    <input type="file" name="f"><br>
    <input type="submit" value="上传">
</form>

文件上传注意事项

  • 浏览器端:
    • method=post 只有post才可以携带大数据
    • 必须使用<input type='file' name='f'>要有name属性
    • 默认值encType=”application/x-www-form-urlencoded”
    • 设置值:encType=”multipart/form-data”
  • 服务器端:
    • request对象是用于获取请求信息的。它有一个方法 getInputStream();可以获取一个字节输入流,通过这个流可以读取到所有的请求正文信息。
  • 文件上传原理:
    • 浏览器注意上述三件事,在服务器端通过流将数据读取到,再对数据进行解析。将上传文件内容得到,保存在服务器端,就完成了文件上传。
  • 在实际开发中,不需要我们进行数据解析,完成文件上传。文件上传的工具(插件),已经封装好了,提供API,只要调用他们的API就可以完成文件上传。
  • commons-fileupload,它是Apache提供的一套开源免费的文件上传工具。

这里写图片描述

如何在Servlet中读取文件上传数据,并保存到本地硬盘中

  • Request 对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件的数据是一项非常麻烦的工作。
  • 为方便用户处理文件上传数据,Apache开源组织提供了一个用来处理表单文件上传的一个开源组件(Commons-fileupload),该组件性能优异,并且其API使用及其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。
  • 使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支持jar包:Commons-fileupload和commons-io。commons-io不属于文件上传组件的开发jar文件,但Commons-fileupload组件从1.1版本开始,它工作时需要commons-io包的支持。
  • 使用commons-fileupload
    • 导入jar包
      • commons-fileupload-1.2.1.jar 文件上传
      • commons-io-1.4.jar 它是提供的IO工具
  • commons-fileupload的三个核心
    • DiskFileItemFactory类
    • ServletFileUpload类
    • FileItem类

快速入门

  • 创建upload2.jsp页面
    <form action= "${pageContext.request.contextPath}/upload2" method = "post" encType="multipart/form-data">
    <input type="file" name="f"><br>
    <input type="submit" value="上传">
    </form>
  • 创建Upload2Servlet
    • 创建一个DiskFileItemFactory
      • DiskFileItemFactory factory = new DiskFileItemFactory();
    • 创建ServletFileUpload类
      • ServletFileUpload upload = new ServletFileUpload(factory);
    • 解析所有上传数据
      • List<FileItem> items=upload.parseRequest(request);
  • 遍历items集合,集合中的每一项,就是一个上传数据。
    • isFormField();
    • getFileName();
      • 返回值String,得到组件名称<input name="">
    • getName();
      • 返回值是String,得到的是上传文件的名称
      • 注意:浏览器不同,它们得到的效果不一样
        • 包含全路径名称 例如:c:\User\Administrator\Desktop\a.txt
        • 只包含上传文件名称 例如:a.txt
    • getString();
      • 这个方法可以 获非上传组件的内容,相当于 getParameter方法作用
      • 如果是上组件,上传的文件是文本文件,可以获取到文本文件的内容,如果不是文本文件,例如一张图片,这样获取并不合适。
    • 获取上传文件的内容,保存到服务器端
      • item.getInputStream();它是用于读取上传文件内容的输入流。
      • 使用文件复制操作就可以完成文件上传
    • *
public class Upload2Servlet extends HttpServlet{
    public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
        //创建DiskFileItemFactory
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //创建ServletFileUpload类
        ServletFileUpload upload = new ServletFileUpload(factory);
        try{
                    //底层通过request获取数据,进行解析,将解析的数据封装到List<FileItem>
            List<FileItem> items= upload.parseRequest(request);
            //遍历集合
            for(FileItem item:items){
                if(item.isFormField()){
//这就是得到了<input type = "text" name = "content">这样的组件             
                    String filedName=item.getFiledName();
                    System.out.println(filedName);
                    String name = item.getName();

                }else{
            //这就是得到了<input type = "file">这样的组件
                    String fieldName = item.getFieldName();
                    System.out.println("上传组件的名称:"+fileName); 
                    String name = item.getName();//上传文件名称
                    name = name.subString(name.lastIndexOf("\\")+1);

                //获取上传文件内容
                InputString is = item.getInputStream();
                byte[] b = new byte[1024];
                int len = -1;
                FileOutputStream fos = new FileOutputStream("d:/upload/"+name);
                while((len=is.read(b)) != -1){
                    fos.write(b,0,len);

                }
                fos.close();
                is.close();
                }
            }
        }catch(FileUploadException e){
            e.printStackTrace();
        }

    }

}

这里写图片描述

//获取上传文件内容,完成文件上传操作
FileOutputStream fos = new FileOutputStream("d:/upload/"+name);
IOUtils.copy(item.getInputStream(),output);

核心API介绍

  • DiskFileItemFactory
    • 作用:可以设置缓存大小以及临时文件保存位置
    • 默认缓存大小是 10240(10k)
    • 临时文件默认存储在系统的临时文件目录下(可以在环境变量中查看)
    • new DiskFileItemFactory();
      • 缓存大小与临时文件存储位置使用默认的
    • DiskFileItemFactory(int sizeThreshold,File repository)
      • sizeThreshold :缓存大小
      • repository:临时文件存储位置
    • 注意:对于无参数构造,也可以设置缓存大小以及临时文件存储位置
      • setSizeThreshold(int sizeThreshold)
      • setRepository(File repository)
public class Upload3Servlet extends HttpServlet{
    public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
        //创建DiskFileItemFactory
        //DiskFileItemFactory factory = new DiskFileItemFactory();//使用默认
        //在webroot下创建一个temp文件作为临时文件存储位置
        File file = new File(this.getServletContext().getRealPath("/temp"));//获取temp目录部署到Tomcat中的绝对磁盘路径
        DiskFileItemFactiry factory = new DiskFileItemFactory(1024*100,file);//使用默认的    
        //创建ServletFileUpload
        ServletFileUpload upload = new ServletFileUpload(factory);-
    }
}
  • ServletFileUpload
  • ServletFileUpload upload = new ServletFileUpload(factory);
    • 创建一个上传工具,指定使用缓冲区与临时文件存储位置
  • List<FileItem> items = upload.parseRequest(request);
    • 它是用于解析request对象,得到所有上传项,每一个FileItem就相当于一个上传项。
  • boolean flag = upload.isMultipartContent(request);
    • 可以用于判断是否是上传。就是判断encType=”multipart/form-data”
  • 设置上传文件大小
    • void setFileSizeMax(long fileSizeMax)设置单个文件上传大小
    • void setSizeMax(long sizeMax)设置总文件上传大小
  • 解决上传文件中中文名称乱码
    • setHeaderEncoding(“utf-8”);
    • 注意:如果使用request.setCharacterEncoding(“utf-8”)也可以(全局编码过滤),但是不建议使用。
  • void setHeaderEncoding(java.lang.String encoding)设置编码集,解决上传文件名乱码。
//创建ServletFileUpload
ServletFileUpload upload = new SErvletFileUpload(factory);
boolean flag = upload.isMultipartContent(request);//用于判断是否是长传操作
if(flag){
    //解决上传文件名称中文乱码
    upload.setHeaderEncoding("utf-8");
    try{
        List<FileItem> items = upload.parseRequest(request);//解决request,得到所有的上传项FileItem
        //得到所有上传项
        for(FileItem item:items){
            if(item.isFormField()){
                //非上传组件
                System.out.println("组件名称:"+item.getFieldName());
                System.out.pritnln("内容:"+item.getString());
            }else{
                //上传组件
                System.out.println("组件名称:"+item.getFieldName());    
                System.out.println("上传文件名称:"+item.getName());           
            }
        }   
    }catch(FileUploadException e){
        e.printStackTrace();
    }   
}else{
    response.getWriter().write("不是上传操作");
}
  • FileItem
  • isFormField
    • 用于判断是否是上传组件
    • 如果是<input type="file">返回的就是false,否则返回true
    • getFiledName()
    • 返回值String,得打组件名称<input name="">
  • getName()
    • 返回值是String,得到的是上传文件的名称
    • 注意:浏览器不同,它们得到的效果不一样
      • 包含全路径名称,例如:C:\Users\Administrator\Desktop\a.txt
      • 只包含上传文件名称 ,例如:a.txt
    • getString()
    • 这个方法可以获取非上传组件的内容,相当于getParameter方法作用
    • 问题:如果信息是中文,会出现乱码,解决方案 getString(“utf-8”);
    • 如果是上传组件,上传的文件是文本文件,可以获取到文件的内容
    • 如果不是文本文件,例如:是一张图片,这样获取合适吗?
  • 获取上传文件的内容,保存到服务器端
    • item.getInputStream();它是用于读取上传文件内容的输入流。
    • 使用文件复制操作就可以完成文件上传。
  • 删除临时文件
    • item.delete();

关于文件上传时的乱码问题

  • 上传文件名称乱码
    • ServletFileUpload.setHeaderEncoding(“utf-8”);
  • 非上传组件内容乱码
    • FileItem.getString(“utf-8”);
  • 上传文件信息是否会乱码,是否需要解决?
    • 不需要解决,因为在上传时,使用的字节流来进行复制。

这里写图片描述

多文件上传

  • 多文件上传的javascript编码
    • 每次动态增加一个文件上传输入框,都把它和删除按钮放置在一个单独的div中,并对删除按钮的onclick事件进行相应,使之删除按钮所在的div
    • 如:this.parentNode.parentNode.removeChild(this.parentNode);
  • 文件上传的注意事项
    • 上传文件在服务器端保存位置问题
      • 保存在可以被浏览器直接访问的位置
        • 例如:商城的商品图片
        • 保存在工程的webRoot下的路径(不包含MeTa-INF 和web-INF目录及其子目录)
      • 保存在不能被浏览器直接访问的位置
        • 例如:付费的视频
        • 保存·工程中的META-INF WEB-INF目录及其子目录
        • 不在工程中的服务器的磁盘目录下
    • 上传文件在同一个目录重名问题
      • 如果文件重名,后上传的文件就会覆盖先上传的文件在开发中解决这个问题,可以给上传文件起随机名称
        • 使用毫秒值
        • 使用UUID,filename=UUID.randomUUID.toString+”_”+filename;
    • 同一个目录下文件过多,只需要分目录就可以
      • 按照上传时间进行目录分离(周,月)
      • 按照上传用户进行目录分离——为每个用户建立单独目录
      • 按照固定数量进行目录分离——-假设每个目录只能存放3000个文件,每当一个目录存满3000个文件后,创建一个新的目录。
      • 按照文件名的hashCode进行目录分离
      • *
<head> 
<script type="text/javascript">
functionaddFile(){
    var div = document.getElementById("content");
    div.innerHTML+="<div><input type='file' name='f'><input type='button' value='remove file'></div>";

}
function removeFile(btn){
    document.getElementById("content").removeChild(btn.parentNode);
}
</script>

</head>
<body>
<input type="button" value="add file" onclick="addFile();"><br>
<form action="${pageContext.request.contextPath}/upload3" method="post" encType="multipart/form-data">
<input type="file" name="f"><br>
<div id = "content">
    <div><input type='file' name='f'><input type='button' value='remove file' onclick='removeFile(this)'></div>
</div>
<input type="submit" value="上传">
</form>
</body>
//目录分离算法
public class FileUploadUtils{
    public static String getRandomDirectory(String filename){
        int hashcode = filename.hashCode();
        //System.out.println(hashcode);
        //int类型数据在内存中占32位,转换成16进制数,就得到8个16进制数
        String hex = Integer.toHexString(hashcode);
        //System.out.println(hex);//056d9363
        //得到两层目录
        return "/"+hex.charAt(0)+"/"+hex.charAt(1);
    }
    //方式2
    public static String getRandomDirectory2(String filename){
    //获取唯一文件名的hashCode
        int hashcode = filename.hashCode();
        int a = hashcode & 0xf;
        //获得一级目录
        hashcode = hashcode >>> 4;
        //获得二级目录
        int b = hashcode & 0xf;
        return "/"+a+"/"+b;

    }
    public static void main(String[] args){
    //生成两层目录
        String path = getRandomDirectory("a.txt");//91067135
        File file = new File("d:/upload");
        File directory = new File(file,path);
        //如果目录不存在,创建目录
        if(!directory.exists()){
            directory.mkdirs();
        }
    }
}

这里写图片描述

这里写图片描述

public class FileUploadUtils{
    //得到上传文件真实名称 c:\a.txt  
    public static String getRealName(String filename){
        int index = filename.lastIndexOf("\\")+1;
        return filename.substring(index);
    }
    //获取随机名称
    public static String getUUIDFileName(String filename){
        int index = filename.lastIndexOf(".");
        if(index != -1){
            return UUID.randomUUID()+filename.substring(index);
        }else{
            return UUID.randomUUID().toString();
        }
    }
    public static String getRandomDirectory(String filename){


    }


}

文件下载

  • 文件下载的方式
    • 超链接下载
      • 需要下载的资源存放在工程的webroot目录下
    • 服务器端通过流下载
  • 超链接下载
    • download1.jsp
    • <a href='${pageContext.request.contextPath}/upload/a.bmp'>a.bmp</a><br></li>
      <li><a href ='${pageContext.request.contextPath}/upload/a.doc'>a.doc</a><br></li>
      <li><a href = '${pageContext.request.contextPath}/upload/a.txt'>a.txt</a><br></li>
      <li><a href = '${pageContext.request.context}'>/upload/tk.mp3</a><br>
  • 注意:如果文件可以直接被浏览器解析,那么会在浏览器中直接打开,不能被浏览器直接解析,就是下载操作。直接打开的要想下载,右键另存为。
  • 超链接下载,要求下载的资源,必须是可以直接被浏览器直接访问的(存放在工程的webroot下,如果放在磁盘目录D盘等,不能被直接访问)。
  • 客户端访问服务器静态资源文件时,静态资源文件是通过缺省Servlet返回的。
    • 在tomcat配置文件conf/web.xml找到—org.apache.catalina.servlets.DefaultServlet
  • 在服务器端编程完成下载
    • 创建download2.jsp,下载保存在D盘的资源
      • <a href='${pageContext.request.contextPath}/download?filename=a.bmp'>a.bmp</a><br></li>
        <li><a href ='${pageContext.request.contextPath}/download?filename=a.doc'>a.doc</a><br></li>
        <li><a href = '${pageContext.request.contextPath}/download?filename=a.txt'>a.txt</a><br></li>
        <li><a href = '${pageContext.request.context}'>/download?filename=tk.mp3</a><br>
    • 创建DownloadServlet
      • 得到要下载的文件名称
        • String filename = request.getParameter(“filename”);
      • 判断文件是否存在
        • File file = new File(“d:/upload/” +filename);
        • if(file.exists())
      • 进行下载
        • 原理:就是通过response获取一个输出流,将要下载的文件内容写回到浏览器端就可以了。
public class DownloadServlet extends HttpServlet{

    public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{

        //得到要下载的文件名称
        String filename = request.getParameter("filename");
        //解决资源名称中文乱码问题
        filename = new String(filename.getBytes("iso8859-1"),"utf-8");

        //在d:/upload目录下查找这个文件是否存在
        File file = new File("d:/upload/"+filename);
        if(file.exists()){
            //文件存在,完成下载

            //下载注意事项1--设置下载文件的mimeType
            String mimeType = this.getServletContext().getMimeType(filename);
            response.setContentType(mimeType);
//判断浏览器,把filename转换成指定的编码
            String agent = request.getHeader("user-agent");
if(agent.contains("MSIE")){

    //IE浏览器
    filename = URLEncoder.encode(filename,"utf-8");
    filename = filename.replace("+"," ");
}else if(agent.contains("Firefox")){
    //火狐浏览器
    BASE64Encoder base64Encoder = new BASE64Encoder();
    filename = "=?utf-8?B?"+base64Encoder.encode(filename.getBytes("utf-8"))+"?=";
}else if(agent.contains("Chrome")){

    //谷歌浏览器
    filename = URLEncoder.encode(filename,"utf-8");
}else{
    //其他浏览器
    filename = URLEncoder.encode(filename,"utf-8");

}
            //下载注意事项2---永远是下载
            response.setHeader("content-disposition","attachment;filename="+filename);

            FileInputStream fis = new FileInputStream(file);//读取要下载文件的内容
            OutputStream os = response.getOutputStream();//将要下载的文件内容通过输出流写回到浏览器端
            int len = -1;
            byte[] b = new byte[1024*100];
            while((len = fis.read(b)) != -1){
                os.write(b,0,len);
                os.flush();
            }
            os.close();
            fis.close();
        }else{
            throw new RuntimeException("下载资源不存在。");
        }
    }
}
  • 注意:要想通过编程的方式,实现文件下载
    • 在servlet文件中,设置response.setContextType(“text/html;charset=utf-8”), .doc 和 .bmp等格式的资源,在浏览器端就会全部被作为文本格式打开。如果设置成response.setContextType(“image/bmp”),在浏览器端会被全部当成图片打开,如果设置成错误的格式,response.setContextType(“hudjor/ji34”) ;浏览器不能正常解析,就会弹出自动下载的窗口。编码方式种类的设置可以参考tomcat目录下web.xml的配置
    • 要设置mimetype类型
      • response.setContextType(String mimeType);
      • 问题:怎样才能得到要下载文件的mimeType类型?
        • ServletContext.getMimeType(String filename);
      • 如果设置了mimeType,浏览器能解析的就直接展示了,不能解析的,直接下载。
    • 设置一个响应头,设置后的效果,就是无论返回的是否可以被浏览器解析,就是下载。
      • response.setHeader(“content-disposition”,”attachment;filename=下载文件名称”) ;
  • 总结:服务器端编程下载
    • 将下载的文件通过response.getOutputStream()。流写回到浏览器端
    • 设置mimeType response.setContentType(getServletContext.getMimeType(String filename));
    • 设置响应头,目的是下载资源的操作
    • response.setHeader(“content-disposition”,”attachment;filename=下载文件名称”);
文件下载时的乱码问题
  • 关于下载时中文名称资源查找不到
    • 原因:<a href ='${pageContext.request.contextPath}/download?filename=天空.mp3'>天空.mp3</a>这是get请求
    • 在服务器端:
      • String filename = request.getParameter(“filename”);
    • 解决:new String(filename.getBytes(“iso8859-1”),”utf-8”);
  • 下载文件显示时的中文乱码问题
    • 原因:response.setHeader("content-disposition","attachment;filename="+filename);
    • 此处的filename就是弹出的下载框,所展示的名称
    • IE:要求filename必须是utf-8码
    • firefox:要求filename必须是base64编码
    • 问题:如何判断浏览器?
      • String useragent = request.getHeader(“user-agent”);

这里写图片描述

处理IE浏览器与Firefox浏览器乱码问题
String agent = request.getHeader("user-agent");
if(agent.contains("MSIE")){

    //IE浏览器
    filename = URLEncoder.encode(filename,"utf-8");
    filename = filename.replace("+"," ");
}else if(agent.contains("Firefox")){
    //火狐浏览器
    BASE64Encoder base64Encoder = new BASE64Encoder();
    filename = "=?utf-8?B?"+base64Encoder.encode(filename.getBytes("utf-8"))+"?=";
}else if(agent.contains("Chrome")){

    //谷歌浏览器
    filename = URLEncoder.encode(filename,"utf-8");
}else{
    //其他浏览器
    filename = URLEncoder.encode(filename,"utf-8");

}

使用队列来优化递归操作完成文件下载

<body>

<%! 
    //声明一个方法
    public void getFile(File file){
        if(file.isDirectory()){
            //是目录
            File[] fs = file.listFiles();
            for(int i =0;i<fs.length;i++){
                getFile(fs[i]);//递归调用
            }
        }else if(file.isFile()){
            //是文件
            System.out.println(file.getName());
        }

    }

%>
<%
    String path="D:\\java1110\\workspace\\day22\\webRoot\\upload"
    File uploadDirectory = new File(path);
    getFile(uploadDirectory);
%>

</body>

对列特点:先进先出

  • java.util.Queue
  • public interface Queue<E> extends Collection<E>
  • 在jdk中有一个接口Queue,它有一个实现类叫LinkedList,它其实就是一个队列。
  • 如果要使用队列,插入 offer 获取使用 poll
  • 使用队列来优化递归操作:是可以解决目录层次过多问题。
  • 原因:递归操作可以理解成纵向的遍历,如果目录层次比较多,在内存中存储的数据也多,会引起溢出。使用队列,它是横向遍历,一层一层遍历,可以解决目录层次比较多问题。
  • 因为使用队列,最多时候在内存中只存储了一层的信息。
<!--使用队列完成下载upload目录下所有文件-->
<body>
<%
    String path = "D:\\java1110\\workspace\\day22\\webRoot\\upload";
    File uploadDirectory = new File(path);
    //创建一个队列
    Queue<File> queue = new LinkedList<File>();

    queue.offer(uploadDirectory);
    while(!queue.isEmpty()){//如果队列不为空
        File f = queue.poll();//从队列中获取一个File

        if(f.isDirectory()){//是目录,将目录下所有文件遍历出来,存储到队列中 
            File[] fs = f.listFiles();
            for(int i=0;i<fs.length;i++){
                queue.offer(fs[i]);
            }
        }else{
            String absolutePath=(f.getAbsolutePath());
            String p = absolutePath.substring(absolutePath.lastIndexOf("\\upload"));
            out.println("<a href='/day22"+p+"'>"+f.getName()+"</a><br>");
        }
    }
%>
</body>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值