一、文件下载简述

  1.使用浏览器从网页上下载文件,Servlet需要增加一些响应头信息

    (1)response.setContentType("application/force-download");

    (2)response.setContentLength(fis.available());

    (3)response.setHeader("Content-Disposition","attachment;filename="+filename);

  2.如果需要下载的文件名是中文,则还需要特殊对待

    (1)如果使用get方式向Servlet进行的请求,需要编码才能获取正确的文件名

      String filename=request.getParameter("filename");
      filename=new String(filename.getBytes("iso-8859-1"),"utf-8");

    (2)必须通知浏览器实际文件名是中文的,但是必须要经过编码才行。

      filename=URLEncoder.encode(filename,"utf-8");

      注:不经过编码的中文文件名能够成功下载,但是文件名是乱码。

  3.文件下载既能够是GET方式的请求,也可以是POST方式的请求。但是文件上传必须是GET方式的请求。

  4.使用多线程文件下载和断点下载都需要的核心类:RandomAccessFile类。

  API1.6对其描述为:   

   此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。

   通常,如果此类中的所有读取例程在读取所需数量的字节之前已到达文件末尾,则抛出 EOFException(是一种 IOException)。如果由于某些原因无法读取任何字节,而不是在读取所需数量的字节之前已到达文件末尾,则抛出 IOException,而不是 EOFException。需要特别指出的是,如果流已被关闭,则可能抛出 IOException

二、单线程文件下载(网页上从服务器下载)

  1.JSP文件

<a href="<c:url value='/downloadFromServer?filename=动漫.jpg'/>">动漫.jpg下载</a><br/>

  2.Servlet响应请求

package com.kdyzm.servlet.singlethread;
/*
* 从服务器上进行单线程下载示例。
* 非断点下载
* 下载既可以是get方式也可以是post方式。
*/
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class DownloadFromServer extends HttpServlet { private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
} public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8"); String filename=request.getParameter("filename");
filename=new String(filename.getBytes("iso-8859-1"),"utf-8");
//第一步:设置相应类型
response.setContentType("application/force-download");
//第二步:读取文件
String path=this.getServletContext().getRealPath("/resource")+"/"+filename;
FileInputStream fis=new FileInputStream(path); //第三步:设置响应头,对文件名进行URL编码
filename=URLEncoder.encode(filename,"utf-8");
response.setContentLength(fis.available());
response.setHeader("Content-Disposition","attachment;filename="+filename); //第三步:开始文件复制
OutputStream os=response.getOutputStream();
int length=-1;
byte[]buff=new byte[1024*1024];
while((length=fis.read(buff))!=-1)
{
os.write(buff, 0, length);
}
os.close();
fis.close();
}
}

DownloadFromServer.java

  3.运行结果:略

三、单线程断点下载(使用HttpURLConnection模拟浏览器向服务器发出请求)

  1.断点下载原理。

   (1)使用RandomAccessFile类对文件进行读写操作。

     使用seek方法进行文件指针的定位。

      使用已下载部分的文件大小确定剩余部分文件的字节数量以及请求的字节范围。

   (2)设置请求头部信息,确定请求范围大小

      conn.setRequestProperty("range", "bytes="+fileSize+"-");

      fileSize是已下载文件的大小。

   (3)使用range设置请求头部信息,响应码是206,而非普通的请求成功状态码200

  2.断点下载实现代码

package com.kdyzm.singlethread.breakpoint;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL; /*
* 单线程断点下载实现文件下载功能。
*/
public class DownloadFromServerByBreakpoint {
public static void main(String[] args) throws Exception {
String fileName="video.mp4";
String path="http://localhost:8080/day22_2/resource/"+fileName;
File file=new File("d://download/"+fileName);
long fileSize=file.length();
URL url=new URL(path);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("range", "bytes="+fileSize+"-");
conn.setDoInput(true);
conn.connect();
int code=conn.getResponseCode();
System.out.println("响应结果是:"+code);
if(code==206)
{
InputStream is=conn.getInputStream();
System.out.println("服务器返回的字节数:"+conn.getContentLength());
System.out.println("这次从哪里开始写入:"+fileSize);
//必须要使用RandomAccessFile进行读写才行
RandomAccessFile out=new RandomAccessFile(file,"rw");
out.seek(fileSize);
byte []buff=new byte[100];
int length=-1;
while((length=is.read(buff))!=-1)
{
out.write(buff, 0, length);
}
out.close();
System.out.println("下载成功!");
}
}
}

DownloadFromServerByBreakpoint.java

四、多线程文件下载

  1.实现原理

   模拟迅雷,在下载的时候不管有没有下载完成都要在磁盘上创建一个相同大小的文件,在该文件对应着多个RandomAccessFile对象,利用多线程可以创建出多个RandomAccessFile对象并分别拥有不同的文件指针对该文件进行写入操作。

  2.实现代码。

package com.kdyzm.multiplethreaddownload;
/*
* 这种方式的多线程下载才是真正的多线程下载
* 没有加上同步代码块,几个线程同时向文件中写入内容,
* 但是为什么效率反而变低了???
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/*
* 该小练习演示多线程下载文件的方法与技巧。
*/
public class MultipleThreadDownloadx {
public static void main(String[] args) throws Exception {
System.out.println("没有同步代码块的多线程下载!");
// String path="http://danbooru.donmai.us/data/8fc288b237cfcc0a8e026f79b65733ed.jpg";
String path="http://localhost:8080/day22_2/resource/video.mp4";
URL url=new URL(path);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
//对connection对象进行一系列的设置处理。
conn.setRequestMethod("GET");//设置GET请求
conn.setDoInput(true);
conn.connect();
//获取状态码
int code=conn.getResponseCode();
System.out.println(code);
if(code==200)//如果请求成功,则开启多线程下载文件
{
String fileName="d://download/video.mp4";
long fileSize=conn.getContentLength();
RandomAccessFile out=new RandomAccessFile(new File(fileName),"rw");
out.setLength(fileSize);
System.out.println("文件的总大小为:"+fileSize);
long threadCount=8;//将会有四个线程参与多线程下载任务。
long threadPerDownloadSize=fileSize/threadCount+(fileSize%threadCount==0?0:1);
System.out.println("每个线程将会下载的数据量是:"+threadPerDownloadSize);
//下面计算每个具体的线程将会下载的数据量并创建线程对象开启线程下载。
for(long i=0;i<threadCount;i++)
{
//计算开始的字节
long start=i*threadPerDownloadSize;
long end=start+(threadPerDownloadSize-1);
System.out.println("第"+(i+1)+"个线程下载的内容为"+start+"-"+end);
new DownloadThreadx(fileName,url.toString(),start,end).start();
}
}
conn.disconnect();//连接断开
}
}
class DownloadThreadx extends Thread
{
private String fileName;
private String url;
private long start;
private long end;
public DownloadThreadx(String fileName, String url, long start, long end) {
this.fileName=fileName;
this.url=url;
this.start=start;
this.end=end;
}
//重写run方法。
@Override
public void run() {
try {
HttpURLConnection conn=(HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setRequestProperty("range","bytes="+start+"-"+end);
conn.connect();
int code=conn.getResponseCode();
System.out.println("响应状态码为:"+code);
if(code==206)
{
long size=conn.getContentLength();
InputStream in=conn.getInputStream();
System.out.println("线程"+this.getName()+"下载的数据量为:"+size);
RandomAccessFile out=new RandomAccessFile(new File(fileName),"rw");
out.seek(start);
byte []buff=new byte[1024*1024];
int length=-1;
while((length=in.read(buff))!=-1)
{
out.write(buff, 0, length);
}
in.close();
out.close();
}
conn.disconnect();//连接断开
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

MultipleThreadDownloadx.java

  3.思考。

   (1)两种多线程实现方式?不!!

      使用2改代码可以实现真正意义上的多线程文件下载,因为每个线程都是用不同的RandomAccessFile对象对文件进行的读写操作。如果改写成为使用同一个RandomAccessFile对象有如何呢?这需要对run方法进行加锁,但是一旦加锁线程将会顺序执行写入操作,这样一来就和单线程下载文件想通了,甚至效率更低,而且由于需要对服务器进行多次请求,如果服务器不稳定的话非常有可能出现其中一个或者几个线程连接超时的情况,这时候其他线程所做的努力就白费了,这种方式应当坚决杜绝出现

   代码错误示例:

package com.kdyzm.multiplethreaddownload;
/*
* 这种方式是多线程下载的方式,但是这种方式的多线程和单线程并没有任何区别,甚至更耗时间,降低了效率
* 不应当适用这种方式
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL; class Resource
{
public static final Object obj=new Object();
}
/*
* 该小练习演示多线程下载文件的方法与技巧。
*/
public class MultipleThreadDownload {
public static void main(String[] args) throws Exception {
String path="http://danbooru.donmai.us/data/8fc288b237cfcc0a8e026f79b65733ed.jpg";
// String path="http://localhost:8080/day22_2/resource/video.mp4";
URL url=new URL(path);
HttpURLConnection conn=(HttpURLConnection) url.openConnection();
//对connection对象进行一系列的设置处理。
conn.setRequestMethod("GET");//设置GET请求
conn.setDoInput(true);
conn.connect();
//获取状态码
int code=conn.getResponseCode();
System.out.println(code);
if(code==200)//如果请求成功,则开启多线程下载文件
{
String fileName="d://download/video.mp4";
long fileSize=conn.getContentLength();
RandomAccessFile out=new RandomAccessFile(new File(fileName),"rw");
out.setLength(fileSize);
System.out.println("文件的总大小为:"+fileSize);
long threadCount=8;//将会有四个线程参与多线程下载任务。
long threadPerDownloadSize=fileSize/threadCount+(fileSize%threadCount==0?0:1);
System.out.println("每个线程将会下载的数据量是:"+threadPerDownloadSize);
//下面计算每个具体的线程将会下载的数据量并创建线程对象开启线程下载。
for(long i=0;i<threadCount;i++)
{
//计算开始的字节
long start=i*threadPerDownloadSize;
long end=start+(threadPerDownloadSize-1);
System.out.println("第"+(i+1)+"个线程下载的内容为"+start+"-"+end);
new DownloadThread(out,url.toString(),start,end).start();
}
}
conn.disconnect();//连接断开
}
}
class DownloadThread extends Thread
{
private RandomAccessFile out;
private String url;
private long start;
private long end;
public DownloadThread(RandomAccessFile out, String url, long start, long end) {
this.url=url;
this.out=out;
this.start=start;
this.end=end;
}
//重写run方法。
@Override
public void run() {
synchronized(Resource.obj)
{
try {
HttpURLConnection conn=(HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setRequestProperty("range","bytes="+start+"-"+end);
conn.connect(); int code=conn.getResponseCode();
System.out.println("响应状态码为:"+code); if(code==206)
{
long size=conn.getContentLength();
InputStream in=conn.getInputStream();
System.out.println("线程"+this.getName()+"下载的数据量为:"+size);
out.seek(start);
byte []buff=new byte[1024*1024];
int length=-1;
while((length=in.read(buff))!=-1)
{
out.write(buff, 0, length);
}
in.close();
}
conn.disconnect();//连接断开
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

MultipleThreadDownload.java

    曾经的博客中:http://www.cnblogs.com/kuangdaoyizhimei/p/4048015.html 出现过这种错误。

  (2)为什么多线程下载文件的效率比单线程下载文件的效率更为低下?

    待续。

【Java EE 学习 22 下】【单线程下载】【单线程断点下载】【多线程下载】的更多相关文章

  1. 【Java EE 学习 67 下】【OA项目练习】【SSH整合JBPM工作流】【JBPM项目实战】

    一.SSH整合JBPM JBPM基础见http://www.cnblogs.com/kuangdaoyizhimei/p/4981551.html 现在将要实现SSH和JBPM的整合. 1.添加jar ...

  2. 【Java EE 学习 79 下】【动态SQL】【mybatis和spring的整合】

    一.动态SQL 什么是动态SQL,就是在不同的条件下,sql语句不相同的意思,曾经在“酒店会员管理系统”中写过大量的多条件查询,那是在SSH的环境中,所以只能在代码中进行判断,以下是其中一个多条件查询 ...

  3. 【Java EE 学习 29 下】【JDBC编程中操作Oracle数据库】【调用存储过程的方法】

    疑问:怎样判断存储过程执行之后返回值是否为空. 一.连接oracle数据库 1.需要的jar包:在安装的oracle中就有,所以不需要到官网下载,我的oracle11g下:D:\app\kdyzm\p ...

  4. 【Java EE 学习 82 下】【MAVEN整合Eclipse】【MAVEN的一些高级概念】

    一.MAVEN整合Eclipse MAVEN是非常优秀,但是总是要开命令行敲命令是比较不爽的,我们已经习惯了使用IDE,所以还有一种将MAVEN整合到Eclipse的方法. 详情查看:http://w ...

  5. 【Java EE 学习 69 下】【数据采集系统第一天】【实体类分析和Base类书写】

    之前SSH框架已经搭建完毕,现在进行实体类的分析和Base类的书写.Base类是抽象类,专门用于继承. 一.实体类关系分析 既然是数据采集系统,首先调查实体(Survey)是一定要有的,一个调查有多个 ...

  6. 【Java EE 学习 35 下】【struts2】【struts2文件上传】【struts2自定义拦截器】【struts2手动验证】

    一.struts2文件上传 1.上传文件的时候要求必须使得表单的enctype属性设置为multipart/form-data,把它的method属性设置为post 2.上传单个文件的时候需要在Act ...

  7. 【Java EE 学习 33 下】【validate表单验证插件】

    一.validate 1.官方网站:http://jqueryvalidation.org/ 2.文档说明:http://jqueryvalidation.org/documentation/ 3.j ...

  8. 【Java EE 学习 17 下】【数据库导出到Excel】【多条件查询方法】

    一.导出到Excel 1.使用DatabaseMetaData分析数据库的数据结构和相关信息. (1)测试得到所有数据库名: private static DataSource ds=DataSour ...

  9. 【Java EE 学习 16 下】【dbutils的使用方法】

    一.为什么要使用dbutils 使用dbutils可以极大程度的简化代码书写,使得开发进度更快,效率更高 二.dbutils下载地址 http://commons.apache.org/proper/ ...

随机推荐

  1. Python笔记(3)迭代器与生成器

    参考自:http://www.cnblogs.com/huxi/category/251137.html 迭代器 迭代器是访问集合内元素的一种方式,他不能倒退只能一直迭代下去.可以写到for循环in后 ...

  2. Web jquery表格组件 JQGrid 的使用 - 7.查询数据、编辑数据、删除数据

    系列索引 Web jquery表格组件 JQGrid 的使用 - 从入门到精通 开篇及索引 Web jquery表格组件 JQGrid 的使用 - 4.JQGrid参数.ColModel API.事件 ...

  3. linux 命令行 光标移动技巧

    linux 命令行 光标移动技巧 看一个真正的专家操作命令行绝对是一种很好的体验-光标在单词之间来回穿梭,命令行不同的滚动.在这里强烈建立适应GUI节目的开发者尝试一下在提示符下面工作.但是事情也不是 ...

  4. apache启动出错原因举例

    这是我这两天频繁遇到的问题.Apache服务器还真是问题少年!任何点改动都可能导致它无法使用. 原因一:80端口占用例如IIS,另外就是迅雷.我的apache服务器就是被迅雷害得无法启用! 原因二:软 ...

  5. Python之路【第十九篇】自定义分页实现(模块化)

    自定义分页 1.目的&环境准备 目的把分页写成一个模块的方式然后在需要分页的地方直接调用模块就行了. 环境准备Django中生成一个APP并且注册,配置URL&Views 配置URL ...

  6. [Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式

    系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...

  7. php 去掉字符串的最后一个字符

    原字符串1,2,3,4,5,6, 去掉最后一个字符",",最终结果为1,2,3,4,5,6 代码如下: $str = "1,2,3,4,5,6,"; $news ...

  8. 什么时候用@Resource,什么时候用@service

    Spring中什么时候用@Resource,什么时候用@service当你需要定义某个类为一个bean,则在这个类的类名前一行使用@Service("XXX"),就相当于讲这个类定 ...

  9. ASP.NET 对于文件的下载与上传

    /// <summary> /// 下载附件查看 /// </summary> /// <param name="sender"></pa ...

  10. 微信 5.3 for iPhone已放出 微信iphone版更新下载

    就在几个小时前,微信发布了更新,本次只放出微信 5.3 for iPhone,距离发布微信5.3内测版也就几天时间.和往常一样微信iphone版先发布,微信android版延后发布,微信看重的是ios ...