斷點續傳/下載
阿新 • • 發佈:2018-11-07
在後臺專案中經常會要求有下載和上傳功能的實現,在大檔案傳輸的過程中可以實現斷點傳輸避免重複下載:現在我們來整理一下,也可以作為一個專案的亮點。
由於是本地測試,所以是將"D:/test/remote/file.txt"傳送到"D:/test/local/file.txt",如果是使用FTP傳送,使用FtpClient就可以了。
1 單執行緒讀取
為方便整理邏輯,先使用單執行緒完成對需求的實現。
關鍵: RandomAccessFile 可以實現任意位置開始讀取/寫入
File f=new File(“D:/test.txt”);
RandomAccessFile localFile = new RandomAccessFile(f,“rw”);
localFile.seek(position);
localFile.write(buf);
code:
import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import com.springboot.SpringBootHello.service.FtpDownLoadService; import com.springboot.SpringBootUtill.downLoadThread; public class FtpDownLoadServiceImply implements FtpDownLoadService{ @Override public void downLoadSingleFile(File loacalFile, String remoteFilePath) { // TODO Auto-generated method stub File remoteFile=new File(remoteFilePath); checkFile(loacalFile); //檢視本地檔案是否存在不存在則建立 long localSize=loacalFile.length(); long remoteSize=remoteFile.length(); long step=localSize;//本地檔案的大小作為標記的position; if (localSize<remoteSize && localSize>=0) { continueDownLoad( loacalFile, remoteFile,step); } else { System.err.println("本地檔案已存在"); } } @Override public void downLoadFiles() { // TODO Auto-generated method stub } @Override public void continueDownLoad(File loacalFile, File remoteFile, long startIndex) { // TODO Auto-generated method stub RandomAccessFile remoteFile2 = null; // RandomAccessFile為任意位置起讀取檔案 FileOutputStream localFile2 =null; try { remoteFile2 = new RandomAccessFile(remoteFile, "rw"); localFile2 = new FileOutputStream(loacalFile, true); // true表示在文末新增 //RandomAccessFile localFile2 = new RandomAccessFile(loacalFile, "rw"); remoteFile2.seek(startIndex);//輸入起始座標 //localFile2.seek(startIndex); // 資料緩衝區 byte[] buf = new byte[1]; while (remoteFile2.read(buf) != -1) { localFile2.write(buf); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally{ //最後關閉IO流 try { localFile2.close(); remoteFile2.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } @Override public void checkFile(File filename) { // TODO Auto-generated method stub if (!filename.exists()) { try { filename.createNewFile(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void main(String[] args) { FtpDownLoadServiceImply tt = new FtpDownLoadServiceImply(); File localFile = new File("D:/test/local/file.txt"); String remote = "D:/test/remote/file.txt"; tt.downLoadSingleFile(localFile, remote); } }
2 多執行緒讀取
上述程式碼已經實現了單執行緒的下載,我們只需要將單執行緒改為多執行緒就可以實現大檔案分片下載。
關鍵:
將需要下載的部分進行劃分 long partSize=(remoteSize-localSize)/threadNum;
設定每個執行緒所需要的標記位 long startIndex = localSize+partSize*(i-1);
long endIndex = localSize+partSize*i;
code:
下載方法:
private void threadDownload(File localFile,File remoteFile, long localSize,long remoteSize) { int threadNum = 4; long partSize=(remoteSize-localSize)/threadNum; for (int i = 1; i <=threadNum; i++) { long startIndex = localSize+partSize*(i-1); long endIndex = localSize+partSize*i; if (i==threadNum) { endIndex=remoteSize; //設定最後一個執行緒的終點位置 } downLoadThread myThread = new downLoadThread(localFile,remoteFile,startIndex,endIndex,i); Thread thread = new Thread(myThread); //啟動執行緒 thread.start(); } }
執行緒類:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class downLoadThread implements Runnable{
private long startIndex;
private long endIndex;
private int i;
private File remoteFile;
private File loacalFile;
public downLoadThread(File loacalFile, File remoteFile, long startIndex,
long endIndex, int i ) {
// TODO Auto-generated constructor stub
this.remoteFile = remoteFile;
this.loacalFile = loacalFile;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.i = i;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.err.println("我是縣城"+i+" endIndex"+endIndex+"startIndex"+startIndex);
RandomAccessFile remoteFile2 =null;
RandomAccessFile localFile2 =null;
try {
remoteFile2 = new RandomAccessFile(remoteFile, "rw");
//FileOutputStream 也可以在文末寫入
// FileOutputStream localFile2 = new FileOutputStream(loacalFile, true);
localFile2 = new RandomAccessFile(loacalFile, "rw");
remoteFile2.seek(startIndex);
localFile2.seek(startIndex);
int part= (int)(endIndex-startIndex);
// 資料緩衝區
byte[] buf = new byte[1];
int hasRead=0;
int num= 0;
while ((hasRead=remoteFile2.read(buf)) != -1) {
num=hasRead+num;
//執行緒終止,繼續寫入的話會重複寫入
if (num>part) {
break;
}
localFile2.write(buf);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
try {
remoteFile2.close();
localFile2.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
我們來溫習下多執行緒的實現方式
詳情點選
1 繼承 thread
public class myThread extends Thread{
@override
run{
system.out.println("你好我是執行緒");
}
}
啟動執行緒
myThread mt = new myThread ();
mt.start();
2 實現介面Runnable
public class myThread implment Runnable{
@override
run{
system.out.println("你好我是執行緒");
}
}
啟動執行緒
myThread mt = new myThread ();
Thread tt = new Thread (mt);
tt.start();
3 實現介面callable
4 使用執行緒池