文件上传

文件上传概述

1.文件上传是什么
    在web开发中经常需要从客户端向服务器上传文件,如:上传照片,上传新闻图片,上传附件等等,这些都需要通过web开发中的文件上传技术实现.
2.文件上传步骤
    实现wb开发中的文件上传功能只需要两个步骤:
    (1).提供一个带有文件上传项的表单.
    (2).在servlet中读取处理上传的文件,保存到服务器.

文件上传实现

1.在用户页面中添加上传输入项(客户端页面操作)
<input type="file" name="filex">
    注意事项:
    (1).必须为文件上传input提供name属性,否则文件上传内容不会被表单提交.
    (2).表单的提交方式必须为post(get提交数据在url地址上显示,有长度限制)
    (3).表单必须设置enctype=multipart/form-data
2.在服务器端编写文件上传程序
    通过Request对象提供一个getInputStream方法,可以读取到客户端提交过来的数据,但这种方式还需要对流中获取到的数据进行处理.
    为了简化这个处理过程,Apache开源组织提供了一个用来处理表单文件上传的一个开源组件(Commons-fileupload),让开发人员轻松实现web文件上传功能.
3.上传组件(Apache commons-fileupload)使用过程
    首先需要下载并导入该组件相关的支撑jar包:Commons-filesupload和commons-io,然后编程实现,步骤如下:
(1)创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录

public DiskFileItemFactory(int sizeThreshold,java.io.File repository) 
//构造工厂时,指定内存缓冲区大小和临时文件存放位置
public void setSizeThreshold(int sizeThreshold)
//设置内存缓冲区大小,默认10k
public void setRepository(java.io.File repository)
//设置临时文件存放位置,默认 System.getProperty("java.io.tmpdir")
*内存缓存区:上传文件时,上传文件的内容优先保存在内存缓冲区中,当
*上传文临时大小超过缓冲区大小,就会在服务器端产生临时文件
*临时文件存放位置:保存超过了内存缓冲区大小上传文件而产生临时文件
*产生临时文件可以通过FileItem的delete方法删除

(2).使用DiskFileItemFactory对象创建ServletFileUpload对象,并设置上传文件的大小限制.
(3).调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传FileItem的list集合

ServeltFIleUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象中.常用的方法有:
//判断上传表单是否为multipart/form-data类型
boolean isMutipartContent(HttpServletRequest request)
//解析request对象,并把表单中的每一个输入项包装成fileItem对象,并返回一个保存了所有FileItem的list集合.
List parseRequest(HttpServletRequest request)
//设置单个上传文件的最大值
setFileSizeMax(long fileSizeMax)
//设置上传文件总量的最大值
setSizeMax(long sizeMax)
//解决上传文件名乱码问题
setHeadEncoding(java.lang.String encoding)
//实时监听文件上传状态
setProgressListener(ProgressListener pListener)

(4).对List进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否上传文件.
    True为普通表单字段,则调用getFieldName,getString方法得到字段名和字段值.
    False为上传文件项,则调用getInputStream方法得到数据输入流,从而读取上传数据.
(5).如果是文件上传就通过流获取上传的数据保存到服务器中.

boolean idFormField()判断FileItem是一个文件上传对象还是普通字段项:
String getFieldName() 获得普通表单对象的name属性
String getString(String encoding)获得普通表单对象的value属性,可以用encoding进行编码设置
文件上传项:
String getName()获得上传文件的文件名(有些浏览器会携带客户端路径)
InputStream getInputStream()获得上传文件的输入流
delete() 在关闭FileItem输入流后,删除临时文件

上传文件的监听

//ServletFileUpload类提供了如下方法,监听文件上传时的进度信息:
pulbic void setProgressListener(ProgressListener pListener)
//设置监听器,文件上传程序会自动执行,监听器中update方法
public void update(long pBytesRead,long pContentLength,int pItems)
//在方法中可以获得文件总大小,已经上传大小和上传第几个元素
能否根据上面三个参数计算:剩余大小、传输速度、已用时间、剩余时间
(1).已用时间 = 当前时间 -开始时间
(2).速度 = 已经上传大小/已用时间
(3).剩余时间 = 总大小 - 已经上传大小
(4).剩余时间 = 剩余大小/速度

上传文件注意问题

1.文件保存位置
    上传的文件如果直接存放在web应用根目录下,通过浏览器是可以直接访问的,如果用户上传一个JSP,再通过浏览器访问就可以通过文件上传在服务器执行任意的java代码,十分危险.
    所以一定要注意,文件上传服务器保存文件的位置必须是WEB-INF目录下或服务器中浏览器无法访问的位置.
2.防止上传的文件重名覆盖
    上传的文件如果重名,后上传的文件就会覆盖先上传文件.    为了防止这样的问题产生,应该对文件名进行处理,可以在文件名钱拼接UUID,防止文件名重复.
filename = UUID.randomUUID().toString()+"_"+filename;
3.防止同一个目录下上传文件过多
    一个目录下如果文件过多,必须会导致访问效率下降,所以我们需要将上传的文件进行分目录存储.
    分目录存储的算法可以有很多:
    (1).按照上传时间进行目录分离(周,月)
    (2).按照上传用户进行目录分离—为每个用户建立单独目录
    (3).按照固定数量进行目录分离–假设每个目录只能存在3000个文件后,创建一个新的目录
    (4).按照唯一文件名的hashcode进行目录分离

public static String generateRandomDir(String uui获dFileName){
//获得唯一文件名的hashcode
int hashcode = uuidFileName.hashCode();
//获得一级目录
int d1 = hashcode & 0xf;
//获得二级目录
int d2 = (hashcode >>> 4)&0xf;
return "/"+ d2 + "/"+ d1;//共有256目录
}

4.文件上传代码

1.创建一个UploadServlet类
try {
    response.setCharacterEncoding("utf-8");
    response.setContentType("text/html;charset=utf-8");

    //1.创建文件上传工厂
    DiskFileItemFactory factory = new DiskFileItemFactory(100, new File(this.getServletContext().getRealPath("WEB-INF/temp")));

    //2.创建文件上传的核心类
    ServletFileUpload fileUpload = new ServletFileUpload(factory);
    //--判断当前文件上传的表单是否满足enctype=multipart/form-data
    if(!fileUpload.isMultipartContent(request)){
        throw new RuntimeException("请使用正确的文件上传表单上传数据!");
    }
    //--设置文件名解析时采用的编码
    fileUpload.setHeaderEncoding("utf-8");
    //--单个文件不能超过1MB
    fileUpload.setFileSizeMax(1024 * 1024);
    //--总大小不能超过10MB
    fileUpload.setSizeMax(10 * 1024 * 1024);
    //--设置上传文件的监听
    fileUpload.setProgressListener(new ProgressListener(){
        long begin = System.currentTimeMillis();
        public void update(long pBytesRead, long pContentLength,int pItems) {
    System.out.print("正在读取第"+pItems+"个上传项。。");
        System.out.print("共"+pContentLength+"字节。。");
        System.out.print("已经读取了"+pBytesRead+"字节。。");

        long leftBytes = pContentLength - pBytesRead;
        System.out.print("剩余"+leftBytes+"字节。。");

        long now = System.currentTimeMillis();
        long usetime = (now - begin)/1000 ;
        System.out.print("已经用时" + usetime+"秒。。");

        long speed = usetime == 0 ? 0 : pBytesRead / usetime / 1024;
        System.out.print("上传速度" + speed+"KB/s。。");

        double per = Math.round(pBytesRead * 10000.0 / pContentLength)/100.0;
        System.out.println("上传百分比" + per + "%。。");

        long lefttime = speed == 0 ? 0 : leftBytes /1024 / speed;
        System.out.println("大致剩余时间"+lefttime+"秒");

        request.getSession().setAttribute("progress", per);
        }

        });

    //--解析request得到FileItem的集合
    List<FileItem> items = fileUpload.parseRequest(request);

    //--遍历每个item分别做处理
    for(FileItem item : items){
        if(item.isFormField()){
            //普通字段项
            String name = item.getFieldName();
            String value = item.getString("utf-8");
            System.out.println(name+"~"+value);
        }else{
            //文件上传项
            String fname = item.getName();
            //--处理ie文件名bug
            if(fname.contains("\\")){
                fname = fname.substring(fname.lastIndexOf("\\")+1);
            }

            //--处理文件名 使其不会重复
            String savename = UUID.randomUUID().toString()+"_"+fname;

            //--文件分目录处理
            //----获取文件名的hash 转换为16进制字符串表现形式 由于文件名随机 所以 hash值也是散列的
            String hash = Integer.toHexString(savename.hashCode());

            //----如果hash不足8位则在前面拼接足够8位的0
            while(hash.length()<8){
                hash += "0";
            }
            //----遍历hash值字符串的每一个字符作为一级目录拼接
            String savepath = "/WEB-INF/upload/";
            for(int i = 0;i<hash.length();i++){
                savepath += (hash.charAt(i)+"/");
            }
            //----创建出该目录
            new File(this.getServletContext().getRealPath(savepath)).mkdirs();

            //----得到输入流
            InputStream in = item.getInputStream();

            //----得到输出流 路径就是上面 hash拼接出的路径+文件名
            OutputStream out = new FileOutputStream(this.getServletContext().getRealPath(savepath + "/" +savename));

            //----输出数据到文件
            byte []  data = new byte[1024];
            int n = -1;
            while((n = in.read(data))!=-1){
                out.write(data,0,n);
            }

            //----关闭流
            in.close();
            out.close();

            //----删除缓存文件
            item.delete();
        }
    }

    } catch (FileSizeLimitExceededException e) {
        response.getWriter().write("文件大小超过限制!!");
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

关注下面微信公众号获得更多学习资源!
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值