1. 程式人生 > >斷點續傳/下載

斷點續傳/下載

在後臺專案中經常會要求有下載和上傳功能的實現,在大檔案傳輸的過程中可以實現斷點傳輸避免重複下載:現在我們來整理一下,也可以作為一個專案的亮點。

由於是本地測試,所以是將"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 使用執行緒池