一.断点续传

一.断点续传

1.1原理

​ “断点续传”最基础的原理,用大白话说就是:我们要在下载行为出现中断的时候,记录下中断的位置信息,然后在下次行为中读取。
​ 有了这个位置信息之后,想想我们该怎么做。是的,很简单,在新的下载行为开始的时候,直接从记录的这个位置开始下载内容,而不再从头开始,当“上传(下载)的行为”出现中断,我们需要记录本次上传(下载)的位置(position)。

​ 当“续”这一行为开始,我们直接跳转到postion处继续上传(下载)的行为。

​ 那么转换到所谓的“断点续传”,我们该使用什么来衡量“position”呢?

​ 很显然,回归二进制,因为这里的本质无非就是文件的读写。那么剩下的工作就很简单了,先是记录position,因为只是数据的持久化而已(内存,文件,数据库),我们有很多方式。另一个关键在于当“续传”的行为开始,我们需要从上次记录的position位置开始读写操作,则需要一个类似于“指针”功能的东西。我们当然也可以自己想办法去实现这样一个“指针”,但高兴地是,Java已经为我们提供了这样的一个类,那就是RandomAccessFile。

​ 这个类的功能从名字就很直观的体现了,能够随机的去访问文件。我们看一下API文档中对该类的说明:

此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。

​ 如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。

1.2举例

​ 假设我们现在有一款“通关制的RPG游戏”。想想我们在玩这类游戏时通常会怎么做?
​ 很明显,第一天我们浴血奋战,大杀四方,假设终于来到了第四关。虽然激战正酣,但一看墙上的时钟,已经凌晨12点,该睡觉了。
​ 这个时候就很尴尬了,为了能够在下一次玩的时候,顺利接轨上我们本次游戏的进度,我们应该怎么办呢?
很简单,我们不关掉游戏,直接去睡觉,第二天再接着玩呗。这样是可以,但似乎总觉着有哪里让人不爽。
那么,这个时候,如果这个游戏有一个功能叫做“存档”,就很关键了。我们直接选择存档,输入存档名“第四关”,然后就可以关闭游戏了。等到下次进行游戏时,我们直接找到“第四关”这个存档,然后进行读档,就可以接着进行游戏了。
​ 这个时候,所谓的“断点续传”就很好理解了。我们顺着我们之前“玩游戏”的思路来理一下:
假设,现在有一个文件需要我们进行下载,当我们下载了一部分的时候,出现情况了,比如:电脑死机、没电、网络中断等等。
其实这就好比我们之前玩游戏玩着玩着,突然12点需要去睡觉休息了是一个道理。OK,那么这个时候的情况是:

  • 如果游戏不能存档,那么则意味着我们下次游戏的时候,这次已经通过的4关的进度将会丢失,无法接档。
  • 对应的,如果“下载”的行为无法记录本次下载的一个进度。那么,当我们再次下载这个文件也就只能从头来过。

话到这里,其实我们已经发现了,对于我们以上所说的行为,关键就在于一个字“续”!
而我们要实现让一种断开的行为“续”起来的目的,关键就在于要有“介质”能够记录和读取行为出现”中断”的这个节点的信息。

1.3代码实现

​ 正常代码

public class Test {
 
 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) {
    System.out.println("write data...");
    fos.write(buf);
   }
  } 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();
   }
 
  }
 }
}
该段代码运行,我们就会发现在E盘中已经成功拷贝了一份“test.txt”。这段代码很简单,唯一稍微说一下就是:
我们看到我们将buf,即缓冲区 设置的大小是1,这其实就代表我们每次read,是读取一个字节的数据(即1个英文字母)。

现在,我们就来模拟这个读写中断的行为,将之前的代码完善如下:
续传代码
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 {
 
 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);
    // 当已经上传了3字节的文件内容时,网络中断了,抛出异常
    if (targetFile.length() == 3) {
     position = 3;
     throw new FileAccessException();
    }
   }
  } catch (FileAccessException e) {
   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) {
  try {
   Thread.sleep(10000);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 
  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 {
 
}
1.4总结

​ 在Java中对一个文件进行读写操作很简单。假设现在的需求如果是“把D盘的这个文件写入到E盘”,那么我们会提起键盘,啪啪啪啪,搞定!
​ 但其实所谓的文件的“上传(下载)”不是也没什么不同吗?区别就仅仅在于行为由“仅仅在本机之间”转变成了”本机与服务器之间”的文件读写。

  • 首先,我们定义了一个变量position,记录在发生中断的时候,已完成读写的位置。(这是为了方便,实际来说肯定应该讲这个值存到文件或者数据库等进行持久化)
  • 然后在文件读写的while循环中,我们去模拟一个中断行为的发生。这里是当targetFile的文件长度为3个字节则模拟抛出一个我们自定义的异常。(我们可以想象为实际下载中,已经上传(下载)了”x”个字节的内容,这个时候网络中断了,那么我们就在网络中断抛出的异常中将”x”记录下来)。
  • 剩下的就如果我们之前说的一样,在“续传”行为开始后,通过RandomAccessFile类来包装我们的文件,然后通过seek将指针指定到之前发生中断的位置进行读写就搞定了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值