文件上传和下载

文件上传

1、上传对表单的限制
* 首先表单的提交方法:post
* 必须添加属性:enctype=”multipart/form-data”(多部件表单数据)

         * 必须添加文件表单项:
2、上传对Servelt的限制
          * 首先以前获取表单的数据使用String str=request.getParametere(“xxx”),但是当表单的属性为enctype=”multipart/form-data”
他就作废了,永远返回null
           ** 那如何获取表单的数据呢?
                        使用request.getInputStream(),返回的是ServletInputStream对象,“这个对象包含了整个请求的体。”

3、什么是多部件表单?
           表单中的每个表单选项都是一个部件,一个部件中含有自己的请求头、空行、请求体。其中包含了普通表单项和文件表单项
        * 普通表单项
                》一个头:  Content-Disposition:  包含name=”xxx”属性,表示表单项的名称
                》请求体;就是表单项的值
        * 文件表单项
                》2个头
                 * Content-disposition:包含了name属性(表单项的名称),还有filename属性(文件的名称)
                 * Content-Type:它是上传文件的MIME类型,例如:image/pjep,表示上传的是图片,图片中jpg是扩展名

3、分割ServletInputStream获得多部件中的内容(commons-filupload.jar)

*所依赖的jar包
    commons-filupload.jar,依赖commons-io.jar这个组件

* 上传操作三步走
    相关类:
        工厂: DiskFileItemFactory
        解析器:ServletFileUpload
        表单项:FileItem

    1)、创建工厂:
           DiskFileItemFactory factory=new DiskFileItemFactory();
    2)、创建解析器    
            ServletFileUpload sfu=new ServletFileUpload(factory);
    3)、使用解析器来解析request:
            List fileItemList= sfu.paraseRequest(request);    


* 操作类FileItem中的常用方法
    针对普通表单项:
        ** boolean isFormField():是否是普通表单项,
        ** String getFieldName():返回当前表项的名称
        ** String getString(String charset):返回表单项的值
    针对文件表单项:
        ** String getName():返回上传的文件名称
        ** long getSize():返回上传文件的字节数
        ** InputStream getInputStream():返回上传文件对应的输入流
        ** void write(File desFile):把上传的文件内容保存到指定的文件中。

上传的细节:

1、文件必须保存到WEB-INF下,目的是为了不让浏览器直接访问

        通常我们会在WEB-INF目录下创建一个uploads目录,用来存放上传的文件,而在Servlet中找到这个目标需要使用ServletContext的getReadPath(String)方法,   例如:
        String savepath=servletContext.getRealPath("/WEB-INF/uploads");
        savepath为:F:\tomcat7.0\webapps\upload\WEB-INF\uploads

2、有的浏览器上传的文件时绝对路径
           我们对绝对路径不感兴趣,所以我们需要切割路径。

int index=filename.getLastIndexof(“\\”);
if(index !=-1){
    filename=filename.substring(index+1);
}

3、文件名乱码或者普通表单项乱码

普通字段的乱码:
    1、手动转换
            request.setCharactorEncoding(“utrf-8”);
            2、FileItem.getString("UTF-8");    //获得名称的时候传一个字符集
上传文件的乱码:
    ServletFileUpload.setHeaderEncoding("UTF-8");

4、文件名同名问题:我们需要为每个文件添加名称前缀,这个前缀要保证不能重复。
方法:
           为文件名加不重复的前缀( CommonUtils.uuid() )
                 Filename=CommonUtils.uuid()+”_”+filename;
5 一个目录不能存放过多的文件(存在目标打散)
      一般存放1000个文件就是上限,如果多,那么打开目录时就会很“卡”。打散的方法有很多,常见的有日期打散算法,首字母打散算法。
            日期打散算法:每天生成一个目录,但也有文件过多的情况。
            首字母打散算法:如果文件名是中文,也会导致目录过多。
            哈希打散(最长用的方法):
                        * 通过文件名称得到int值(hashCode())
                        * 将得到的int值转换成16进制(Integer.toHex)
                        * 用着16进制的前两位创建目录
                        代码演示:
                                    request.setCharacterEncoding(“utf-8”);
                                    response.setContentType(“text/html;charset=’utf-8’”);

    DiskFileItemFactory factory =new DiskFileItemFactory();
    ServletFileUpload sfu=new ServletFileUpload(factory);
    try {
        List listItem=sfu.parseRequest(request);
        FileItem f1=listItem.get(1);//得到文件表单部件


        String root=this.getServletContext().getRealPath("/WEB-INF/uploads");

        String filename = f1.getName();
        int index = filename.lastIndexOf("\\");           //解决绝对路径问题
        if(index!=-1){
            filename=filename.substring(index+1);
        }

        String savename=CommonUtils.uuid()+"_"+filename; //解决同名问题(依赖itacast-tool)
        //3、得到savagename的hashcode的int值,将int转换成16进制
        String hex = Integer.toHexString(savename.hashCode());
        //4、获取hex的前两个字母,与root连接在一起,构成一个完成的路径
        File file = new File(root, hex.charAt(0)+"/"+hex.charAt(1));
        //5、创建目录链和文件
         file.mkdirs();
         File desfile=new File(file,savename);
        //6、保存
         f1.write(desfile);         
    } catch (FileUploadException e) {
        new RuntimeException(e);
    } catch (Exception e) {
        new RuntimeException(e);
    }

注意:
            Itcast-tool包依赖两个包commons-beanutils和commons-logging包

6、上传文件的大小限制
            * 单个文件大小限制(限制一个文件表单项 )
                        > ServletFileUpload 对象sfu
                                  sfu.setFileSizeMax(100*1024); //限制大小为100KB
         > 如果上传的文件超出限制,在parseRequest()方法执行时,会抛FileUploadBase.FileSizeLimitException异常
            * 整个请求的大小限制
                        > ServletFileUpload 对象sfu
                                  sfu.setSizeMax(1024*1024); //限制大小为1M
                        >如果上传的文件超出限制,在parseRequest()方法执行时,会抛出
                         FileUploadBase.SizeLimitException异常

            * 所以可以通过异常的类别来区分到底是那种异常,从而给出响应的错误信息
例:

DiskFileItemFactory factory=new DiskFileItemFactory();
ServletFileUpload sfu=new ServletFileUpload(factory);
sfu.setSizeMax(1024*1024);   //设置整个表单的大小为1M
try{
    List
    list=sfu.paraseRequest(request);
}catch(FileUploadException e){
    If(e instanceof FileUploadBase.SizeLimitException){  
        //设置错误信息 
        request.setAttribute(“mstr”,”您上传的文件超出了100KB”);
        request.getRequestDiapatcher(“/index.jsp”).forward(request,response);
      }
}

注意:
           限制文件的大小的设置一定要在解析reque对象之前,否则就会报错。

7、缓存大小与临时目录
           一个JVM启动默认的堆内存大小是64M。当文件过于太大时,要先保存在缓存中。当保存文件时,只需将缓存中的大文件直接copy到目标地址就行了
           问题1:超出多大时,才向硬盘保存,而不保存在内存。(默认是10KB):缓存大小
问题2:向硬盘的什么目录保存(答:临时目录)
           问题3:如何设置缓存大小和临时目录?
                       使用DisFileItemFactory的构造函数,
                                   DisFileItemFactory(int sizeThreshold , File repository)
                                   int sizeThreshold:指定缓存大小
                                   File repository:指定临时目录
在上传文件时,会创建临时目录。待临时目录上传后临时目录就会删除。

文件下载

下载就是我们响应客户端的是字节数据,把一个文件变成字节数组,使用response.getOutputStream()来响应浏览器。
下载的要求?
    形象的表示:(两个头一个流)
    Content-Type:传递给客户端的文件是什么MIME类型
    Content-Disposition:默认值时inline,表示在浏览器窗口打开,主要的作用是:让浏览器给我们提供下载下载框。通过设置 attachment;
    filename=xxx(attachment意味增加一个附件,filename指定的是下载的内容)。

代码演示:

    //文件的MIMe类型
    String filename="G:/CloudMusic/走马.mp3";
    FileInputStream fis=new FileInputStream(filename); //一流    
    String mimeType = this.getServletContext().getMimeType(filename);//得到MiMe类型
    String disposition="attachment;filename=zouma.mp3";

    //设置二头
    response.setHeader("Content-Type", mimeType);
    response.setHeader("Content-Disposition", disposition);
    ServletOutputStream output = response.getOutputStream();
    IOUtils.copy(fis, output);

注意;
/index.jsp 中的路径时是在url访问中使用的,不要搞错。

文件下载指定文件名的中文乱码问题?
因为在firfox中,使用的是Base64编码。而在已IE为首的绝大多数浏览器时是以URL编码的。

一种解决方法;
           filename=new String(filename.getBytes(“GBK”),”ISO-8859-1”);
这种方法虽然能解决但是对于有些特殊字符还是会出错的。
Eg:
           filename=new String(“走马”.getBytes(“GBK”),”ISO-8859-1”);

另一种方法(常用):只需传进filename和request对象就行(记住这个函数就行了)
使用一个函数方法,不需要记住但是会使用就行。(需要commons-Io包)
public static String filenameEncoding(String filename ,HttpServletRequest request ,)throw IOException{
           String agent=request.getHeader(“User-Agent”); //获得浏览器
           If(agent.contains(“Firefox”)){
               BASE64Encoding base64Encoding=new BASE64Encoding();
               filename=”=?utf-8?B”+base64Encoder.encode(filename.getBytes(“utf-8”))+”?=”;
           }else if(agent.contains(“MSIE”)){
                      filename=URLEncoder.encode(filename,”utf-8”);
           }else{
                      Filename=URLEncoder.encode(filename,”utf-8”);
           }
                      return filename;
}

你可能感兴趣的:(JavaWeb,文件上传)