分片上传:将源文件按长度分为N片,一片一片上传。
断点续传:文件在传输过程式中被中断后,在重新传输时,可以从上次的断点处开始传输。
HTTP1.1协议(RFC2616)中定义了断点续传相关的HTTP头 Range和Content-Range字段,故实现断点续传就是要能提交Content-Range(返回代码是206)

断点续下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//断点续传下载文件
URL url = new URL("http://www.baidu.com/todo.zip");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//设置断点续传的范围
connection.setRequestProperty("RANGE", "bytes=0-20000");
//获得输入流
InputStream input = connection.getInputStream();
RandomAccessFile accessFile = new RandomAccessFile("todo.zip", "rw");
//从bytes指定的位置开始存文件
long pos = 2000;
//定位到指定位置
accessFile.seek(pos);
byte[] b = new byte[1024];
int nRead;
//从输入流中读入字节流,然后写到文件中
while ((nRead = input.read(b, 0, 1024)) > 0) {
accessFile.write(b, 0, nRead);
}

为防止续传时服务器的文件已经发生了变化,续传数据将出现错误,此时需要重新传输。可以使用Lat-Modified来标识文件的最后修改时间,或者使用ETag头来放置文件的唯一标识,如文件的MD5值。

多线程下载

将源文件分成N块,开辟N个线程,每个线程传输一块,最后合并所有线程文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/**
* 断点续传文件下载线程
*/
public class BreakDownloadThread extends Thread {
long[] startPos, endPos;
SplitDownloadThread[] childThreads;
long fileLength;
boolean firstDown = true;
File tempFile;
DataOutputStream output; //输出到文件的输出流
String mUrl;
String filePath, fileName;
boolean stopped =false;
public BreakDownloadThread(String url, String filePath, String fileName, int splitCount) {
this.mUrl = url;
this.filePath = filePath;
this.fileName = fileName;
tempFile = new File(filePath + File.separator + fileName + ".dat");
if (tempFile.exists()) {
firstDown = false;
//读取上次下载到的位置
readPos();
} else {
startPos = new long[splitCount];
endPos = new long[splitCount];
}
}
@Override
public void run() {
try {
if (firstDown) { //第一次下载先获取一下文件的大小
fileLength = getFileSize();
if (fileLength == -1) {
System.err.println("File Length is not known!");
} else if (fileLength == -2) {
System.err.println("File is not access!");
} else {
// 设置各段要下载的文件位置
for (int i = 0; i < startPos.length; i++) {
startPos[i] = (long) (i * (fileLength / startPos.length));
}
for (int i = 0; i < endPos.length - 1; i++) {
endPos[i] = startPos[i + 1];
}
endPos[endPos.length - 1] = fileLength;
}
}
//启动子线程
childThreads = new SplitDownloadThread[startPos.length];
for (int i = 0; i < startPos.length; i++) {
//int threadID, String url, String name, long startPos, long endPos
childThreads[i] = new SplitDownloadThread(i, mUrl, filePath + File.separator + fileName, startPos[i], endPos[i]);
childThreads[i].start();
}
//等待子线程结束
//是否结束while循环
boolean breakWhile = false;
while (!stopped) {
writePos();
Thread.sleep(500);
breakWhile = true;
for (int i = 0; i < startPos.length; i++) {
if (!childThreads[i].downOver) {
breakWhile = false;
break;
}
}
if (breakWhile) break;
}
System.out.println("文件下载结束!");
} catch (Exception e) {
e.printStackTrace();
}
}
//获得文件长度
public long getFileSize() {
int nFileLength = -1;
try {
URL url = new URL(mUrl);
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
int responseCode = httpConnection.getResponseCode();
if (responseCode >= 400) {
return -2;
}
String sHeader;
for (int i = 1; ; i++) {
sHeader = httpConnection.getHeaderFieldKey(i);
if (sHeader != null) {
if (sHeader.equals("Content-Length")) {
nFileLength = Integer.parseInt(httpConnection.getHeaderField(sHeader));
break;
}
} else
break;
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return nFileLength;
}
//保存下载信息(文件指针位置)
private void writePos() {
try {
output = new DataOutputStream(new FileOutputStream(tempFile));
output.writeInt(startPos.length);
for (int i = 0; i < startPos.length; i++) {
output.writeLong(childThreads[i].startPos);
output.writeLong(childThreads[i].endPos);
}
output.close();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
//读取保存的下载信息(文件指针位置)
private void readPos() {
try {
DataInputStream input = new DataInputStream(new FileInputStream(tempFile));
int nCount = input.readInt();
startPos = new long[nCount];
endPos = new long[nCount];
for (int i = 0; i < startPos.length; i++) {
startPos[i] = input.readLong();
endPos[i] = input.readLong();
}
input.close();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
//分片下载的小线程
public class SplitDownloadThread extends Thread {
String mUrl;
long startPos, endPos;
int threadID;
boolean stopped = false, downOver = false;
RandomAccessFile accessFile;
public SplitDownloadThread(int threadID, String url, String name, long startPos, long endPos) throws IOException {
this.threadID = threadID;
this.mUrl = url;
this.startPos = startPos;
this.endPos = endPos;
accessFile = new RandomAccessFile(name, "rw");
accessFile.seek(startPos);
}
@Override
public void run() {
while (startPos < endPos && !stopped) {
try {
URL url = new URL(mUrl);
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
String sProperty = "bytes=" + startPos + "-";
httpConnection.setRequestProperty("RANGE", sProperty);
InputStream input = httpConnection.getInputStream();
byte[] b = new byte[1024];
int nRead;
//下载到endPos结束该线程
while ((nRead = input.read(b, 0, 1024)) > 0 && startPos < endPos && !stopped) {
startPos += write(b, 0, nRead);
}
downOver = true;
} catch (Exception e) {
e.printStackTrace();
}
}
}
public synchronized int write(byte[] b, int nStart, int nLen) {
int n = -1;
try {
accessFile.write(b, nStart, nLen);
n = nLen;
} catch (IOException e) {
e.printStackTrace();
}
return n;
}
}
}

断点续上传

分片上传(断点续上传)