1 基本概括
2 主要介绍
2.1 用 RandomAccess 实现一个断点下载的功能
多线程下载,即是一个文件能过多个线程进行下载;而断点续传说的是当一个文件下载到一半时突然由于某个原因下载中断了,比如突然电脑关机了,那么当再开机时已经下载到一半的文件不需要重头开始,而是接着下载;其原理很简单:首先,下载中断时记住上一个时点下载的位置,然后接着这个位置继续下载,这个继续下载可以是人手工触发的也可以是程序运行时自动识别进行下载的。
步骤如下:
1、设置开启线程数,发送http请求到下载地址,获取下载文件的总长度
2、计算每个线程下载数据的开始和结束位置
3、将下载到的数据,存放至临时文件中
4、带断点续传的多线程下载
定义一个int变量,记录每条线程下载的数据总长度,然后加上该线程的下载开始位置,得到的结果就是下次下载时,该线程的开始位置,把得到的结果存入缓存文件,当文件下载完成,删除临时进度文件。
程序如下:
package com.amos.tool; | |
import java.io.InputStream; | |
import java.io.RandomAccessFile; | |
import java.net.HttpURLConnection; | |
import java.net.URL; | |
/** | |
* Created by amosli on 14-7-2. | |
*/public class DownUtil | |
{ | |
// 定义下载资源的路径 | |
private String path; | |
// 指定所下载的文件的保存位置 | |
private String targetFile; | |
// 定义需要使用多少线程下载资源 | |
private int threadNum; | |
// 定义下载的 线程 对象 | |
private DownThread[] threads; | |
// 定义下载的文件的总大小 | |
private int fileSize; | |
public DownUtil(String path, String targetFile, int threadNum) | |
{ | |
this.path = path; | |
this.threadNum = threadNum; | |
// 初始化threads数组 | |
Thread s = new DownThread[threadNum]; | |
this.targetFile = target File ; | |
} | |
public void download() throws Exception | |
{ | |
URL url = new URL(path); | |
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); | |
conn.setConnectTimeout(5 * 1000); | |
conn.setRequestMethod("GET"); | |
conn.setRequestProperty( | |
"Accept", | |
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, " | |
+ "application/x-shockwave-flash, application/ xaml +xml, " | |
+ "application/vnd.ms-xpsdocument, application/x-ms-xbap, " | |
+ "application/x-ms-application, application/vnd.ms-excel, " | |
+ "application/vnd.ms-powerpoint, application/msword, */*"); | |
conn.setRequestProperty("Accept-Language", "zh-CN"); | |
conn.setRequestProperty("Charset", "UTF-8"); | |
conn.setRequestProperty("Connection", "Keep-Alive"); | |
// 得到文件大小 | |
fileSize = conn.getContentLength(); | |
conn.disconnect(); | |
int currentPartSize = fileSize / threadNum + 1;//这里不必一定要加1,不加1也可以 | |
RandomAccessFile file = new RandomAccessFile(targetFile, "rw"); | |
// 设置本地文件的大小 | |
file.setLength(fileSize); | |
file.close(); | |
for (int i = 0; i < threadNum; i++) | |
{ | |
// 计算每条线程的下载的开始位置 | |
int startPos = i * currentPartSize; | |
// 每个线程使用一个RandomAccessFile进行下载 | |
RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw"); | |
// 定位该线程的下载位置 | |
currentPart.seek(startPos); | |
// 创建下载线程 | |
threads[i] = new DownThread(startPos, currentPartSize, currentPart); | |
// 启动下载线程 | |
threads[i].start(); | |
} | |
} | |
// 获取下载的完成百分比 | |
public double getCompleteRate() | |
{ | |
// 统计多条线程已经下载的总大小 | |
int sumSize = 0; | |
for (int i = 0; i < threadNum; i++) | |
{ | |
sumSize += threads[i].length; | |
} | |
// 返回已经完成的百分比 | |
return sumSize * 1.0 / fileSize; | |
} | |
private class DownThread extends Thread | |
{ | |
// 当前线程的下载位置 | |
private int startPos; | |
// 定义当前线程负责下载的文件大小 | |
private int currentPartSize; | |
// 当前线程需要下载的文件块 | |
private RandomAccessFile currentPart; | |
// 定义已经该线程已下载的字节数 | |
public int length; | |
public DownThread(int startPos, int currentPartSize,RandomAccessFile currentPart) | |
{ | |
this.startPos = startPos; | |
this.currentPartSize = currentPartSize; | |
this.currentPart = currentPart; | |
} | |
@ Override | |
public void run() | |
{ | |
try | |
{ | |
URL url = new URL(path); | |
HttpURLConnection conn = (HttpURLConnection)url.openConnection(); | |
conn.setConnectTimeout(5 * 1000); | |
conn.setRequestMethod("GET"); | |
conn.setRequestProperty( | |
"Accept", | |
"image/gif, image/jpeg, image/pjpeg, image/pjpeg, " | |
+ "application/x-shockwave-flash, application/xaml+xml, " | |
+ "application/vnd.ms-xpsdocument, application/x-ms-xbap, " | |
+ "application/x-ms-application, application/vnd.ms-excel, " | |
+ "application/vnd.ms-powerpoint, application/msword, */*"); | |
conn.setRequestProperty("Accept-Language", "zh-CN"); | |
conn.setRequestProperty("Charset", "UTF-8"); | |
InputStream inStream = conn.getInputStream(); | |
// 跳过startPos个字节,表明该线程只下载自己负责哪部分文件。 | |
inStream.skip(this.startPos); | |
byte [] buffer = new byte[1024]; | |
int hasRead = 0; | |
// 读取网络数据,并写入本地文件 | |
while (length < currentPartSize | |
&& (hasRead = inStream.read(buffer)) != -1) | |
{ | |
currentPart.write(buffer, 0, hasRead); | |
// 累计该线程下载的总大小 | |
length += hasRead; | |
} | |
currentPart.close(); | |
inStream.close(); | |
} | |
catch (Exception e) | |
{ | |
e.printStackTrace(); | |
} | |
} | |
} | |
} |
测试程序:
package com.amos; | |
import com.amos.tool.DownUtil; | |
import org.omg.PortableServer.THREAD_POLICY_ID; | |
/** | |
* Created by amosli on 14-7-2. | |
*/public class DownUtilTest { | |
public static void main(String args[]) throws Exception { | |
final DownUtil downUtil = new DownUtil("#34;, "tomcat-7.0.54.zip", 3); | |
downUtil.download(); | |
new Thread(new Runnable() { | |
@Override | |
public void run() { | |
while(downUtil.getCompleteRate()<1){ | |
System.out.println("已完成:"+downUtil.getCompleteRate()); | |
try { | |
Thread.sleep(100); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
}).start(); | |
} | |
} | |
3 简单用例
3.1 利用随机访问流向文件写入数据
@ Test | |
public void IOTest1() throws Exception{ | |
RandomAccessFile raf = new RandomAccessFile("D:\randomtest.txt", "rw"); | |
raf.write("ABC我是测试用例123".getBytes()); | |
raf.close(); | |
} |
3.2 利用随机访问流读取文件的数据
@Test | |
public void IOTest2() throws Exception{ | |
RandomAccessFile raf = new RandomAccessFile("D:\randomtest.txt", "r"); | |
byte[] b = new byte[1024]; | |
int len; | |
while((len = raf.read(b)) != -1){ | |
System.out.println(new String(b, 0, len)); | |
} | |
raf.close(); | |
} |
3.3 直接从某一位置开始读取数据,即跳过某一位置之前,使用 seek
@Test | |
public void IOTest3() throws Exception{ | |
RandomAccessFile raf = new RandomAccessFile("D:\randomtest.txt", "r"); | |
//使用seek设置指针位置为3 | |
raf.seek(3); | |
byte[] b = new byte[1024]; | |
int len; | |
while((len = raf.read(b)) != -1){ | |
System.out.println(new String(b, 0, len)); | |
} | |
raf.close(); | |
} |
3.4 如果不设置文件的指针,写入文件是从0下标开始,如果文件里面有数据,会覆盖掉从该下标开始到写入数据的结束位置,其它数据不变
public void IOTest4() throws Exception{ | |
RandomAccessFile raf = new RandomAccessFile("D:\randomtest.txt", "rw"); | |
raf.write("测试".getBytes()); | |
raf.close(); | |
} |
3.5 我们用一个下载的文件实例来记录下载到的位置,然后通过读写操作,每次读写的位置就是从已经下载好的文件的末尾开始,下面通过代码来展示
public void IOTest5() throws Exception{ | |
RandomAccessFile r = new RandomAccessFile("D:\测试.avi", "r"); | |
//这个file就是我们下载到本地的文件,之后用file.length来记录下载的大小,每次读取和写入就从file.length开始 | |
File file = new File("H:\javaio\下载.avi"); | |
RandomAccessFile w = new RandomAccessFile(file,"rw" ); | |
r.seek(file.length()); | |
w.seek(file.length()); | |
byte[] b = new byte[1024]; | |
int len; | |
while((len = r.read(b)) != -1){ | |
w.write(b, 0, len); | |
} | |
w.close(); | |
r.close(); | |
} |
3.6 文件末尾追加信息
public void IOTest6() throws Exception{ | |
RandomAccessFile raf = new RandomAccessFile("D:\randomtest.txt", "rw"); | |
raf.seek(raf.length()); | |
raf.write("追加信息".getBytes()); | |
raf.close(); | |
} |