• Java实现断点续传和原理


    原理说明:

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

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

    实现:

    当“续传”的行为开始,我们需要需要从上次记录的position位置开始读写操作,所以我们需要一个类似于“指针”功能的东西。
    我们当然也可以自己想办法去实现这样一个“指针”,但高兴地是,Java已经为我们提供了这样的一个类,那就是RandomAccessFile。
    这个类的功能从名字就很直观的体现了,能够随机的去访问文件。

    我们看一下API文档中对该类的说明:

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

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

    写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。

    为了通过对比加深理解,我们先来写一段正常的代码,即正常读写,不发生中断:

    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 {

    }


    在这次改动当中都做了什么工作:

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

    在我们这段代码,开启”续传“行为,即keepGoing方法中:我们起头让线程休眠10秒钟,这正是为了让我们运行程序看到效果。
    现在我们运行程序,那么文件就会开启“由D盘上传到E盘的过程”,我们首先点开E盘,会发现的确多了一个test.txt文件,打开它发现内容如下只有"abc"

    没错,这个时候我们发现内容只有“abc”。这是在我们预料以内的,因为我们的程序模拟在文件上传了3个字节的时候发生了中断。

    Ok,我们静静的等待10秒钟过去,然后再点开该文件,看看是否能够成功:

    我们发现内容的确已经变成了“abcdef”,由此也就完成了续传。

    END

  • 相关阅读:
    [bzoj1113][Poi2008]海报PLA
    [CF1111D]Destroy the Colony
    [CF1111E]Tree
    [CF1111C]Creative Snap
    [洛谷P5136]sequence
    [洛谷P5190][COCI 2010] PROGRAM
    [洛谷P5137]polynomial
    US Open 2016 Contest
    【hackerrank】Week of Code 26
    usaco中遇到的问题
  • 原文地址:https://www.cnblogs.com/xiaweicn/p/15602326.html
Copyright © 2020-2023  润新知