【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/ ...
随机推荐
- Centos7下安装python,查看python版本
安装Centos的时候,一般会自带默认安装python2.x 一般用python -V可以查看python版本. 我当时安装的时候,运行了那个语句,但是却显示了一大堆出来,虽然里面也带有版本信息,但是 ...
- a版本冲刺第七天
队名:Aruba 队员: 黄辉昌 李陈辉 林炳锋 鄢继仁 张秀锋 章 鼎 学号 昨天完成的任务 今天做的任务 明天要做的任务 困难点 体会 408 学习活动的消息传递和日志分析 因为大家都在赶进 ...
- php.ini 中文注释
这个文件控制了PHP许多方面的观点.为了让PHP读取这个文件,它必须被命名为 ; ´php.ini´.PHP 将在这些地方依次查找该文件:当前工作目录:环境变量PHPRC ; 指明的路径:编译时指定的 ...
- CSS书写顺序
CSS书写顺序 1.位置属性(position, top, right, z-index, display, float等)2.大小(width, height, margin, padding)3. ...
- MySQL主从同步延迟
早上接到open-falcon报警,一台mysql从库同步延迟2w多秒,mysql版本比较老,用的5.1.37. 连接从库查找原因: show processlist一下,查看哪些线程在跑. 看到Ti ...
- Elasticsearch集群状态脚本及grafana监控面板导出的json文件
脚本文件: #!/usr/bin/env python import datetime import time import urllib import json import urllib2 imp ...
- SDL简介(网络汇总)
摄像头视频播放采用sdl,下面简单介绍下.不保证正确及网址永远有效.后面文章采用tao框架http://sourceforge.net/projects/taoframework/ SDL. ...
- SDK 支付
充值:用什么买什么 MSDK: Q点:百科 http://baike.baidu.com/link?url=Dw8ySUIvv6AAprULG_wnI7Mst61gG4bO2qzfpfi1j9xx6c ...
- 在Application中集成Microsoft Translator服务之开发前准备
第一步:准备一个微软账号 要使用Microsoft Translator API需要在Microsoft Azure Marketplace(https://datamarket.azure.com/ ...
- MySql 外键约束 之CASCADE、SET NULL、RESTRICT、NO ACTION分析和作用
MySQL有两种常用的引擎类型:MyISAM和InnoDB.目前只有InnoDB引擎类型支持外键约束.InnoDB中外键约束定义的语法如下: ALTER TABLE tbl_name ADD [CON ...