springMVC下载

来源:互联网 发布:默纳克蓝牙调试软件 编辑:程序博客网 时间:2024/05/21 14:07

这两天玩spring的过程中遇到了一个很常见的问题——文件下载。以往很多时候都是直接给一个文件的静态链接,这种方法有很多局限性,其中一个很明显的局限性就是不易统计下次状态,还有就是需要http服务器来保存文件,不可访问服务器本机文件系统的文件,综上所述,我们需要一种易于统计并可下载本地文件系统中文件的方法:那就是服务器读取本地文件流,然后将文件流输出到客户端。这个过程中流传输异常、完成都可以轻易获取,方便统计该次下载的状态。我对spring框架并不是太熟,基本上是面向搜索引擎的编程。这次想用spring完成一个文件下载的过程,于是查了相关资料,获得两种实现方式:

基于ResponseEntity实现

@RequestMapping("/testHttpMessageDown")public ResponseEntity<byte[]> download(HttpServletRequest request) throws IOException {    File file = new File("E://123.jpg");    byte[] body = null;    InputStream is = new FileInputStream(file);    body = new byte[is.available()];    is.read(body);    HttpHeaders headers = new HttpHeaders();    headers.add("Content-Disposition", "attchement;filename=" + file.getName());    HttpStatus statusCode = HttpStatus.OK;    ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(body, headers, statusCode);    return entity;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Java通用下载实现

下载过程的实现,在java中调用reponse.getOutputStream()方法会自动激活下载操作

public static void download(String fileName, String filePath,HttpServletRequest request, HttpServletResponse response) throws Exception {    //声明本次下载状态的记录对象    DownloadRecord downloadRecord = new DownloadRecord(fileName, filePath, request);    //设置响应头和客户端保存文件名    response.setCharacterEncoding("utf-8");    response.setContentType("multipart/form-data");    response.setHeader("Content-Disposition", "attachment;fileName=" + fileName);    //用于记录以完成的下载的数据量,单位是byte    long downloadedLength = 0l;    try {        //打开本地文件流        InputStream inputStream = new FileInputStream(filePath);        //激活下载操作        OutputStream os = response.getOutputStream();        //循环写入输出流        byte[] b = new byte[2048];        int length;        while ((length = inputStream.read(b)) > 0) {            os.write(b, 0, length);            downloadedLength += b.length;        }        // 这里主要关闭。        os.close();        inputStream.close();    } catch (Exception e){        downloadRecord.setStatus(DownloadRecord.STATUS_ERROR);        throw e;    }    downloadRecord.setStatus(DownloadRecord.STATUS_SUCCESS);    downloadRecord.setEndTime(new Timestamp(System.currentTimeMillis()));    downloadRecord.setLength(downloadedLength);    //存储记录}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

下载记录信息实体

public class DownloadRecord {public static final int STATUS_ERROR = 0;    public static final int STATUS_SUCCESS = 1;    private String uid;    private String ip;    private int port;    private String ua;    private String fileName;    private String filePath;    private long length;    private int status;    private Timestamp startTime;    private Timestamp endTime;    public DownloadRecord() {    }    public DownloadRecord(String fileName, String filePath,     HttpServletRequest request) {        this.uid = UUID.randomUUID().toString().replace("-","");        this.fileName = fileName;        this.filePath = filePath;        this.ip = request.getRemoteAddr();        this.port = request.getRemotePort();        this.ua = this.ua = request.getHeader("user-agent");        this.startTime = new Timestamp(System.currentTimeMillis());    }    /** getter and setter **/}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

比较

  • 基于ResponseEntity的实现的局限性还是很大,从代码中可以看出这种下载方式是一种一次性读取的下载方式,在文件较大的时候会直接抛出内存溢出(我自己亲测一个1.8G的文件在执行下载操作的时候直接抛出了内存溢出)。还有就是这种方式在进行下载统计的时候也存在局限性,无法统计在下载失败的情况已完成下载量,因此限制了对下载的功能扩展。虽然这种实现方式有局限性,但是也有着优点——简洁。在很多时候我们并不需要那么复杂的下载功能时,这种实现就应该是首选了。
  • 然而下载java通用实现在功能上比第一种实现更加丰富,对下载的文件大小无限制(循环读取一定量的字节写入到输出流中,因此不会造成内存溢出,但是在下载人数过多的时候应该还是出现一些异常,不过下载量较大的文件一般都会使用ftp服务器来做吧),另外因为是这种实现方式是基于循环写入的方式进行下载,在每次将字节块写入到输出流中的时都会进行输出流的合法性检测,在因为用户取消或者网络原因造成socket断开的时候,系统会抛出SocketWriteException,系统可以捕捉这个过程中抛出的异常,当捕捉到异常的时候我们可以记录当前已经传输的数据量,这样就可以完成下载状态和对应状态下载量和速度之类的数据记录。另外这种方式实现方式还可以实现一种断点续载的功能。

关于BufferedInputStream和BufferedOutputStream的实现原理的理解

  在介绍FileInputStream和FileOutputStream的例子中,使用了一个byte数组来作为数据读入的缓冲区,以文件存取为例,硬盘存取的速度远低于内存中的数据存取速度。为了减少对硬盘的存取,通常从文件中一次读入一定长度的数据,而写入时也是一次写入一定长度的数据,这可以增加文件存取的效率。

java.io.BufferedInputStream与java.io.BufferedOutputStream可以为InputStream、OutputStream类的对象增加缓冲区功能。

构建BufferedInputStream实例时,需要给定一个InputStream类型的实例,实现BufferedInputStream时,实际上最后是实现InputStream实例。

同样地,在构建BufferedOutputStream时,也需要给定一个OutputStream实例,实现BufferedOutputStream时,实际上最后是实现OutputStream实例。

BufferedInputStream的数据成员buf是一个位数组,默认为2048字节。当读取数据来源时,例如文件,BufferedInputStream会尽量将buf填满。当使用read()方法时,实际上是先读取buf中的数据,而不是直接对数据来源作读取。当buf中的数据不足时,BufferedInputStream才会再实现给定的InputStream对象的read()方法,从指定的装置中提取数据。

        BufferedOutputStream的数据成员buf是一个位数组,默认为512字节。当使用write()方法写入数据时,实际上会先将数据写至buf中,当buf已满时才会实现给定的OutputStream对象的write()方法,将buf数据写至目的地,而不是每次都对目的地作写入的动作。

--------------调用BufferedInputStream、BufferedOutputStream 例子 ------------------

复制代码
 1 public static void main(String[] args) {     2         try { 3             BufferedInputStream bis=new BufferedInputStream(new FileInputStream("f:/a.mp3")); 4             BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("f:/b.mp3")); 5             byte[] b=new byte[1024];  //①..??为什么还要用到数组 6             int len=0; 7             while(-1!=(len=bis.read(b))){ 8                 bos.write(b,0,len); 9             }10             11         } catch (FileNotFoundException e) {12             System.out.println("文件找不到");13             e.printStackTrace();14         } catch (IOException e) {15             e.printStackTrace();16         }finally{17             if(null!=bos){18                 bos.close();19             }20             if(null!=bis){21                 bis.close();22             }23         }24 }
复制代码
①。在这里,自己有个疑问:既然BufferedInputStream已经实现了缓冲,那为什么在程序中还要写那个byte[]数组提高性能?
个人理解:(有待商榷)
   1.它们的产生不一样:
                        其中,BufferedInputStream中的缓冲数组,跟自己定义的缓冲数组不一样。
                        BufferedInputStream中的缓冲数组是程序一开始就已经定义好的,节省了程序创建一个数组的时间。
                        而上面代码中的byte[] b数组(相当于缓冲数组),是程序运行的时候才开始创建的,花费了一些时间
   2.它们的作用对象不一样:
                        BufferedInputStream的缓冲数组是面对程序对硬盘数据的,而自己在程序定义的数组byte[] b是对已经从硬盘读取到内存的数据,
                        也就是说,byte[] b是读取内存中的buf数组的数据。
   3.它们的作用目的一致:
                        它们都是为了提高性能

原创粉丝点击