断点续传

Java 断点续传
(1)原理
在下载行为出现中断的时候,记录下中断的位置信息,然后在下次行为开始的时候,直接从记录的这个位置开始下载内容,而不再从头开始。
分为两步:

当“上传(下载)的行为”出现中断,我们需要记录本次上传(下载)的位置(position)。
当“续”这一行为开始,我们直接跳转到postion处继续上传(下载)的行为。
(2)代码

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

public class Test {
// step1:首先,我们定义了一个变量position,记录在发生中断的时候,已完成读写的位置。(这是为了方便,实际来说肯定应该讲这个值存到文件或者数据库等进行持久化)
    private static int position = -1;

    public static void main(String[] args) {
        // 源文件与目标文件
        File sourceFile = new File("D:/", "test.txt");
        File targetFile = new File("E:/", "test.txt");
        // 输入输出流
        FileInputStream fis = null;
        FileOutputStream fos = null;
        // 数据缓冲区
        byte[] buf = new byte[1];

        try {
            fis = new FileInputStream(sourceFile);
            fos = new FileOutputStream(targetFile);
            // 数据读写
            while (fis.read(buf) != -1) {
                fos.write(buf);
// step2:然后在文件读写的while循环中,我们去模拟一个中断行为的发生。这里是当targetFile的文件长度为3个字节则模拟抛出一个我们自定义的异常。(我们可以想象为实际下载中,已经上传(下载)了”x”个字节的内容,这个时候网络中断了,那么我们就在网络中断抛出的异常中将”x”记录下来)。
                if (targetFile.length() == 3) {
                    position = 3;
                    throw new FileAccessException();
                }
            }
        } catch (FileAccessException e) {
//step3:开启”续传“行为,即keepGoing方法.
            keepGoing(sourceFile,targetFile, position);
        } catch (FileNotFoundException e) {
            System.out.println("指定文件不存在");
        } catch (IOException e) {
            // TODO: handle exception
        } finally {
            try {
                // 关闭输入输出流
                if (fis != null)
                    fis.close();

                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    private static void keepGoing(File source,File target, int position) {
// step3.1:我们起头让线程休眠10秒钟,这正是为了让我们运行程序看到效果。     
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
// step3.2:在“续传”行为开始后,通过RandomAccessFile类来包装我们的文件,然后通过seek将指针指定到之前发生中断的位置进行读写就搞定了。 
(实际的文件下载上传,我们当然需要将保存的中断值上传给服务器,这个方式通常为
        try {
            RandomAccessFile readFile = new RandomAccessFile(source, "rw");
            RandomAccessFile writeFile = new RandomAccessFile(target, "rw");
            readFile.seek(position);
            writeFile.seek(position);

            // 数据缓冲区
            byte[] buf = new byte[1];
            // 数据读写
            while (readFile.read(buf) != -1) {
                writeFile.write(buf);
            }
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class FileAccessException extends Exception {

}

(3)实现结果
运行程序,那么文件就会开启“由D盘上传到E盘的过程”,我们首先点开E盘,会发现的确多了一个test.txt文件,打开它发现内容如下:

在这里插入图片描述

这个时候我们发现内容只有“abc”。这是在我们预料以内的,因为我们的程序模拟在文件上传了3个字节的时候发生了中断。
等待10秒钟过去,然后再点开该文件,发现内容的确已经变成了“abc”,由此也就完成了续传。


在这里插入图片描述

android中的实现

 import...;
  2 
  3 public class MainActivity extends AppCompatActivity {
  4 
  5     private EditText et_path;
  6     private EditText et_threadCount;
  7     private LinearLayout ll_pb;
  8     private String path;
  9 
 10     private static int runningThread;// 代表正在运行的线程
 11     private int threadCount;
 12     private List<ProgressBar> pbList;//集合存储进度条的引用
 13 
 14     @Override
 15     protected void onCreate(Bundle savedInstanceState) {
 16         super.onCreate(savedInstanceState);
 17         setContentView(R.layout.activity_main);
 18 
 19         et_path = findViewById(R.id.et_path);
 20         et_threadCount = findViewById(R.id.et_threadCount);
 21         ll_pb = findViewById(R.id.ll_pb);
 22         //添加一个进度条的引用
 23         pbList = new ArrayList<ProgressBar>();
 24     }
 25 
 26     //点击按钮实现下载逻辑
 27     public void click(View view) {
 28         //获取下载路径
 29         path = et_path.getText().toString().trim();
 30         //获取线程数量
 31         String threadCounts = et_threadCount.getText().toString().trim();
 32         //移除以前的进度条添加新的进度条
 33         ll_pb.removeAllViews();
 34         threadCount = Integer.parseInt(threadCounts);
 35         pbList.clear();
 36         for (int i = 0; i < threadCount; i++) {
 37             ProgressBar v = (ProgressBar) View.inflate(getApplicationContext(), R.layout.layout, null);
 38 
 39             //把v添加到几何中
 40             pbList.add(v);
 41 
 42             //动态获取进度条
 43             ll_pb.addView(v);
 44         }
 45 
 46         //java逻辑移植
 47         new Thread() {
 48             @Override
 49             public void run() {
 50                 /*************/
 51                 System.out.println("你好");
 52                 try {
 53                     URL url = new URL(path);
 54                     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 55                     conn.setRequestMethod("GET");
 56                     conn.setConnectTimeout(5000);
 57                     int code = conn.getResponseCode();
 58                     if (code == 200) {
 59                         int length = conn.getContentLength();
 60                         // 把运行线程的数量赋值给runningThread
 61                         runningThread = threadCount;
 62 
 63                         System.out.println("length=" + length);
 64                         // 创建一个和服务器的文件一样大小的文件,提前申请空间
 65                         RandomAccessFile randomAccessFile = new RandomAccessFile(getFileName(path), "rw");
 66                         randomAccessFile.setLength(length);
 67                         // 算出每个线程下载的大小
 68                         int blockSize = length / threadCount;
 69                         // 计算每个线程下载的开始位置和结束位置
 70                         for (int i = 0; i < length; i++) {
 71                             int startIndex = i * blockSize;// 开始位置
 72                             int endIndex = (i + 1) * blockSize;// 结束位置
 73                             // 特殊情况就是最后一个线程
 74                             if (i == threadCount - 1) {
 75                                 // 说明是最后一个线程
 76                                 endIndex = length - 1;
 77                             }
 78                             // 开启线程去服务器下载
 79                             DownLoadThread downLoadThread = new DownLoadThread(startIndex, endIndex, i);
 80                             downLoadThread.start();
 81 
 82                         }
 83 
 84                     }
 85                 } catch (MalformedURLException e) {
 86                     // TODO Auto-generated catch block
 87                     e.printStackTrace();
 88                 } catch (IOException e) {
 89                     // TODO Auto-generated catch block
 90                     e.printStackTrace();
 91                 }
 92                 /*************/
 93             }
 94         }.start();
 95 
 96     }
 97 
 98     private class DownLoadThread extends Thread {
 99         // 通过构造方法吧每个线程的开始位置和结束位置传进来
100         private int startIndex;
101         private int endIndex;
102         private int threadID;
103         private int PbMaxSize;//代表当前下载(进度条)的最大值
104         private int pblastPosition;//如果中断过,这是进度条上次的位置
105 
106         public DownLoadThread(int startIndex, int endIndex, int threadID) {
107             this.startIndex = startIndex;
108             this.endIndex = endIndex;
109             this.threadID = threadID;
110 
111         }
112 
113         @Override
114         public void run() {
115             // 实现去服务器下载文件
116             try {
117                 //计算进度条最大值
118                 PbMaxSize = endIndex - startIndex;
119                 URL url = new URL(path);
120                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
121                 conn.setRequestMethod("GET");
122                 conn.setConnectTimeout(5000);
123                 // 如果中间断过,接着上次的位置继续下载,聪慧文件中读取上次下载的位置
124                 File file = new File(getFileName(path) + threadID + ".txt");
125                 if (file.exists() && file.length() > 0) {
126                     FileInputStream fis = new FileInputStream(file);
127                     BufferedReader bufr = new BufferedReader(new InputStreamReader(fis));
128                     String lastPosition = bufr.readLine();
129                     int lastPosition1 = Integer.parseInt(lastPosition);
130 
131                     //赋值给进度条位置
132                     pblastPosition = lastPosition1 - startIndex;
133                     // 改变一下startIndex的值
134                     startIndex = lastPosition1 + 1;
135                     System.out.println("线程id:" + threadID + "真实下载的位置:" + lastPosition + "-------" + endIndex);
136 
137                     bufr.close();
138                     fis.close();
139 
140                 }
141 
142                 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
143                 int code = conn.getResponseCode();
144                 if (code == 206) {
145                     // 随机读写文件对象
146                     RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
147                     // 每个线程从自己的位置开始写
148 
149                     raf.seek(startIndex);
150                     InputStream in = conn.getInputStream();
151                     // 把数据写到文件中
152                     int len = -1;
153                     byte[] buffer = new byte[1024];
154                     int totle = 0;// 代表当前线程下载的大小
155                     while ((len = in.read(buffer)) != -1) {
156                         raf.write(buffer, 0, len);
157                         totle += len;
158 
159                         // 实现断点续传就是把当前线程下载的位置保存起来,下次再下载的时候按照上次下载的位置继续下载
160                         int currentThreadPosition = startIndex + totle;// 存到一个txt文本中
161                         // 用来存储当前线程当前下载的位置
162                         RandomAccessFile raff = new RandomAccessFile(getFileName(path) + threadID + ".txt", "rwd");
163                         raff.write(String.valueOf(currentThreadPosition).getBytes());
164                         raff.close();
165 
166                         //设置进度条当前的进度
167                         pbList.get(threadID).setMax(PbMaxSize);
168                         pbList.get(threadID).setProgress(pblastPosition + totle);
169                     }
170                     raf.close();
171                     System.out.println("线程ID:" + threadID + "下载完成");
172                     // 将产生的txt文件删除,每个线程下载完成的具体时间不知道
173                     synchronized (DownLoadThread.class) {
174                         runningThread--;
175                         if (runningThread == 0) {
176                             //说明线程执行完毕
177                             for (int i = 0; i < threadCount; i++) {
178 
179                                 File filedel = new File(getFileName(path) + i + ".txt");
180                                 filedel.delete();
181                             }
182 
183                         }
184 
185                     }
186 
187                 }
188             } catch (MalformedURLException e) {
189                 // TODO Auto-generated catch block
190                 e.printStackTrace();
191             } catch (IOException e) {
192                 // TODO Auto-generated catch block
193                 e.printStackTrace();
194             }
195 
196         }
197     }
198 
199     public String getFileName(String path) {
200         int start = path.lastIndexOf("/") + 1;
201         String subString = path.substring(start);
202         String fileName = "/data/data/com.lgqrlchinese.heima76android_11_mutildownload/" + subString;
203         return fileName;
204 
205     }
206 }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,270评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,489评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,630评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,906评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,928评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,718评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,442评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,345评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,802评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,984评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,117评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,810评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,462评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,011评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,139评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,377评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,060评论 2 355