欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

文件上传与下载

程序员文章站 2022-06-18 17:42:13
...

文件上传

  • 文件上传:就是将客户端资源,通过网络传递到服务器端
  • 原因:因为数据比较大,必须通过文件上传才可以完成将数据保存到服务器端操作。
  • 文件上传的本质: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>
相关标签: 文件上传与下载