【Java EE 学习 22 下】【单线程下载】【单线程断点下载】【多线程下载】
一、文件下载简述
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 下】【单线程下载】【单线程断点下载】【多线程下载】的更多相关文章
- 【Java EE 学习 67 下】【OA项目练习】【SSH整合JBPM工作流】【JBPM项目实战】
一.SSH整合JBPM JBPM基础见http://www.cnblogs.com/kuangdaoyizhimei/p/4981551.html 现在将要实现SSH和JBPM的整合. 1.添加jar ...
- 【Java EE 学习 79 下】【动态SQL】【mybatis和spring的整合】
一.动态SQL 什么是动态SQL,就是在不同的条件下,sql语句不相同的意思,曾经在“酒店会员管理系统”中写过大量的多条件查询,那是在SSH的环境中,所以只能在代码中进行判断,以下是其中一个多条件查询 ...
- 【Java EE 学习 29 下】【JDBC编程中操作Oracle数据库】【调用存储过程的方法】
疑问:怎样判断存储过程执行之后返回值是否为空. 一.连接oracle数据库 1.需要的jar包:在安装的oracle中就有,所以不需要到官网下载,我的oracle11g下:D:\app\kdyzm\p ...
- 【Java EE 学习 82 下】【MAVEN整合Eclipse】【MAVEN的一些高级概念】
一.MAVEN整合Eclipse MAVEN是非常优秀,但是总是要开命令行敲命令是比较不爽的,我们已经习惯了使用IDE,所以还有一种将MAVEN整合到Eclipse的方法. 详情查看:http://w ...
- 【Java EE 学习 69 下】【数据采集系统第一天】【实体类分析和Base类书写】
之前SSH框架已经搭建完毕,现在进行实体类的分析和Base类的书写.Base类是抽象类,专门用于继承. 一.实体类关系分析 既然是数据采集系统,首先调查实体(Survey)是一定要有的,一个调查有多个 ...
- 【Java EE 学习 35 下】【struts2】【struts2文件上传】【struts2自定义拦截器】【struts2手动验证】
一.struts2文件上传 1.上传文件的时候要求必须使得表单的enctype属性设置为multipart/form-data,把它的method属性设置为post 2.上传单个文件的时候需要在Act ...
- 【Java EE 学习 33 下】【validate表单验证插件】
一.validate 1.官方网站:http://jqueryvalidation.org/ 2.文档说明:http://jqueryvalidation.org/documentation/ 3.j ...
- 【Java EE 学习 17 下】【数据库导出到Excel】【多条件查询方法】
一.导出到Excel 1.使用DatabaseMetaData分析数据库的数据结构和相关信息. (1)测试得到所有数据库名: private static DataSource ds=DataSour ...
- 【Java EE 学习 16 下】【dbutils的使用方法】
一.为什么要使用dbutils 使用dbutils可以极大程度的简化代码书写,使得开发进度更快,效率更高 二.dbutils下载地址 http://commons.apache.org/proper/ ...
随机推荐
- Android开发:关于WebView
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://liangruijun.blog.51cto.com/3061169/647456 ...
- Mac 下面 apache 不解析PHP(or PHP 版本不对)的解决办法
Mac 升级到 EI Caption 之后发现 apache 的配置被重置了,记录下解决的步骤: 在 apache2 配置文件中修改 PHP 的扩展路径即可: vi /etc/apache2/http ...
- 由于一个粗心造成的RuntimeException
今天在测试时候,老是有一个TextView报错,以下是错误日志: java.lang.RuntimeException: Unable to resume activity {com....Activ ...
- 【Beta】七天屠蛟记
团队名字: 一不小心就火了 屠龙天团少年们: 031402504 陈逸超 (组长) 031402505 陈少铭 031402511 黄家俊 031402515 翁祖航 031402516 黄瑞钰 03 ...
- js获取当前坐标
<script type="text/javascript"> var position_option = {enableHighAccuracy: false,max ...
- MSYS2的源配置
关于MSYS2的文章可以参考下面的链接,笔者不多赘述: msys2安装笔记 MSYS2 + MinGW-w64 + Git + gVim 环境配置 msys2环境搭建 msys2安装g++: pacm ...
- PHP中类的继承和构造函数的继承
PHP4.x 版本: PHP 4.x 的构造函数名与类名相同. 子类的构造函数名与子类名相同(废话). 在子类里父类的构造函数不会自动执行. 要在子类里执行父类的构造函数,必须执行类似以下语句: $t ...
- PHP Object 转 Array,Json 转 Array
object 转 array /** * object 转 array */ function object_to_array($obj){ $_arr = is_object($obj)? get_ ...
- vue2.0学习(一)
1.解决双花括号在初始化时的闪烁,两种方式,一种是<div v-text="name"></div>,将用v-text指令来显示,类似于angular的ng ...
- shell--2.shell数组
shell 数组 (1)定义数组 shell中,用括号表示数组,数组元素用空格分开,定义数组的一般形式 arrt_name=(val1 val2 val3) 或者 arry_name=(val1 va ...