zoukankan      html  css  js  c++  java
  • RandomAccessFile 解决多线程下载及断点续传

    导读:本篇文章主要介绍 RandomAccessFile,该类是 IO 流体系中功能最丰富的文件内容访问类,既可以读取文件内容,也可以向文件输出数据。总结本篇文章希望对从事相关工作的同学能够有所帮助或者启发

    一、背景


    在针对文件处理当网络环境不好,出现上传失败的时候,可以对失败的 Part 进行独立的重试,而不需要重新上传其他的 Part;中途暂停之后,可以从上次上传完成的 Part 的位置继续上传。或者要上传到 OSS 的本地文件很大的时候,可以并行上传多个 Part 以加快上传;再或者面对一些文件比较大时,我们需要对大文件进行切割分批上传完后再合并处理。

     

    二、RandomAccessFile 简介


    RandomAccessFile 既可以读取文件内容,也可以向文件输出数据。同时,RandomAccessFile 支持“随机访问”的方式,程序快可以直接跳转到文件的任意地方来读写数据。

     

    由于 RandomAccessFile 可以自由访问文件的任意位置,所以如果需要访问文件的部分内容,而不是把文件从头读到尾,使用 RandomAccessFile 将是更好的选择。

     

    与 OutputStream、Writer 等输出流不同的是,RandomAccessFile 允许自由定义文件记录指针,RandomAccessFile 可以不从开始的地方开始输出,因此 RandomAccessFile 可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用 RandomAccessFile。

     

     

    从类图中可以看出 RandomAccessFile 实现 DataInput 和 DataOutput 数据写入和数据写出函数,下面是具体实现函数声明。

     

    ▐ RandomAccessFile 的构造函数

    RandomAccessFile 类有两个构造函数,其实这两个构造函数基本相同,只不过是指定文件的形式不同—。

     

    一个需要使用 String 参数来指定文件名

     

     

    另一个个使用 File 参数来指定文件本身。

    除此之外,创建 RandomAccessFile 对象时还需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式,一共有 4 种模式。

    • **"r" : ** 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。

    • "rw": 打开以便读取和写入。

    • "rws": 打开以便读取和写入。相对于 "rw","rws" 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。

    • "rwd" : 打开以便读取和写入,相对于 "rw","rwd" 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。

     

    ▐ RandomAccessFile 的重要方法

    RandomAccessFile 类包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个 RandomAccessFile 对象时,该对象的文件记录指针位于文件头(也就是 0 处),当读/写了 n 个字节后,文件记录指针将会向后移动 n 个字节。除此之外,RandomAccessFile 可以自由的移动记录指针,即可以向前移动,也可以向后移动。RandomAccessFile 包含了以下两个方法来操作文件的记录指针.

    • long getFilePointer(); 返回文件记录指针的当前位置

    • void seek(long pos); 将文件记录指针定位到 pos 位置

     

    三、RandomAccessFile 的使用


    ▐ 指定位置读取文件

    public static void main(String[] args) {
        RandomAccessFile accessFile = null;
        try {
            File file = new File(filePath);
            accessFile = new RandomAccessFile(file, "r");
    
            // 获取 RandomAccessFile对象文件指针的位置,初始位置为0
            log.debug("输入内容:{}", accessFile.getFilePointer());
    
            // 移动文件记录指针的位置
            accessFile.seek(1000);
    
            byte[] b = new byte[1024];
            int hasRead = 0;
            //循环读取文件
            while ((hasRead = accessFile.read(b)) > 0) {
                //输出文件读取的内容
                System.out.print(new String(b, 0, hasRead));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            accessFile.close();
        }
    }

     

     

    在上面的程序的关键代码两处,一处是创建了 RandomAccessFile 对象,该对象以只读模式打开了文件,这意味着 RandomAccessFile 文件只能读取文件内容,不能执行写入。第二处调用了 seek(1000)方法,是指把文件的记录指针定位到 1000 字节的位置。也就是说程序将从 1000 字节开始读取数据。其他部分的代码的读取方式和其他的输入流没有区别。

    ▐ 向文件中追加内容

     

     

     
    public static void main(String[] args) {
        RandomAccessFile accessFile = null;
        File file = null;
        try {
            file = new File(filePath);
            // 以读写的方式打开一个RandomAccessFile对象
            accessFile = new RandomAccessFile(file, "rw");
    
            //将记录指针移动到该文件的最后
            accessFile.seek(accessFile.length());
    
            //向文件末尾追加内容
            accessFile.writeChars("这是追加内容。。");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            accessFile.close();
        }
    }
     

    上面代码先以读,写方式创建了一个 RandomAccessFile 对象,然后将文件记录指针移动到最后,接下来使用 RandomAccessFile 向文件中写入内容。和其他输出例 OutputStream 的方式相同。每运行一次上面的程序,就能发现 text.txt 文件中多添加了一行内容。

    ▐ 向文件指定位置插入内容

     

    注:RandomAccessFile 不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输出的内容会覆盖文件原有的内容,如果需要向指定位置插入内容,程序需要先把插入点后面的内容写入缓存区,等把需要插入的数据写入到文件后,再将缓存区的内容追加到文件后面。

    /**
     * 向文件指定位置插入内容
     *
     * @param filePath     源文件路径
     * @param pos          插入文件指定位置
     * @param writeContent 写入内容
     */
    private static void readFileThenWrite(String filePath, long pos, String writeContent) throws IOException {
        RandomAccessFile raf = null;
        File tempFile = File.createTempFile("tmp", null);
        tempFile.deleteOnExit();
        try {
            // 以读写的方式打开一个RandomAccessFile对象
            raf = new RandomAccessFile(new File(filePath), "rw");
    
            // 创建一个临时文件来保存插入点后的数据
            FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
            FileInputStream fileInputStream = new FileInputStream(tempFile);
    
            // 把文件记录指针定位到pos位置
            raf.seek(pos);
            raf.seek(pos);
    
            //------------将插入点后的内容读入临时文件中保存------------
            byte[] bytes = new byte[64];
            //用于保存实际读取的字节数据
            int hasRead = 0;
            //使用循环读取插入点后的数据
            while ((hasRead = raf.read(bytes)) != -1) {
                //将读取的内容写入临时文件
                fileOutputStream.write(bytes, 0, hasRead);
            }
    
            //------------用于插入内容 ------------
            //把文件记录指针重新定位到pos位置
            raf.seek(pos);
    
            //追加需要插入的内容
            raf.write(writeContent.getBytes());
    
            //追加临时文件中的内容
            while ((hasRead = fileInputStream.read(bytes)) != -1) {
                //将读取的内容写入临时文件
                raf.write(bytes, 0, hasRead);
            }
        } catch (Exception e) {
            throw e;
        }
    }
     

    上面的程序使用 File 类的 createTempFile 方法创建了一个临时文件(该文件将在 JVM 退出后被删除),用于保存被插入点后面的内容。程序先将文件中插入点后的内容读入临时文件中,然后重新定位到插入点,将需要插入的内容添加到文件后面,最后将临时文件的内容添加到文件后面,通过这个过程就可以向指定文件,指定位置插入内容。每次运行上面的程序,都会看到文件中多了一行内容。

     

    四、总结


    通过阅读 RandomAccessFile 源码,你会发现虽然方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他 IO 节点。

     

    但是由于 RandomAccessFile 可以自由访问文件的任意位置,所以如果需要访问文件的部分内容,而不是把文件从头读到尾,因此 RandomAccessFile 的一个重要使用场景就是网络请求中的多线程下载及断点续传。

     

    本篇文章主要介绍 RandomAccessFile,该类是 IO 流体系中功能最丰富的文件内容访问类,既可以读取文件内容,也可以向文件输出数据,总结本篇文章主要是为了承接上篇文章 《https://mp.weixin.qq.com/s/yxBG4t-GVRYYUIMXUcOwKw》的话题对于文件分片的处理的一个知识点的补充。

     

  • 相关阅读:
    第36课 经典问题解析三
    第35课 函数对象分析
    67. Add Binary
    66. Plus One
    58. Length of Last Word
    53. Maximum Subarray
    38. Count and Say
    35. Search Insert Position
    28. Implement strStr()
    27. Remove Element
  • 原文地址:https://www.cnblogs.com/ibytecoding/p/15745499.html
Copyright © 2011-2022 走看看