java: 多线程复制文件

  • 突然想写一个多线程下载的小案例。但是并不知道怎么做,所以想先写一个多线程复制文件的小案例。

    搜遍全网,都没有找到一个类似的。不过还是找到一篇有参考价值的博文:jAVA基础 提高文件复制性能之多线程复制文件
    以及这篇java多线程复制文件,RandomAccessFile类

  • 想起每次面试都会被问起:怎么实现多线程下载?很慌。每次回答都是支支吾吾的。毕竟,我不能直接说,我都是用开源框架直接实现的,并没有去看过实现源码~ 一种被支配的恐惧,一直笼罩在我内心…

好了,不多说了,贴一下我实现的代码:

  • 首先看 Main.java 这是一个调用类:
package com.cat.multi.copy;

import java.io.File;
import java.io.IOException;

/**
 * Created by cat on 2018/1/20.
 * 多线程拷贝
 */
public class Main {

    public static void main(String[] args) throws IOException, InterruptedException {

        String path = Class.class.getClass().getResource("/").getPath();
        String rootPath = new File(path).getParentFile().getParentFile().getParentFile().getPath();

        File sf = new File(rootPath, "raw/src/memo_methine.pdf"); // src
        File df = new File(rootPath, "raw/dest/memo_methine_copied.pdf"); // dest
        if (df.isFile()) {
            df.delete();
        }

        long srcsize = sf.length();

        System.out.println("srcSize==" + srcsize);

//        long pos = 0;
//        long end = srcsize / 2 / 1024 * 1024;

        long pos1 = 0;
        long end1= srcsize / 2 / 1024 * 1024;
        Copy copy1 = new Copy(sf.getAbsolutePath(), df.getAbsolutePath(), pos1, end1);

        long pos2 = srcsize / 2 / 1024 * 1024;
        long end2 = srcsize;
        Copy copy2 = new Copy(sf.getAbsolutePath(), df.getAbsolutePath(), pos2, end2);
        new Thread(copy2).start();
//        Thread.sleep(10);
        new Thread(copy1).start();
    }
}

可以看到,调用还是比较简单的,不过要传4个参数srcPath,destPath,pos,end。为啥要4个参数,这里简要说明一下:

srcPathdestPath 就不说了,就是源文件路径,以及复制后的目标路径
pos 是啥?是 RandomAccessFile#seek(pos)方法需要的参数。表示偏移位置,从pos 开始去读写数据。
end 是啥?既然是多线程复制,也就是表示如果一个文件是 3M 的,然后开3个线程去复制,那么,每个线程都去复制 1M 的大小就可以了。所以,end 是和 pos 组合使用的,表示,当前线程复制的文件的起点位置和终点位置。(可以把文件理解成一个数据流,流就用节点和长度…)

然后是 Copy.java 的代码:

package com.cat.multi.copy;

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

/**
 * 
 *
 * Created by cat on 2018/1/20.
 * 小应用:多线程拷贝数据
 * 分析一下:多线程拷贝是否需要考虑线程安全?
 *      1. 是否是多线程运行环境? ---> 是
 *      2. 是否需要访问共享数据? ---> 否
 *          * 为什么说没有访问共享数据,因为调用的时候是为每个线程分别创建了一个Copy 对象
 *      3. 操作共享数据是否是多条语句?--> 是
 *
 * 
*/
public class Copy implements Runnable { private final int bufferSize = 1024; private final String srcPath; private final String dest; private long pos; private long end; /** * @param dest 全路径 --> /data/storage/camera/dog.jpg * @param srcPath 全路径 */ public Copy(String srcPath, String dest, long pos, long end) { this.srcPath = srcPath; this.dest = dest; this.pos = pos; this.end = end; } /** * 传统的读写文件的方式 --> 可用,但是不能实现分段读写文件。 * * @throws IOException io */ private void copy() throws IOException { RandomAccessFile bin = new RandomAccessFile(srcPath, "r"); RandomAccessFile bout = new RandomAccessFile(dest, "rw"); // BufferedInputStream bin = new BufferedInputStream(new FileInputStream(src)); // BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(df)); byte[] buffer = new byte[1024]; int read; while ((read = bin.read(buffer)) != -1) { bout.write(buffer, 0, read); } bout.close(); bin.close(); } /** * need copy 2 * * @throws IOException */ public void copy1() throws IOException, InterruptedException { if (this.pos == this.end) { System.out.println(" 文件已经复制结束了...."); return; } Thread.sleep(100); RandomAccessFile bin = new RandomAccessFile(srcPath, "r"); RandomAccessFile bout = new RandomAccessFile(dest, "rw"); byte[] buffer = new byte[bufferSize]; int read; long total = 0; bin.seek(pos); bout.seek(pos); while ((read = bin.read(buffer)) != -1) { bout.write(buffer, 0, read); total += read; if (total + pos == this.end) { System.out.println("我的任务完成了. 当前完成的数据为:" + total); System.out.println("c1. from:" + pos + " , end=" + end + " , total=" + new File(srcPath).length()); this.pos = this.end; break; } else if (total > this.end) { throw new RuntimeException("end 计算出错了,需要修改程序,否则数据复制会出错..."); } } bout.close(); bin.close(); } @Override public void run() { while (true) { if (this.pos == this.end) { System.out.println(" 文件已经复制结束了...."); break; } try { copy1(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } } }

这里重点是Copy#copy1()这个方法,通过改变pos实现了对文件的分段复制。
看如下代码片段:

byte[] buffer = new byte[bufferSize];
        int read;
        long total = 0;
        bin.seek(pos);  // 关键点在这里
        bout.seek(pos); // in 和 out 都需要 seek()
        while ((read = bin.read(buffer)) != -1) {

            bout.write(buffer, 0, read);
            total += read;

            if (total + pos == this.end) {
                System.out.println("我的任务完成了. 当前完成的数据为:" + total);
                System.out.println("c1. from:" + pos + " , end=" + end + " , total=" + new File(srcPath).length());
                this.pos = this.end;
                break;
            } else if (total > this.end) {
                throw new RuntimeException("end 计算出错了,需要修改程序,否则数据复制会出错...");
            }
        }

分段复制的大体思路就是:先计算好每一段需要复制的文件的起点和终点,对应的参数就是pos , end。然后再复制的时候,按照这个参数去复制就可以了。

  • 起点移动到 pos这里。bin.seek(pos);bout.seek(pos);
  • 然后到终点的时候,就停止复制,把后面的内容留个下个线程去操作:if (total + pos == this.end){...break;}

最后,说明一下,这个多线程复制文件的程序,是不用考虑线程安全的问题的。因为不涉及多个线程对共享数据的写操作。虽然,是多线程共同写目标文件了,但是分工明确,每个线程只写自己的pos,end的数据。

你可能感兴趣的:(Java,computer)