import sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 多线程文件复制
* 经过多种方式和多次的测试:使用NIO的方式是能完整复制文件且相对耗时少的方式,实测:复制一个185M的文件,大概耗时5-6秒左右。
* 但其实使用单线程复制也不慢多少,大概慢1-2秒。
* 比较遗憾的是,信心满满的使用MappedByteBuffer来测试,但测试结果是会出错,具体原因没深究!
*
*/
public class MultiThreadCopyFile {
private ExecutorService executor;
private String srcFile;
private String desFile;
private long fileSize;
private long blockSize;
private int threadNum;
public MultiThreadCopyFile(String srcFile, String desFile, int threadNum) {
File file = new File(srcFile);
if (!file.exists()) {
throw new RuntimeException("源文件不存在");
}
if (threadNum <= 0) {
throw new RuntimeException("线程数必须大于0");
}
this.srcFile = srcFile;
this.desFile = desFile;
this.threadNum = threadNum;
this.fileSize = file.length(); // 文件总长度
this.blockSize = this.fileSize / threadNum; // 每块下载的文件大小
// 由于使用的MappedByteBuffer最大只能映射2G的大小,所以这里判断一下每块的区间是否大于2G
if (this.blockSize > Integer.MAX_VALUE - 1) {
throw new RuntimeException("当前下载线程数量偏少,建议增加下载线程数量");
} else if (blockSize == 0) { // 线程数量太多也需要检查一下
throw new RuntimeException("当前下载线程数量偏多,请减少下载线程数量");
}
executor = Executors.newFixedThreadPool(threadNum);
}
public void start() {
for (int i = 0; i < threadNum; i++) {
long startPos = i * blockSize;
long endPos;
if (i == threadNum - 1) {
endPos = fileSize;
} else {
endPos = startPos + blockSize;
}
executor.submit(new DownlodTask(startPos, endPos));
}
// 测试单线程复制文件
// new Thread(new DownlodTask(0, 0)).start();
}
class DownlodTask implements Runnable {
private long startPos;
private long endPos;
public DownlodTask(long startPos, long endPost) {
this.startPos = startPos;
this.endPos = endPost;
}
@Override
public void run() {
copyByNIO();
}
/**
* 使用NIO的FileChannel
*
*/
private void copyByNIO() {
try {
long start = System.currentTimeMillis();
RandomAccessFile rafi = new RandomAccessFile(srcFile, "r");
RandomAccessFile rafo = new RandomAccessFile(desFile, "rw");
rafi.seek(startPos);
rafo.seek(startPos);
long size = endPos - startPos;
FileChannel channelIn = rafi.getChannel();
FileChannel channelOut = rafo.getChannel();
// 锁住需要操作的区域,第三个参数为fasle代表锁住
// FileLock lock = channelOut.lock(startPos, size, false);
// 复制数据
channelIn.transferTo(startPos, size, channelOut);
// 释放锁
// lock.release();
rafi.close();
rafo.close();
System.out.println(Thread.currentThread().getName() + ":文件开始位置:" + startPos + ",文件大小:" + size + ",耗时:" + (System.currentTimeMillis() - start));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用byte数组来做中间缓存
*/
private void copyByByteArray() {
try {
long start = System.currentTimeMillis();
RandomAccessFile rafi = new RandomAccessFile(srcFile, "r");
RandomAccessFile rafo = new RandomAccessFile(desFile, "rw");
long size = endPos - startPos;
long markSize = 0; // 标记当前已读取了多少字节
rafi.seek(startPos);
rafo.seek(startPos);
int bufSize = 8192;
byte[] buff = new byte[bufSize];
int len; // 实际读取到的字节长度
int readLen; // 还能往缓存数组读取多少个字节
while (true) {
// 判断当前已读取的数量加上缓存数组长度是否大于需要读取的总大小
if (markSize + bufSize > size) {
readLen = (int) (size - markSize);
} else {
readLen = bufSize;
}
if (readLen <= 0) {
break;
}
len = rafi.read(buff, 0, readLen);
if (len == -1) {
break;
}
rafo.write(buff, 0, len);
markSize += len;
}
rafi.close();
rafo.close();
System.out.println(Thread.currentThread().getName() + ":文件大小:" + size + ",markSize=" + markSize + ",耗时:" + (System.currentTimeMillis() - start));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 单线程复制
*/
private void copy() {
try {
long start = System.currentTimeMillis();
RandomAccessFile rafi = new RandomAccessFile(srcFile, "r");
RandomAccessFile rafo = new RandomAccessFile(desFile, "rw");
byte[] buff = new byte[8192];
int len;
while ((len = rafi.read(buff)) != -1) {
rafo.write(buff, 0, len);
}
rafi.close();
rafo.close();
System.out.println("耗时:" + (System.currentTimeMillis() - start));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 这个在多线程下会直接出现以下bug,具体是什么问题和怎么解决没细究:
*
* # A fatal error has been detected by the Java Runtime Environment:
* #
* # SIGBUS (0xa) at pc=0x00007fff731d7811, pid=87173, tid=0x0000000000003703
* #
* # JRE version: Java(TM) SE Runtime Environment (8.0_191-b12) (build 1.8.0_191-b12)
* # Java VM: Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode bsd-amd64 compressed oops)
* # Problematic frame:
* # C [libsystem_platform.dylib+0x3811] _platform_memmove$VARIANT$Ivybridge+0x31
* #
* # Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
* #
* # An error report file with more information is saved as:
* # /Users/log/Documents/workspace/idea_workspace/AndroidServer/hs_err_pid87173.log
* [thread 21763 also had an error]
* #
* # If you would like to submit a bug report, please visit:
* # http://bugreport.java.com/bugreport/crash.jsp
* #
*
* Process finished with exit code 134 (interrupted by signal 6: SIGABRT)
*
*/
private void copyByMappedBuffer() {
try {
RandomAccessFile rafi = new RandomAccessFile(srcFile, "r");
RandomAccessFile rafo = new RandomAccessFile(desFile, "rw");
FileChannel channelIn = rafi.getChannel();
FileChannel channelOut = rafo.getChannel();
long size = endPos - startPos;
System.out.println(Thread.currentThread().getName() + ": startPos=" + startPos + "; endPos=" + endPos);
// 这里需注意:通过查看MappedByteBuffer的构造方法可知,它能接受的最大映射内存为Integer.MAX_VALUE=0x7fffffff,
// 也就是大概2G,所以在切割文件时需要特别处理一下!
MappedByteBuffer bufferIn = channelIn.map(FileChannel.MapMode.READ_ONLY, startPos, size);
MappedByteBuffer bufferOut = channelOut.map(FileChannel.MapMode.READ_WRITE, startPos, size);
for (int i = 0; i < size; i++) {
bufferOut.put(bufferIn);
}
bufferOut.force();
rafi.close();
rafo.close();
unmap(bufferIn);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 释放MappedByteBuffer资源,需手动自己释放MappedByteBuffer的内存
*/
private void unmap(MappedByteBuffer mappedByteBuffer) {
Cleaner cleaner = ((DirectBuffer) mappedByteBuffer).cleaner();
if (cleaner != null) {
cleaner.clean();
}
}
}
}