マルチスレッドセグメントコピーを実現するためのRandomAccessFileの使用について

序文

最近、RandomAccessFileとマルチスレッドを使用してファイルセグメントのコピーとダイレクトストリーミングコピーを実現するというアイデアが突然思い浮かびましたが、時間はかかりませんか?当初考えられていたマルチスレッドコピーと同時に、時間のかかるものは短くする必要があります。

しかし実際には、そうではありません。ほとんどの場合、開いているスレッドの数に関係なく、速度は遅くなります。理由は次のとおりです。

1.RandomAccessFileはバッファフローよりも効率的です

2.開いているスレッドが多いほど、スレッドスケジューリングの切り替えのコストが高くなります

3.マルチスレッドセグメントコピーにより、基盤となるディスクのヘッドが頻繁に変更され、ディスクのシーク時間とポジショニング時間が増加します。つまり、マルチスレッドコピーとダイレクトストリーミングコピーの場合、ディスクデータは最初にメモリに読み込まれ、次にディスクコピーに書き込まれます。しかし、マルチスレッドコピーが遅い主な理由は、基盤となるディスクIOの速度がCPUの速度を制限するためです[主に

したがって、RandomAccessFile使用してマルチスレッドのセグメント化されたコピーを実装することは無意味です。しかし、論理的なデザイン思考は依然として意味があるため、コードはまだ記録されています。コード実装プロセスでは、マルチスレッドの同時実行性の問題も関係しています。

PS:私はオペレーティングシステムを本当によく学びました、しかし先生はよく話します。

コード

package net.ysq.nio.test;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 复制者。负责调度多线程复制src到dest
 *
 * @author	passerbyYSQ
 * @date	2020-11-5 16:11:40
 */
public class MultiThreadCopyer {

	private File src;
	private File dest;
	private String newName;

	private long totalSize;
	// 各个线程已经复制的大小
	private long[] copied;
	private int lastProgress;

	// 专门负责从src到dest复制工作的线程池
	// 并不是为每一个MultiThreadCopyer实例对象创建一个线程池
	// 而是所有MultiThreadCopyer实例对象都是用这一线程池,故声明为静态变量
	private static ThreadPoolExecutor threadPool;
	// 线程数量
	private int threadCount;
	private CountDownLatch countDownLatch;

	private CopyingListener listener;

	static {
		threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(32);
	}

	public MultiThreadCopyer(File src, File dest, CopyingListener listener) {
		this(src, dest, null, 14, listener);
	}

	public MultiThreadCopyer(File src, File dest, String newName, int threadCount,
							 CopyingListener listener) {
		if (src.isDirectory()) {
			throw new RuntimeException("不支持复制目录");
		}
		if (!dest.exists()) {
			boolean isCreated = dest.mkdirs();
			if (!isCreated) {
				throw new RuntimeException("创建目标目录失败");
			}
		}
		this.newName = (newName == null || "".equals(newName)) ? src.getName() : newName;
		File tempDest = new File(dest, this.newName);
		if (tempDest.exists()) {
//			boolean isDel = tempDest.delete(); // 目标目录存在同名文件,则先删除
//			if (!isDel) {
//				throw new RuntimeException("删除目标目录同名文件失败");
//			}
		}
		this.src = src;
		this.dest = tempDest;
		this.totalSize = this.src.length();
		this.threadCount = (threadCount <= 0) ? 4 : threadCount;
		this.countDownLatch = new CountDownLatch(this.threadCount);
		this.copied = new long[ this.threadCount ];
		this.listener = listener;
	}

	public void copy() {
		long startTime = System.currentTimeMillis();

		long sectionSize = (long) (totalSize / threadCount); // 取下整
//		System.out.println("totalSize=" + totalSize);
//		System.out.println("sectionSize=" + sectionSize);
		long start = 0, end = sectionSize;
		for(int i = 0; i < threadCount; i++) {
			CopyWorker worker = new CopyWorker(start, end, i);
			threadPool.execute(worker); // 交由线程池调度
//			System.out.println(i + ":start=" + start + "; end=" + end);
			start = end;
			end = (i < threadCount - 1) ? (start + sectionSize) : totalSize;
		}

		try {
			// 阻塞等待, 直至所有线程执行完成。CountDownLatch里面有线程同步
			countDownLatch.await();

			long endTime = System.currentTimeMillis();
			if (listener != null) {
				listener.onCompleted(dest, endTime - startTime);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	// 计算进度
	private void calculateProgress() {
		if (listener != null) {
			long totalCopied = 0;
			for(int i = 0; i < threadCount; i++) {
				totalCopied += copied[i];
			}
			int progress = (int) (100.0 * totalCopied / totalSize);
			if (progress > lastProgress) { // 排队前先过滤。增加效率
				synchronized (this) { // 防止多个线程同时,刷新同一个百分点,造成百分点错乱
					if (progress > lastProgress) { // 抢到锁之后,再次验证
						listener.onProgress(progress); // 回调进度
						lastProgress = progress; // 记录上一个百分点
					}
				}
			}
		}
	}

	// 复制的线程
	class CopyWorker extends Thread {

		private RandomAccessFile randSrc;
		private RandomAccessFile randDest;

		// 负责的区间[start, end)
		private long start;
		private long end;

		private int num; // 线程编号
		private String threadName;

		public CopyWorker(long start, long end, int num) {
			super(src.getName() + "=>" + newName + ": thread-" + num);
			this.start = start;
			this.end = end;
			this.num = num;
			this.threadName = "thread-" + num;
		}

		@Override
		public void run() {
			try {
				randSrc = new RandomAccessFile(src, "r");
				randDest = new RandomAccessFile(dest, "rw");

//				long startTime = System.currentTimeMillis();
				randSrc.seek(start);
				randDest.seek(start);
//				long endTime = System.currentTimeMillis();
//				System.out.println(num + " seek耗时:" + (endTime-startTime));

				byte[] buf = new byte[1024]; // 缓冲的字节数组
				long sum = 0; // 该区间内已复制的长度
				int remain;
				while (true) {
					remain = (int) Math.min((end - start) - sum, buf.length);

					int read = randSrc.read(buf, 0, remain);
					randDest.write(buf, 0, read);
					sum += read;
					copied[num] = sum;

					// 计算并回调进度
					calculateProgress();

					if (sum >= (end - start)) {
						// 该区间复制完成
						System.out.println(threadName + " 已完成区间复制");
						countDownLatch.countDown();
						break; // 不要忘了
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	// 监听器
	interface CopyingListener {
		void onProgress(int progress); // 回调复制进度,百分数

		void onCompleted(File dest, long millisecond); // 下载完成,回调 目标文件和耗时(毫秒)
	}

}

テスト

package net.ysq.nio.test;

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

import org.junit.Test;

/**
 * @author	passerbyYSQ
 * @date	2020-11-5 18:05:49
 */
public class MultiThreadUtil {
	File src = new File("E:\\Download\\cn_sql_server_2016_enterprise_x64_dvd_8699450.iso");
	File dest = new File("E:\\");

	@Test
	public void test() {

		new MultiThreadCopyer(src, dest, new MultiThreadCopyer.CopyingListener() {

			@Override
			public void onProgress(int progress) {
				System.out.println("下载进度:" + progress + "%");
			}

			@Override
			public void onCompleted(File dest, long millisecond) {
				System.out.println("下载完成:");
				System.out.println("目标文件:" + dest.getAbsolutePath());
				System.out.println("耗时:" + millisecond + " ms");
			}

		}).copy(); // 不要忘了 .copy()
	}

	@Test
	public void test2() throws IOException {
		long startTime = System.currentTimeMillis();
		dest = new File("E:\\abc.iso");
		StreamUtil.copy(src, dest);
		long endTime = System.currentTimeMillis();
		System.out.println("耗时:" + (endTime - startTime));
	}


}

 

おすすめ

転載: blog.csdn.net/qq_43290318/article/details/109556478