一.使用java IO
下载文件最基本的方法是java IO,使用URL类打开待下载文件的连接。为有效读取文件,我们使用openStream() 方法获取 InputStream:
BufferedInputStream in = new BufferedInputStream(new URL(FILE_URL).openStream())
从InputStream读取文件时,强烈建议使用BufferedInputStream去包装InputStream,用于提升性能。
使用缓存可以提升性能。read方法每次读一个字节,每次方法调用意味着系统调用底层的文件系统。当JVM调用read()方法时,程序执行上下文将从用户模式切换到内核模式并返回。
从性能的角度来看,这种上下文切换非常昂贵。当我们读取大量字节时,由于涉及大量上下文切换,应用程序性能将会很差。
为了读取URL的字节并写至本地文件,需要使用FileOutputStream 类的write方法:
try (BufferedInputStream in = new BufferedInputStream(new URL(FILE_URL).openStream()); FileOutputStream fileOutputStream = new FileOutputStream(FILE_NAME)) { byte dataBuffer[] = new byte[1024]; int bytesRead; while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) { fileOutputStream.write(dataBuffer, 0, bytesRead); } } catch (IOException e) { // handle exception }
使用BufferedInputStream,read方法按照我们设置缓冲器大小读取文件。示例中我们设置一次读取1024字节,所以BufferedInputStream 是必要的。
上述示例代码冗长,幸运的是在Java7中Files类包含处理IO操作的助手方法。可以使用File.copy()方法从InputStream中读取所有字节,然后复制至本地文件:
InputStream in = new URL(FILE_URL).openStream();
Files.copy(in, Paths.get(FILE_NAME), StandardCopyOption.REPLACE_EXISTING);
上述代码可以正常工作,但缺点是字节被缓冲到内存中。Java为我们提供了NIO包,它有方法在两个通道之间直接传输字节,而无需缓冲。下面我们会详细讲解。
二.使用NIO
java NIO包提供了无缓冲情况下在两个通道之间直接传输字节的可能。
为了读来自URL的文件,需从URL流创建ReadableByteChannel :
ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream());
从ReadableByteChannel 读取字节将被传输至FileChannel:
FileOutputStream fileOutputStream = new FileOutputStream(FILE_NAME);
FileChannel fileChannel = fileOutputStream.getChannel();
然后使用transferFrom方法,从ReadableByteChannel 类下载来自URL的字节传输到FileChannel:
fileOutputStream.getChannel()
.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
transferTo() 和 transferFrom() 方法比简单使用缓存从流中读更有效。依据不同的底层操作系统,数据可以直接从文件系统缓存传输到我们的文件,而不必将任何字节复制到应用程序内存中。
在Linux和UNIX系统上,这些方法使用零拷贝技术,减少了内核模式和用户模式之间的上下文切换次数。
三.javaweb实现文件下载(包含.txt文件等默认在浏览器中打开的文件)
jsp:
<a href="javascript:void(0)" id="zip" >src.zip</a><br><br>
js
$(document).ready(function(){ $("#zip").click(function() { location.href= path + "/device/downloadFile"; }); });
后台代码
@RequestMapping(value="/downloadFile") @ResponseBody public void downloadDeviceLog(HttpServletRequest request, HttpServletResponse response) throws Exception { String logUrl = "http://localhost:8080/dm/img/in.zip"; try { String [] logUrlArray = logUrl.split("/"); String fileName = logUrlArray[logUrlArray.length-1]; URL url = new URL(logUrl); URLConnection uc = url.openConnection(); response.setContentType("application/octet-stream");//设置文件类型 response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); response.setHeader("Content-Length", String.valueOf(uc.getContentLength())); ServletOutputStream out = response.getOutputStream(); IOUtils.copy(uc.getInputStream(), out); } catch (Exception e) { e.printStackTrace(); } }
运行结果
注:
1、其中关键的一句是给响应头设置“content-disposition”属性,关于它的介绍可参考文章《Content-Disposition 响应头,设置文件在浏览器打开还是下载》
2、contenType也可以设置成专门针对某种文件类型的,比如文中是.txt类型,就可以这样设置:
response.setContentType("text/plain");//设置文件类型
3、最近将代码放在两台服务器上(两台tomcat编码相同),一台正常,一台报Server returned HTTP response code: 400 for URL,报错的地方在 IOUtils.copy(uc.getInputStream(), out);后来发现代码引用的url里面含中文,去掉中文之后发现好了,但是还是不知道为什么两台服务器会出现不一样的情况,先贴出来,以后找到问题原因再补充。
后边也可以自行设计代码copy
OutputStream outp = null; FileInputStream in = null; try { outp = response.getOutputStream(); in = new FileInputStream(filedownload); byte[] b = new byte[1024]; int i = 0; while((i = in.read(b)) > 0) { outp.write(b, 0, i); } outp.flush(); } catch(Exception e) { System.out.println("Error!"); e.printStackTrace(); } finally { if(in != null) { in.close(); in = null; } if(outp != null) { outp.close(); out.clear(); outp = null; } } !