/**
* @param :
* @return void
* @author Ladidol
* @description 文件的续写.
* @date 2022/6/30 14:35
*/
@Test
public void fileContinue() {
File preFile = new File("D:\\randomaccessfiletest\\pre.txt");
File endFile = new File("D:\\randomaccessfiletest\\end.txt");
try (
RandomAccessFile r = new RandomAccessFile(endFile, "r");
RandomAccessFile w = new RandomAccessFile(preFile, "rw");
) {
w.seek(w.length()); // 将指针指向文件最后,进行追加
byte[] bytes = new byte[1024];
int len = -1;
while ((len = r.read(bytes)) != -1) {
w.write(bytes);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @param :
* @return void
* @author Ladidol
* @description 单线程文件读写. 耗时长, 且主线程堵塞.
* @date 2022/6/29 21:03
*/
@Test
public void fileReadAndWrite() throws IOException {
//开始传输时间
long start = System.currentTimeMillis();
File preFile = new File("D:\\迅雷云盘\\[公众号:分派电影]V字仇杀队.V.for.Vendetta.2005.BD1080P.中英双字.mp4");//用一个很大的文件, 比如电影来传.
String readFile = preFile.getAbsolutePath();
String writeFile = preFile.getParent() + File.separator + "单线程传输-" + start + ".mp4";//File.separator相当于路径分隔符
byte[] bytes = new byte[2048];
try (RandomAccessFile r = new RandomAccessFile(new File(readFile), "r");
RandomAccessFile w = new RandomAccessFile(new File(writeFile), "rw");) {
int len = -1;
while ((len = r.read(bytes)) != -1) {
w.write(bytes);
}
}
long end = System.currentTimeMillis();
System.out.println("(end - start) = " + (end - start));
/*传输测试时间结果(end - start) = 9520*/
}
/**
* @param :
* @return void
* @author Ladidol
* @description 多线程分片对同一个文件进行读和写
* @date 2022/6/29 21:14
*/
@Test
public void multiThreadReadAndWrite() throws ExecutionException, InterruptedException {
//开始传输时间
long start = System.currentTimeMillis();
File preFile = new File("D:\\迅雷云盘\\[公众号:分派电影]V字仇杀队.V.for.Vendetta.2005.BD1080P.中英双字.mp4");
String readFile = preFile.getAbsolutePath();//文件路径
String writeFile = preFile.getParent() + File.separator + "多线程传输-" + start + ".mp4";//新文件名字
long length = new File(readFile).length(); // 文件一共大小
int slice = (int) length / 8; // 将文件分成8各部分来传.
System.out.println("sliceNum = " + slice);
List<long[]> longs = new ArrayList<>(8);
for (int i = 0; i < 8; i++) {
longs.add(new long[]{i * slice});
}
//开启线程.
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<?>> futures = new ArrayList<>();
for (long[] aLong : longs) {
long l = aLong[0]; // 起始位置
// 创建线程并运行
Future<?> randomAccessFile =
// ExecutorUtils.createFuture(
executorService.submit(
() -> {
byte[] bytes = new byte[slice];
try (
// 在线程内部创RandomAccessFile对象
RandomAccessFile r = new RandomAccessFile(new File(readFile), "r");
RandomAccessFile w = new RandomAccessFile(new File(writeFile), "rw");) {
r.seek(l);
int len = r.read(bytes);
if (len < slice) {
// 调整数组, 避免出现问题.
bytes = getActualBytes(bytes,len);
}
// 写入文件
w.seek(l);
w.write(bytes);
System.out.println("当前线程读写起点: " + l);
} catch (IOException e) {
e.printStackTrace();
}
//callable的返回值
return "call return " + Thread.currentThread().getName();
});
futures.add(randomAccessFile);
}
int count = 0;
for (Future<?> future : futures) {
count++;
if (!future.isDone()) {
System.out.println("资源还没有准备好" + count);
}
if (future.isDone()) {
System.out.println("资源准备好了捏!" + count);
}
System.out.println(future.get());
}
// 阻塞全部线程执行完毕
executorService.shutdown();
long end = System.currentTimeMillis();
System.out.println("(end - start) = " + (end - start));
/*传输测试时间结果(end - start) = 2377*/
}
/**
* @param bytes:
* @param len:
* @return byte
* @author Ladidol
* @description 截取真实长度的数组.
* @date 2022/6/30 14:07
*/
byte[] getActualBytes(byte[] bytes, int len) {
byte[] b1 = new byte[len];
System.arraycopy(bytes, 0, b1, 0, len);
return b1;
}
常见的使用多线程的场景: 断点续传和断点下载,或者文件加密解密等
断点续传原理就是:
- 前端将文件安装百分比进行计算,每次上传文件的百分之一(文件分片),给文件分片做上序号
- 后端将前端每次上传的文件,放入到缓存目录
- 等待前端将全部的文件内容都上传完毕后,发送一个合并请求
- 后端使用RandomAccessFile进多线程读取所有的分片文件,一个线程一个分片
- 后端每个线程按照序号将分片的文件写入到目标文件中,
- 在上传文件的过程中发生断网了或者手动暂停了,下次上传的时候发送续传请求,让后端删除最后一个分片
- 前端重新发送上次的文件分片