您的位置:首页 > 编程语言 > Java开发

java: 多线程复制文件

2018-01-21 01:58 309 查看
突然想写一个多线程下载的小案例。但是并不知道怎么做,所以想先写一个多线程复制文件的小案例。

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

srcPath
destPath
就不说了,就是源文件路径,以及复制后的目标路径

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
的数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: