import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; /** * 多线程下载,以及断点下载的实现<br> * 当中有个不好的地方,<br> * 就是进度文件的保存的时候假设採用RandomAccessFile的方式进行保存的时候<br> * 尽管会将文件的进度时时的保存在进度文件里,<br> * 可是,经过实际的測试这样会大大的减少文件的下载的速度,<br> * 假设採用File和FileOutputStream的话尽管能够加快下载的速度<br> * 可是进度文件的时时写入会出现故障.<br> * * <br> * 眼下我还没有找到非常好的解决方案,假设大家有的话欢迎给我留言. * * @author MartinDong * */ public class Demo { // 定义线程个数 public static int threadCount = 3; // 定义当前存货的线程个数 public static int runningThread = 3; public static void main(String[] args) throws Exception { // 1,连接到server,获取一个文件,获取文件的大小跟server的文件一样的暂时文件 String path = "http://172.22.64.193:8080/test.exe"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // 设置超时 conn.setConnectTimeout(5000); // 设置请求方式 conn.setRequestMethod("GET"); // 获取server的返回码 int code = conn.getResponseCode(); // 推断返回码 if (code == 200) { // 获取返回的长度 int length = conn.getContentLength(); System.out.println("文件总长度:" + length); // 在client创建出一个跟server大小一致的暂时文件 RandomAccessFile raf = new RandomAccessFile("test.exe", "rwd"); // 指定暂时文件的大小 raf.setLength(length); // 释放资源 raf.close(); // 平均每个线程的文件大小 int blockSize = length / threadCount; for (int threadId = 1; threadId <= threadCount; threadId++) { // 线程開始的下载位置 int startIndex = (threadId - 1) * blockSize; // 线程的结束位置 int endIndex = threadId * blockSize - 1; // 推断是否是最后一个线程 if (threadId == threadCount) { // 设置结束的位置为到文件的最后 endIndex = length; } System.out.println("线程:" + threadId + "下载:>>>>>>>>" + startIndex + ">>>>>>>>>>" + endIndex); new DownlodeThread(path, threadId, startIndex, endIndex) .start(); } } } /** * 下载文件的子线程类,每个线程下载相应位置文件数据 * * @author MartinDong * */ public static class DownlodeThread extends Thread { private String path; private int threadId; private int startIndex; private int endIndex; /** * * @param path * 文件的下载路径 * @param threadId * 线程id * @param startIndex * 线程開始的位置 * @param endIndex * 线程结束的位置 */ public DownlodeThread(String path, int threadId, int startIndex, int endIndex) { this.path = path; this.threadId = threadId; this.startIndex = startIndex; this.endIndex = endIndex; } @Override public void run() { try { // 检查是否存在下载历史的文件 File tempFile = new File(threadId + ".txt");// =========================断点记录操作=============================== if (tempFile.exists() && tempFile.length() > 0) { // 文件输入流 FileInputStream fis = new FileInputStream( tempFile); // 中间变量,缓存的作用 byte[] tempBuffer = new byte[1024]; // 获取进度文件的数据大小 int length = fis.read(tempBuffer); // 获取进度文件的数据 String historyData = new String(tempBuffer, 0, length); // 将进度数据装换为整型 int historyDataInt = Integer.parseInt(historyData); // 改动真正的下载位置 startIndex = historyDataInt; fis.close(); }// =========================断点记录操作=============================== // 将地址转换为URL URL url = new URL(path); // 获取http连接 HttpURLConnection conn = (HttpURLConnection) url .openConnection(); // 设置连接的请求方式 conn.setRequestMethod("GET"); // 重要:请求server下载部分的文件,指定文件的位置 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); System.out .println("线程:" + threadId + "真实開始的下载进度:" + startIndex); // 设置超时时间 conn.setReadTimeout(5000); // 得到server的状态码,200表示请求的所有资源得到响应=== ok,206请求的部分资源得到响应=== ok int code = conn.getResponseCode(); System.out.println("code:" + code); if (code == 206) { // 返回的是指定位置的文件流 InputStream is = conn.getInputStream(); // 创建一个暂时的文件 RandomAccessFile raf = new RandomAccessFile("test.exe", "rwd"); // 移动指针,到指定的文件位置, raf.seek(startIndex); // 创建中间缓冲字节数组 byte[] buffer = new byte[1024]; // 读取文件的大小 int length = 0; // 定义已经下载的数据长度,用作断点下载的记录=========================断点记录操作=============================== int downlodeTotal = 0; // 循环写入 while ((length = is.read(buffer)) != -1) { // 定义一个记录线程的记录文件=========================断点记录操作=============================== RandomAccessFile historyFile = new RandomAccessFile( threadId + ".txt", "rwd"); // 向文件里写入数据 raf.write(buffer, 0, length); // 记录已经下载的文件长度 downlodeTotal += length; // 将已经下载的文件长度和開始的读取位置相加,得到已经读取的文件位置 historyFile.write((downlodeTotal + startIndex + "") .getBytes()); historyFile.close();// =========================断点记录操作=============================== System.out.println("线程:" + threadId + "已下载:" + downlodeTotal); } is.close(); raf.close(); System.out.println("线程:" + threadId + "完成下载............"); } else { System.out.println("线程:" + threadId + "下载失败请又一次下载............"); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { // 进行线程数量的变化操作 runningThread--; // 假设当前存活的线程为0,运行进度文件统一销毁的操作 if (runningThread == 0) { // 假设完成下载,清除进度文件 for (int threadIndex = 1; threadIndex <= threadCount; threadIndex++) { // 这里创建的是与线程文件相应的文件,文件名称能够自己定义,方便起见是採用的是线程的ID表示 File temp = new File(threadIndex + ".txt"); // 运行文件删除的操作 temp.delete(); } System.out.println("文件完成下载,删除进度文件............."); } } } } }