I believe many students in the interview, often asked by the interviewer such a question: how to achieve breakpoint download, that is, before the file download is completed, save the progress, continue to download next time. It’s not that hard to do, just use a temporary file to record the current download progress, and then start the next download from the progress recorded in the temporary file to implement the function.





Once you’ve done that, the interviewer might ask: Can you do multithreaded breakpoint downloads? This problem can be easily solved if you solve the first problem by splitting the file size into several chunks and downloading different chunks on different threads. This example solves both of these problems.





Here are the solutions:






1. Divide the file size into different blocks based on the number of threads.



2. The file name is generated based on the URL, and the download path is generated.



3. Generate the start and end positions of each block.



4. Create multiple threads and perform operations 5 through 7 on each thread.



5. Set up a connection, read the start of the last download from the temporary file, and set the download range (start, end) in the request header.



6. Open the downloaded file, move the cursor to the starting position, and write data in it. Each write, record the file location in the temporary file.



7. After the file download is complete, delete the temporary file.





HttpUrlConnection is one of the most basic network frameworks provided by Java. HttpUrlConnection is one of the most basic network frameworks provided by Java.

The full class is available on Github: github.com/ryanlijianc…

[java]
view plain
copy


print
?

  1. import java.io.BufferedInputStream;  
  2. import java.io.File;  
  3. import java.io.FileInputStream;  
  4. import java.io.RandomAccessFile;  
  5. import java.net.HttpURLConnection;  
  6. import java.net.URL;  
  7.   
  8. public class ResumeDownload {  
  9.     public static final String DOWNLOAD_URL =  “http://7xs0af.com1.z0.glb.clouddn.com/High-Wake.mp3”;  
  10.     public static final String DOWNLOAD_PARENT_PATH =  “D:\\test_resume_download\\hi”;  
  11.     public static final int THREAD_COUNT =  3;  
  12.   
  13.     public static void main(String[] args) {  
  14.         try {  
  15.             // Get the connection to the download address  
  16.             URL mUrl = new URL(DOWNLOAD_URL);  
  17.             HttpURLConnection conn = (HttpURLConnection) mUrl.openConnection();  
  18.             // Get the size of the downloaded file  
  19.             int fileLen = conn.getContentLength();  
  20.             // Obtain the name of the downloaded file from the download link  
  21.             String filePathUrl = conn.getURL().getFile();  
  22.             String fileName = filePathUrl.substring(filePathUrl.lastIndexOf(File.separator) + 1);  
  23.             // Generate the download path  
  24.             String fileDownloadPath = DOWNLOAD_PARENT_PATH + File.separator + fileName;  
  25.             // Check if the parent path exists  
  26.             File file = new File(fileDownloadPath);  
  27.             if(! file.getParentFile().exists()) {
  28.                 file.getParentFile().mkdirs();  
  29.             }  
  30.             // Close the connection  
  31.             conn.disconnect();  
  32.   
  33.             / * * 
  34. * The following is a multi-threaded download, the main principle is to evenly divide the file size into multiple blocks (according to the number of threads), each thread from a different starting position, download the same size file mainly through 
  35. * HttpUrlConnection set the Range parameter to set the download Range for each thread 
  36.              * setRequestProperty(“Range”, “bytes=” + startPos + “-” + endPos); 
  37. * /  
  38.   
  39.             int blockSize = fileLen / THREAD_COUNT;  
  40.             for (int threadId = 1; threadId <= THREAD_COUNT; threadId++) {  
  41.                 // Get the start and end locations of each thread download  
  42.                 long startPos = (threadId – 1) * blockSize;  
  43.                 long endPos = threadId * blockSize –  1;  
  44.                 if (threadId == THREAD_COUNT) {  
  45.                     endPos = fileLen;  
  46.                 }  
  47.   
  48.                 // Then implement the download logic in different threads  
  49.                 // Implement the Runnable DownloadThread  
  50.                 new Thread(new DownLoadTask(threadId, startPos, endPos, fileDownloadPath, DOWNLOAD_URL)).start();  
  51.             }  
  52.   
  53.         } catch (Exception e) {  
  54.             e.printStackTrace();  
  55.         }  
  56.   
  57.     }  
  58. }  
  59.   
  60. / * * 
  61. * Specific download logic 
  62.  *  
  63.  * @author Administrator 
  64.  * 
  65. * /  
  66. class DownLoadTask implements Runnable {  
  67.     public static final String TEMP_NAME =  “_tempfile”;  
  68.     private int threadId; // Id of the current thread  
  69.     private long startPos; // Start location of the download  
  70.     private long endPos; // The end of the download  
  71.     private String fileDownloadPath; // Download the file location  
  72.     private String downloadUrl; // Download the link  
  73.   
  74.     private String tempFilePath; // Temporary file path to record progress  
  75.   
  76.     public DownLoadTask(int threadId, long startPos,  long endPos, String fileDownloadPath, String downloadUrl) {  
  77.         super(a);
  78.         this.threadId = threadId;  
  79.         this.startPos = startPos;  
  80.         this.endPos = endPos;  
  81.         this.fileDownloadPath = fileDownloadPath;  
  82.         this.downloadUrl = downloadUrl;  
  83.   
  84.         this.tempFilePath = fileDownloadPath + TEMP_NAME + threadId;  
  85.     }  
  86.   
  87.     @Override  
  88.     public void run() {  
  89.         try {  
  90.             // Record the start time of the download  
  91.             long startTime = System.currentTimeMillis();  
  92.   
  93.             URL mUrl = new URL(downloadUrl);  
  94.   
  95.             // To achieve a breakpoint download, get the starting location of the download from the cache file when redownloading  
  96.             if(getProgress(threadId) ! =0) {  
  97.                 startPos = getProgress(threadId);  
  98.             }  
  99.   
  100.             System.out.println(“Thread” + threadId + “Continue downloading, start location:” + startPos +  “The end position is:” + endPos);  
  101.   
  102.             // General operation of HttpUrlConnection  
  103.             / / in order to realize the breakpoint downloading, mConnection must be set. The setRequestProperty (” Range “and” bytes = “+  
  104.             // startPos + “-” + endPos);  
  105.             HttpURLConnection mConnection = (HttpURLConnection) mUrl.openConnection();  
  106.             mConnection.setRequestMethod(“POST”);  
  107.             mConnection.setReadTimeout(5000);  
  108.             mConnection.setRequestProperty(“Charset”.“UTF-8”);  
  109.             mConnection.setRequestProperty(“Range”.“bytes=” + startPos +  “-“ + endPos);  
  110.             mConnection.connect();  
  111.   
  112.             // Create a file path if the download path does not exist  
  113.             File file = new File(fileDownloadPath);  
  114.             if(! file.getParentFile().exists()) {
  115.                 file.getParentFile().mkdirs();  
  116.             }  
  117.   
  118.             // Read and write the file to download using RandomAccessFile  
  119.             RandomAccessFile downloadFile = new RandomAccessFile(fileDownloadPath,  “rw”);  
  120.             // While writing, move the cursor to the starting position to download  
  121.             downloadFile.seek(startPos);  
  122.   
  123.             BufferedInputStream bis = new BufferedInputStream(mConnection.getInputStream());  
  124.             int size = 0// Get the size of the cache in bytes  
  125.             long len = 0// Record the size of the download to calculate where the start position of the download has moved to  
  126.             byte[] buf = new byte[ 1024];  
  127.             while((size = bis.read(buf)) ! = –1) {  
  128.                 / / accumulation  
  129.                 len += size;  
  130.                 // Then write the contents of the buffer to the download file  
  131.                 downloadFile.write(buf, 0, size);  
  132.                 // Then move the start of the download to the end of the download and write it to the cache file  
  133.                 setProgress(threadId, startPos + len);  
  134.             }  
  135.   
  136.             // Get the download end time, output  
  137.             long curTime = System.currentTimeMillis();  
  138.             System.out.println(“Thread” + threadId + “Download completed, time:” + (curTime – startTime) +  “ms.”);  
  139.   
  140.             // Close streams, files, and connections  
  141.             downloadFile.close();  
  142.             mConnection.disconnect();  
  143.             bis.close();  
  144.   
  145.         } catch (Exception e) {  
  146.             e.printStackTrace();  
  147.         }  
  148.     }  
  149.   
  150.     / * * 
  151. * Get download progress from temp file 
  152.      *  
  153.      * @param threadId 
  154.      * @return 
  155. * /  
  156.     private long getProgress(int threadId) {  
  157.         try {  
  158.             File markFile = new File(tempFilePath);  
  159.             if(! markFile.exists()) {
  160.                 return 0;  
  161.             }  
  162.             FileInputStream fis = new FileInputStream(markFile);  
  163.             BufferedInputStream bis = new BufferedInputStream(fis);  
  164.             byte[] buf = new byte[ 1024];  
  165.             String startPos = “”;  
  166.             int len = –1;  
  167.             while((len = bis.read(buf)) ! = –1) {  
  168.                 startPos += new String(buf, 0, len);  
  169.             }  
  170.   
  171.             // Files cannot be deleted without closing the stream  
  172.             fis.close();  
  173.             bis.close();  
  174.   
  175.             return Long.parseLong(startPos);  
  176.   
  177.         } catch (Exception e) {  
  178.             e.printStackTrace();  
  179.         }  
  180.         return 0;  
  181.     }  
  182.   
  183.     / * * 
  184. * Record download progress in temp file 
  185.      *  
  186.      * @param threadId 
  187.      * @param startPos 
  188. * /  
  189.     private void setProgress(int threadId,  long startPos) {  
  190.         try {  
  191.             File markFile = new File(tempFilePath);  
  192.             if(! markFile.getParentFile().exists()) {
  193.                 markFile.getParentFile().mkdirs();  
  194.             }  
  195.               
  196.             RandomAccessFile rr = new RandomAccessFile(markFile, “rw”); // Store the file marked for download  
  197.             String strStartPos = String.valueOf(startPos);  
  198.             rr.write(strStartPos.getBytes(), 0, strStartPos.length());  
  199.               
  200.             rr.close();  
  201.         } catch (Exception e) {  
  202.             e.printStackTrace();  
  203.         } finally {  
  204.             // When the file download is complete, i.e., when the start position and the end position overlap, delete the cache file recording the progress  
  205.             if (startPos >= endPos) {  
  206.                 File markFile = new File(tempFilePath);  
  207.                 if (markFile.exists()) {  
  208.                     System.out.println(“markFile delete”);  
  209.                     markFile.delete();  
  210.                 }  
  211.             }  
  212.         }  
  213.   
  214.     }  
  215. }  
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class ResumeDownload {
	public static final String DOWNLOAD_URL = "http://7xs0af.com1.z0.glb.clouddn.com/High-Wake.mp3";
	public static final String DOWNLOAD_PARENT_PATH = "D:\\test_resume_download\\hi";
	public static final int THREAD_COUNT = 3;

	public static void main(String[] args) {
		try {
			// 获取到下载地址的连接
			URL mUrl = new URL(DOWNLOAD_URL);
			HttpURLConnection conn = (HttpURLConnection) mUrl.openConnection();
			// 获取下载文件的大小
			int fileLen = conn.getContentLength();
			// 通过下载链接获取下载文件的文件名
			String filePathUrl = conn.getURL().getFile();
			String fileName = filePathUrl.substring(filePathUrl.lastIndexOf(File.separator) + 1);
			// 生成下载路径
			String fileDownloadPath = DOWNLOAD_PARENT_PATH + File.separator + fileName;
			// 判断父路径是否存在,不存在就生成
			File file = new File(fileDownloadPath);
			if (!file.getParentFile().exists()) {
				file.getParentFile().mkdirs();
			}
			// 关闭连接
			conn.disconnect();

			/**
			 * 以下为多线程下载,主要原理就是将文件大小均分多块(根据线程数) 每一个线程从不同的起始位置,下载相等大小的文件 主要通过
			 * HttpUrlConnection里面设置Range参数,设置每一个线程下载的范围
			 * setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
			 */

			int blockSize = fileLen / THREAD_COUNT;
			for (int threadId = 1; threadId <= THREAD_COUNT; threadId++) {
				// 获取每一个线程下载的起始位置和结束位置
				long startPos = (threadId - 1) * blockSize;
				long endPos = threadId * blockSize - 1;
				if (threadId == THREAD_COUNT) {
					endPos = fileLen;
				}

				// 然后通过再不同线程里面实现下载逻辑
				// 具体实现在DownloadThread这个Runnable里面
				new Thread(new DownLoadTask(threadId, startPos, endPos, fileDownloadPath, DOWNLOAD_URL)).start();
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}

/**
 * 具体下载逻辑
 * 
 * @author Administrator
 *
 */
class DownLoadTask implements Runnable {
	public static final String TEMP_NAME = "_tempfile";
	private int threadId; // 当前线程id
	private long startPos; // 下载的起始位置
	private long endPos; // 下载的结束位置
	private String fileDownloadPath; // 下载文件存放的文件位置
	private String downloadUrl; // 下载链接

	private String tempFilePath; // 记录进度的临时文件路径

	public DownLoadTask(int threadId, long startPos, long endPos, String fileDownloadPath, String downloadUrl) {
		super();
		this.threadId = threadId;
		this.startPos = startPos;
		this.endPos = endPos;
		this.fileDownloadPath = fileDownloadPath;
		this.downloadUrl = downloadUrl;

		this.tempFilePath = fileDownloadPath + TEMP_NAME + threadId;
	}

	@Override
	public void run() {
		try {
			// 记录下载的开始时间
			long startTime = System.currentTimeMillis();

			URL mUrl = new URL(downloadUrl);

			// 为了实现断点下载,在重新下载时从缓存文件里面获取下载的起始位置
			if (getProgress(threadId) != 0) {
				startPos = getProgress(threadId);
			}

			System.out.println("线程" + threadId + "继续下载,开始位置:" + startPos + "结束位置是:" + endPos);

			// HttpUrlConnection的常规操作
			// 要实现断点下载的话,必须要设置mConnection.setRequestProperty("Range", "bytes=" +
			// startPos + "-" + endPos);
			HttpURLConnection mConnection = (HttpURLConnection) mUrl.openConnection();
			mConnection.setRequestMethod("POST");
			mConnection.setReadTimeout(5000);
			mConnection.setRequestProperty("Charset", "UTF-8");
			mConnection.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
			mConnection.connect();

			// 如果下载路径不存在的话,则创建文件路径
			File file = new File(fileDownloadPath);
			if (!file.getParentFile().exists()) {
				file.getParentFile().mkdirs();
			}

			// 通过RandomAccessFile对要下载的文件进行读写
			RandomAccessFile downloadFile = new RandomAccessFile(fileDownloadPath, "rw");
			// 写的时候,将光标移到要下载的起始位置
			downloadFile.seek(startPos);

			BufferedInputStream bis = new BufferedInputStream(mConnection.getInputStream());
			int size = 0; // 获取缓存区存放的字节大小
			long len = 0; // 记录本次下载的大小,以便计算本次下载的起始位置移动到了哪里
			byte[] buf = new byte[1024];
			while ((size = bis.read(buf)) != -1) {
				// 累加
				len += size;
				// 然后将缓冲区的内容写到下载文件中
				downloadFile.write(buf, 0, size);
				// 然后将下载的起始位置移动到已经下载完的末尾,写到缓存文件里面去
				setProgress(threadId, startPos + len);
			}

			// 获取下载结束时间,输出
			long curTime = System.currentTimeMillis();
			System.out.println("线程" + threadId + "已经下载完成,耗时:" + (curTime - startTime) + "ms.");

			// 关闭流、文件和连接
			downloadFile.close();
			mConnection.disconnect();
			bis.close();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 从temp文件获取下载进度
	 * 
	 * @param threadId
	 * @return
	 */
	private long getProgress(int threadId) {
		try {
			File markFile = new File(tempFilePath);
			if (!markFile.exists()) {
				return 0;
			}
			FileInputStream fis = new FileInputStream(markFile);
			BufferedInputStream bis = new BufferedInputStream(fis);
			byte[] buf = new byte[1024];
			String startPos = "";
			int len = -1;
			while ((len = bis.read(buf)) != -1) {
				startPos += new String(buf, 0, len);
			}

			// 不关闭流的话,不能删除文件
			fis.close();
			bis.close();

			return Long.parseLong(startPos);

		} catch (Exception e) {
			e.printStackTrace();
		}
		return 0;
	}

	/**
	 * 在temp文件记录下载进度
	 * 
	 * @param threadId
	 * @param startPos
	 */
	private void setProgress(int threadId, long startPos) {
		try {
			File markFile = new File(tempFilePath);
			if (!markFile.getParentFile().exists()) {
				markFile.getParentFile().mkdirs();
			}
			
			RandomAccessFile rr = new RandomAccessFile(markFile, "rw");// 存储下载标记的文件
			String strStartPos = String.valueOf(startPos);
			rr.write(strStartPos.getBytes(), 0, strStartPos.length());
			
			rr.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 当文件下载完成时,即开始位置和结束位置重合时,删除记录进度的缓存文件
			if (startPos >= endPos) {
				File markFile = new File(tempFilePath);
				if (markFile.exists()) {
					System.out.println("markFile delete");
					markFile.delete();
				}
			}
		}

	}
}
Copy the code