Skip to content

Commit e02befe

Browse files
authored
Merge pull request onlyliuxin#8 from honokaBiu/master
implement FileDownloader by Korben
2 parents 4116f15 + d4c6bf6 commit e02befe

File tree

9 files changed

+437
-0
lines changed

9 files changed

+437
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.korben.coderising.download;
2+
3+
import java.io.IOException;
4+
import java.io.RandomAccessFile;
5+
import org.korben.coderising.download.api.Connection;
6+
import org.korben.coderising.download.api.ConnectionException;
7+
import org.korben.coderising.download.api.ConnectionManager;
8+
import org.korben.coderising.download.api.DownloadListener;
9+
10+
public class DownloadThread extends Thread {
11+
12+
private int endPos;
13+
private int startPos;
14+
private String url;
15+
private String destFilePath;
16+
private ConnectionManager connManager;
17+
private DownloadListener downloadListener;
18+
19+
public DownloadThread(ConnectionManager connManager, String url, int startPos, int endPos, String destFilePath,
20+
DownloadListener downloadListener) {
21+
22+
this.url = url;
23+
this.endPos = endPos;
24+
this.startPos = startPos;
25+
this.connManager = connManager;
26+
this.destFilePath = destFilePath;
27+
this.downloadListener = downloadListener;
28+
}
29+
30+
@Override
31+
public void run() {
32+
Connection conn = null;
33+
RandomAccessFile randomAccessFile = null;
34+
try {
35+
doLog("BIN");
36+
conn = connManager.open(url, startPos, endPos);
37+
byte[] read = conn.read(startPos, endPos);
38+
String _filePath = destFilePath;
39+
if (_filePath == null || _filePath.length() == 0) {
40+
_filePath = conn.getFileName();
41+
}
42+
randomAccessFile = new RandomAccessFile(_filePath, "rw");
43+
randomAccessFile.seek(startPos);
44+
randomAccessFile.write(read);
45+
doLog("END");
46+
} catch (IOException e) {
47+
doLog("EXP");
48+
e.printStackTrace();
49+
} catch (ConnectionException e) {
50+
doLog("EXP");
51+
e.printStackTrace();
52+
} finally {
53+
if (randomAccessFile != null) {
54+
try {
55+
randomAccessFile.close();
56+
} catch (IOException e) {
57+
e.printStackTrace();
58+
}
59+
}
60+
if (conn != null) {
61+
conn.close();
62+
}
63+
if (downloadListener != null) {
64+
downloadListener.notifyFinished();
65+
}
66+
}
67+
}
68+
69+
private void doLog(String action) {
70+
System.out.println(
71+
"*********** " + action
72+
+ " ["
73+
+ startPos
74+
+ "-"
75+
+ endPos
76+
+ "]"
77+
+ " ***********");
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package org.korben.coderising.download;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
import org.korben.coderising.download.api.ConnectionException;
5+
import org.korben.coderising.download.api.ConnectionManager;
6+
import org.korben.coderising.download.api.DownloadListener;
7+
8+
public class FileDownloader {
9+
10+
private String url;
11+
12+
private DownloadListener listener;
13+
14+
private ConnectionManager cm;
15+
16+
private AtomicInteger atomicInteger;
17+
18+
public FileDownloader(String _url) {
19+
this.url = _url;
20+
atomicInteger = new AtomicInteger();
21+
}
22+
23+
/**
24+
* 在这里实现你的代码, 注意: 需要用多线程实现下载
25+
* 这个类依赖于其他几个接口, 你需要写这几个接口的实现代码
26+
* (1) ConnectionManager , 可以打开一个连接,通过Connection可以读取其中的一段(用startPos, endPos来指定)
27+
* (2) DownloadListener, 由于是多线程下载, 调用这个类的客户端不知道什么时候结束,所以你需要实现当所有
28+
* 线程都执行完以后, 调用listener的notifiedFinished方法, 这样客户端就能收到通知。
29+
* 具体的实现思路:
30+
* 1. 需要调用ConnectionManager的open方法打开连接, 然后通过Connection.getContentLength方法获得文件的长度
31+
* 2. 至少启动3个线程下载, 注意每个线程需要先调用ConnectionManager的open方法
32+
* 然后调用read方法, read方法中有读取文件的开始位置和结束位置的参数, 返回值是byte[]数组
33+
* 3. 把byte数组写入到文件中
34+
* 4. 所有的线程都下载完成以后, 需要调用listener的notifiedFinished方法
35+
*
36+
* 下面的代码是示例代码, 也就是说只有一个线程, 你需要改造成多线程的。
37+
*/
38+
public void execute() {
39+
try {
40+
41+
int threadCount = 5;
42+
int length = this.cm.getContentLength(this.url);
43+
for (int i = 0; i < threadCount; i++) {
44+
45+
int threadLoadLength = length / threadCount;
46+
int startPos = threadLoadLength * i;
47+
int endPos;
48+
if (i != threadCount - 1) {
49+
endPos = threadLoadLength * (i + 1) - 1;
50+
} else {
51+
endPos = length - 1;
52+
}
53+
atomicInteger.getAndIncrement();
54+
new DownloadThread(cm, this.url, startPos, endPos, null, new DownloadListener() {
55+
@Override
56+
public void notifyFinished() {
57+
if (atomicInteger.decrementAndGet() == 0) {
58+
if (FileDownloader.this.listener != null) {
59+
FileDownloader.this.listener.notifyFinished();
60+
}
61+
}
62+
}
63+
}).start();
64+
}
65+
} catch (ConnectionException e) {
66+
e.printStackTrace();
67+
}
68+
}
69+
70+
public void setConnectionManager(ConnectionManager ucm) {
71+
this.cm = ucm;
72+
}
73+
74+
public DownloadListener getListener() {
75+
return this.listener;
76+
}
77+
78+
public void setListener(DownloadListener listener) {
79+
this.listener = listener;
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.korben.coderising.download;
2+
3+
import org.junit.After;
4+
import org.junit.Before;
5+
import org.junit.Test;
6+
import org.korben.coderising.download.api.ConnectionManager;
7+
import org.korben.coderising.download.api.DownloadListener;
8+
import org.korben.coderising.download.impl.ConnectionManagerImpl;
9+
10+
public class FileDownloaderTest {
11+
12+
boolean downloadFinished = false;
13+
14+
@Before
15+
public void setUp() throws Exception {
16+
}
17+
18+
@After
19+
public void tearDown() throws Exception {
20+
}
21+
22+
@Test
23+
public void testDownload() {
24+
25+
String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1489721424&di=1fda6467501ab1d5e5bff43e801d14ee&imgtype=jpg&er=1&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201507%2F30%2F20150730163204_A24MX.thumb.700_0.jpeg";
26+
//String url = "http://apache.fayea.com/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz";
27+
28+
FileDownloader downloader = new FileDownloader(url);
29+
30+
ConnectionManager cm = new ConnectionManagerImpl();
31+
downloader.setConnectionManager(cm);
32+
33+
downloader.setListener(new DownloadListener() {
34+
@Override
35+
public void notifyFinished() {
36+
downloadFinished = true;
37+
}
38+
});
39+
40+
downloader.execute();
41+
42+
// 等待多线程下载程序执行完毕
43+
while (!downloadFinished) {
44+
try {
45+
System.out.println("还没有下载完成,休眠五秒");
46+
//休眠5秒
47+
Thread.sleep(5000);
48+
} catch (InterruptedException e) {
49+
e.printStackTrace();
50+
}
51+
}
52+
System.out.println("下载完成!");
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.korben.coderising.download.api;
2+
3+
import java.io.IOException;
4+
5+
public interface Connection {
6+
/**
7+
* 给定开始和结束位置, 读取数据, 返回值是字节数组
8+
*
9+
* @param startPos 开始位置, 从0开始
10+
* @param endPos 结束位置
11+
* @return 读取的字节数组
12+
*/
13+
byte[] read(int startPos, int endPos) throws IOException;
14+
15+
/**
16+
* 得到数据内容的长度
17+
*
18+
* @return 数据内容长度
19+
*/
20+
int getContentLength();
21+
22+
/**
23+
* 关闭连接
24+
*/
25+
void close();
26+
27+
/**
28+
* 获取下载文件的文件名
29+
*
30+
* @return 文件名
31+
*/
32+
String getFileName();
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.korben.coderising.download.api;
2+
3+
public class ConnectionException extends Exception {
4+
public ConnectionException(Exception e) {
5+
super(e);
6+
}
7+
8+
public ConnectionException(String msg) {
9+
super(msg);
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.korben.coderising.download.api;
2+
3+
public interface ConnectionManager {
4+
/**
5+
* 给定一个url , 打开一个连接
6+
*
7+
* @param url 连接地址
8+
* @param startPos 读取文件的起始位置
9+
* @param endPos 读取文件的结束位置
10+
* @return 连接
11+
*/
12+
Connection open(String url, int startPos, int endPos) throws ConnectionException;
13+
14+
/**
15+
* 获取文件长度
16+
*
17+
* @param url 连接地址
18+
* @return 文件长度
19+
*/
20+
int getContentLength(String url) throws ConnectionException;
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.korben.coderising.download.api;
2+
3+
public interface DownloadListener {
4+
void notifyFinished();
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.korben.coderising.download.impl;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.net.HttpURLConnection;
7+
import org.korben.coderising.download.api.Connection;
8+
9+
public class ConnectionImpl implements Connection {
10+
11+
private static final int BUFFER_SIZE = 4096;
12+
private HttpURLConnection httpConn;
13+
private String fileUrl;
14+
private InputStream inputStream;
15+
16+
public ConnectionImpl(HttpURLConnection httpConn, String fileUrl) {
17+
this.httpConn = httpConn;
18+
this.fileUrl = fileUrl;
19+
}
20+
21+
@Override
22+
public byte[] read(int startPos, int endPos) throws IOException {
23+
if (endPos < startPos) {
24+
throw new IllegalArgumentException("argument endPos[" + endPos + "] less than startPos[" + startPos + "]");
25+
}
26+
int bytesNeed2Read = endPos - startPos + 1;
27+
if (bytesNeed2Read > getContentLength()) {
28+
throw new IllegalArgumentException(
29+
"endPos[" + endPos + "] is bigger than content length[" + getContentLength() + "]");
30+
}
31+
32+
inputStream = httpConn.getInputStream();
33+
34+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
35+
byte[] buffer = new byte[Math.min(bytesNeed2Read, BUFFER_SIZE)];
36+
int read;
37+
38+
long startTime = System.currentTimeMillis();
39+
final long progressInterval = 2000;
40+
while ((read = inputStream.read(buffer)) != -1) {
41+
byteArrayOutputStream.write(buffer, 0, read);
42+
43+
if (System.currentTimeMillis() - startTime > progressInterval) {
44+
startTime = System.currentTimeMillis();
45+
System.out.println(String.format(Thread.currentThread().getName() +
46+
" [%.2f%%]", byteArrayOutputStream.size() * 100.0 / bytesNeed2Read)
47+
);
48+
}
49+
}
50+
System.out.println(String.format(Thread.currentThread().getName() + " [%.2f%%]", 100.0));
51+
System.out.println("bytes read: " + byteArrayOutputStream.size());
52+
53+
return byteArrayOutputStream.toByteArray();
54+
}
55+
56+
@Override
57+
public int getContentLength() {
58+
if (httpConn != null) {
59+
return httpConn.getContentLength();
60+
}
61+
return 0;
62+
}
63+
64+
@Override
65+
public void close() {
66+
if (inputStream != null) {
67+
try {
68+
inputStream.close();
69+
} catch (IOException e) {
70+
e.printStackTrace();
71+
}
72+
}
73+
if (httpConn != null) {
74+
httpConn.disconnect();
75+
}
76+
}
77+
78+
@Override
79+
public String getFileName() {
80+
String disposition = httpConn.getHeaderField("Content-Disposition");
81+
if (disposition != null) {
82+
// extracts file name from header field
83+
int index = disposition.indexOf("filename=");
84+
if (index > 0) {
85+
return disposition.substring(index + 10,
86+
disposition.length() - 1);
87+
}
88+
}
89+
// extracts file name from URL
90+
return fileUrl.substring(fileUrl.lastIndexOf("/") + 1,
91+
fileUrl.length());
92+
}
93+
}

0 commit comments

Comments
 (0)