RandomAccessFile短点续传

RandomAccessFile短点续传

1. 什么是短点续传?

# FTP(文件传输协议的简称)(File Transfer Protocol、 FTP)客户端软件断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。

2. RandomAccessFile类

2.1 读写的函数
package com.liu.learn;

import org.junit.Test;

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;


/**
 * 读写函数
 * @author lms
 * @date 2021-12-25 - 19:43
 */
public class RandomAccessFileTest {

    @Test
    public void read() throws Exception {
        File file = new File("D:/E_DISK/IDEAJavaCode/BreakingPointFile/a.txt");
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
        // 默认读取一个字节
        randomAccessFile.read();

        // 相对路径,跳过前3个字节开始读取数据信息
        // randomAccessFile.skipBytes(3);

        // 绝对路径(从文件头开始读取数据)
        randomAccessFile.seek(3);

        byte[] bytes = new byte[1024];
        // 返回读取到的字节数
        int len = randomAccessFile.read(bytes);
        System.out.println("len = " + len);
        System.out.println("str = " + new String(bytes, 0, len));
    }

    @Test
    public void write() throws Exception {
        File file = new File("D:/E_DISK/IDEAJavaCode/BreakingPointFile/a.txt");
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");

        String str = "zhangsan";
        randomAccessFile.write(str.getBytes(StandardCharsets.UTF_8));
    }
}
2.2 单线程的文件复制
package com.liu.learn;

import org.junit.Test;

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

/**
 * @author lms
 * @date 2021-12-25 - 20:25
 * 实现多线程的文件复制操作
 */
public class RandomAccessFileTest2 {
    /**
     * 测试单线程的文件复制
     */
    @Test
    public void SingleFile() {
        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            File file = new File("C:/Users/Administrator/Desktop/PCQQ2021.exe");
            fis = new FileInputStream(file);
            fos = new FileOutputStream("D:\\360Downloads\\xxx.exe");

            // 一次读取8k字节
            byte[] bytes = new byte[1024 * 8];
            int len = -1;
            long start = System.currentTimeMillis();
            while ((len = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, len);
            }
            long end = System.currentTimeMillis();
            System.out.println("总耗时: " + (end - start));
            System.out.println("文件复制完成~~~");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
2.3 多线程下的文件复制
package com.liu.learn;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;

/**
 * 使用RandomAccessFile实现多线程文件的复制
 *
 * @author lms
 * @date 2021-12-25 - 20:45
 */
public class RandomAccessFileTest3 {
    public static void main(String[] args) {
        File file = new File("C:/Users/Administrator/Desktop/PCQQ2021.exe");

        // 用于存放线程
        ArrayList<Thread> threadArrayList = new ArrayList<>();

        // 线程的数量
        int threadNum = 5;
        // 计算文件的总长度
        long length = file.length();
        // 每个线程分得的长度(向上取整,因为randomAccessFile会对重叠的部分进行合并整合)
        int partLen = (int) Math.ceil(length / threadNum);

        // 创建线程
        for (int i = 0; i < threadNum; i++) {
            final int k = i;
            Thread thread = new Thread(() -> {
                // 每个线程需要做的事情
                try {
                    // 进行文件的读写
                    RandomAccessFile rafIn = new RandomAccessFile(file, "r");
                    RandomAccessFile rafOut = new RandomAccessFile("D:/360Downloads/xxx.exe", "rw");

                    // 计算每个线程读取文件的开始位置
                    rafIn.seek(k * partLen);
                    // 每个写文件的开始位置
                    rafOut.seek(k * partLen);

                    int plen = 0, len = -1;
                    // 每次读取的文件大小为 8k
                    byte[] bytes = new byte[1024 * 8];

                    // 不断的读取,写文件
                    while (true) {
                        // 记录当前读取的字节数
                        len = rafIn.read(bytes);

                        // -1说明整个文件已经被读取完毕
                        if (len == -1) {
                            break;
                        }
                        // 否则记录每次读取的字节数,并将其累加到plen中
                        plen += len;
                        rafOut.write(bytes, 0, len);
                        // 如果plen大于partLen,说明当前线程要写的文件长度已经达到,就执行退出操作
                        if (plen >= partLen) {
                            break;
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

            });
            // 开启线程
            thread.start();
            threadArrayList.add(thread);
        }
        long start = System.currentTimeMillis();
        // 阻塞主线程
        for (Thread thread : threadArrayList) {
            // 等所有的线程结束之后,main线程才能继续执行
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("总耗时: " + (end - start));
        System.out.println("文件复制完成~~~");
    }
}
2.4 断点续传的实现
package com.liu.learn;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 基于多线程的断点续传方法
 *
 * @author lms
 * @date 2021-12-25 - 20:45
 */
public class RandomAccessFileTest4 {
    public static void main(String[] args) throws Exception {
        File file = new File("C:/Users/Administrator/Desktop/PCQQ2021.exe");

        // 使用ConcurrentHashMap记录每个线程读取到位置
        ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>();

        // 用于存放线程
        ArrayList<Thread> threadArrayList = new ArrayList<>();

        // 线程的数量
        int threadNum = 5;
        // 计算文件的总长度
        long length = file.length();
        // 每个线程分得的长度(向上取整,因为randomAccessFile会对重叠的部分进行合并整合)
        int partLen = (int) Math.ceil(length / threadNum);
        // 日记文件
        String logFile = "D:/360Downloads/xxx.exe.log";

        // 读取日记文件
        String[] data = null;
        File fl = new File(logFile);
        if (fl.exists()) {
            BufferedReader reader = new BufferedReader(new FileReader(fl));
            String line = reader.readLine();
            // 拆分字符串
            data = line.split(",");
            reader.close();
        }
        final String[] _data = data;

        // 创建线程
        for (int i = 0; i < threadNum; i++) {
            final int k = i;
            Thread thread = new Thread(() -> {
                RandomAccessFile log = null;
                // 每个线程需要做的事情
                try {
                    // 进行文件的读写
                    RandomAccessFile rafIn = new RandomAccessFile(file, "r");
                    RandomAccessFile rafOut = new RandomAccessFile("D:/360Downloads/xxx.exe", "rw");
                    // 记录读到的日记文件
                    log = new RandomAccessFile(logFile, "rw");

                    // 计算每个线程读取文件的开始位置
                    // 需要从指定的位置开始读取数据,从而实现断点续传
                    rafIn.seek(_data == null ? k * partLen : Integer.parseInt(_data[k]));
                    // 每个写文件的开始位置
                    // 从指定的位置开始写输入数据
                    rafOut.seek(_data == null ? k * partLen : Integer.parseInt(_data[k]));

                    int plen = 0, len = -1;
                    // 每次读取的文件大小为 8k
                    byte[] bytes = new byte[1024 * 8];

                    // 不断的读取,写文件
                    while (true) {
                        // 记录当前读取的字节数
                        len = rafIn.read(bytes);

                        // -1说明整个文件已经被读取完毕
                        if (len == -1) {
                            break;
                        }
                        // 否则记录每次读取的字节数,并将其累加到plen中
                        plen += len;

                        // 将读取到的字节数量添加到map中
                        map.put(k, plen + (_data == null ? k * partLen : Integer.parseInt(_data[k])));
                        // 将读取到的数据进行写入
                        rafOut.write(bytes, 0, len);

                        // 写入完成之后,覆盖之前的log文件
                        log.seek(0);
                        // 以逗号进行分割,写入
                        StringJoiner joiner = new StringJoiner(",");
                        map.forEach((key, val) -> {
                            String.valueOf(val);
                        });
                        log.write(joiner.toString().getBytes(StandardCharsets.UTF_8));

                        // 如果plen大于等于下一个线程的开始位置,说明当前线程要写的文件长度已经达到,就执行退出操作
                        if (plen + (_data == null ? k * partLen : Integer.parseInt(_data[k])) >= (k + 1) * partLen) {
                            break;
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (log != null) {
                        try {
                            log.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }

            });
            // 开启线程
            thread.start();
            threadArrayList.add(thread);
        }
        long start = System.currentTimeMillis();
        // 阻塞主线程
        for (Thread thread : threadArrayList) {
            // 等所有的线程结束之后,main线程才能继续执行
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 线程读取完毕,将日记数据进行删除
        fl.delete();

        long end = System.currentTimeMillis();
        System.out.println("总耗时: " + (end - start));
        System.out.println("文件复制完成~~~");
    }
}
上一篇:java io 之RandomAccessFile类


下一篇:Java 基础 (IO 对象流,随机存储文件流)