java: 多线程复制文件

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

    搜遍全网,都没有找到一个类似的。不过还是找到一篇有参考价值的博文: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;

/**
 * <pre>
 *
 * Created by cat on 2018/1/20.
 * 小应用:多线程拷贝数据
 * 分析一下:多线程拷贝是否需要考虑线程安全?
 *      1. 是否是多线程运行环境? ---> 是
 *      2. 是否需要访问共享数据? ---> 否
 *          * 为什么说没有访问共享数据,因为调用的时候是为每个线程分别创建了一个Copy 对象
 *      3. 操作共享数据是否是多条语句?--> 是
 *
 * </pre>
 */
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的数据。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值