高端网站哪个比较好,网络安全未来发展趋势,推广普通话主题手抄报,网站开发的数据库文章目录学习链接RandomAccessFile构造方法实现的接口DataOutputDataInputAutoCloseable重要的方法多线程读写同一个文件#xff08;多线程复制文件#xff09;代码1代码2断点续传FileUtils学习链接
RandomAccessFile详解 Java IO——RandomAccessFile类详解 java多线程-断点…
文章目录学习链接RandomAccessFile构造方法实现的接口DataOutputDataInputAutoCloseable重要的方法多线程读写同一个文件多线程复制文件代码1代码2断点续传FileUtils学习链接
RandomAccessFile详解 Java IO——RandomAccessFile类详解 java多线程-断点续传
RandomAccessFile
构造方法
需要传入一个 File 和 指定模式
当指定的file不存在时会创建文件。但它不会帮我们创建目录目录不存在的话会报错。当指定的file存在时不会对原文件有影响模式可以指定为r、rw、rwd、rws
实现的接口
它实现了3个接口其中的方法如下
DataOutput
定义了写的方法每次写都是从文件指针的位置开始写每写一个字节文件指针往后移动一位可以接着从文件指针位置继续读初始文件指针filePointer为0也就意味着刚开始如果不设置初始文件指针就会从头开始覆盖文件的数据。
与FileOutputStream区别文件输出流如果需要追加需要传入第二个参数append为true否则会删除文件的所有字节这点是比较危险的。RandomAccessFile可以先把文件指针移动最后面通过seek或skipBytes然后开始写入
- void write(int b)-写入的是一个字符而不是整数写入整数要用writeInt
- void write(byte b[])
- void write(byte b[], int off, int len)
- void writeBoolean(boolean v)
- void writeByte(int v)
- void writeShort(int v)
- void writeChar(int v)
- void writeInt(int v)
- void writeLong(long v)
- void writeDouble(double v)
- void writeBytes(String s)
- void writeChars(String s)
- void writeUTF(String s)DataInput
定义了读的方法每次读都是从指针的位置开始写每读一个字节指针往后移动一位可以接着从指针位置继续读初始指针filePointer为0。
- void readFully(byte b[], int off, int len) - 将从文件指针开始最多读取len个字节内容给到byte数组的从off开始的偏移量
- void readFully(byte b[]) - 同上只不过off为0len为b.length
- int skipBytes(int n)
- boolean readBoolean()
- byte readByte()
- int readUnsignedByte()
- short readShort()
- int readUnsignedShort()
- char readChar()
- int readInt()
- long readLong()
- float readFloat()
- double readDouble()
- String readLine()
- String readUTF()AutoCloseable
- void close()重要的方法
native long length()- 获取文件的字节数native void setLength(long newLength)- 设置文件占用的字节数如果源文件不够这个字节数量用0补充如果源文件比这个子节数量要是多的话多的部分直接截掉native long getFilePointer()- 获取当前文件指针以字节为单位相当于游标void seek(long pos)绝对位置- 可以直接用来设置当前文件指针的位置会移动文件指针到指定的位置从文件最开始的位置开始算- 可以超出文件大小但是超出后只有当写入之后才会改变文件的大小int skipBytes(int n) 相对位置- 从当前位置跳过指定的字节数量会移动文件指针到跳过的位置- 如果设置的参数超过了文件末尾不会抛出异常只会返回实际跳过的字节数多线程读写同一个文件多线程复制文件
代码1
代码是每个线程负责一个分片大小10M算出需要创建的线程然后每个线程就只负责自己的10M数据。 public class RafTest {public static void main(String[] args) throws IOException, InterruptedException {File file new File(D:\\Projects\\demo-raf\\test.mp4);// 分片大小int shardSize 10 * 1024 * 1024; // 10M// 线程数量int threadNum (int) (Math.ceil((double) file.length() / shardSize));System.out.println(线程数量: threadNum);CountDownLatch latch new CountDownLatch(threadNum);ListThread threadList new ArrayList();for (int i 0; i threadNum; i) {final int j i;Thread t new Thread(() - {try {// 每个线程要用自己的RandomAccessFile它不是线程安全的// 待读取的文件RandomAccessFile rafsrc new RandomAccessFile(new File(D:\\Projects\\demo-raf\\test.mp4), r);// 待写入的文件// 刚开始这个文件是不存在的new完之后还没开始写它就创建了第一个线程可以直接写入// 第二个线程发现当前文件是存在的然后需要从指定位置开始写注意这个指定位置是可以超过文件大小的看seek的用法RandomAccessFile raftarget new RandomAccessFile(new File(D:\\Projects\\demo-raf\\test-copy.mp4), rw);// 每个线程负责读写的开始位置int pos j * shardSize;rafsrc.seek(pos);raftarget.seek(pos);// 每个线程最多负责读取长度为shardSize,即一个分片大小int totalRead 0; // 记录当前线程已读取的字节数int len 0; // 当次循环中读取的字节数// 缓冲数组byte[] bytes new byte[5 * 1024]; // 5kwhile (true) {// 最多读取到bytes.lengh的字节数量的数据到bytes中len是读取的字节数量len rafsrc.read(bytes);// 读到末尾了没数据了就退出循环if (len -1) {break;}// 确定上面的read方法能读到数据再写入raftarget.write(bytes, 0, len);totalRead len; // 当前线程已读取并且写入的字节数// 每个线程只负责一分片大小if (totalRead shardSize) { // 1. 这里大于或等于的意思是要读够一个分片大小除非遇到文件末尾了break; // 2. 读取该分片时由于缓冲数组的存在有可能会读到下一个分片的数据} // 但是没有关系下一个分片的起始位置会从指定处开始写因此会覆盖上一个线程写入的数据。// 覆不覆盖也没关系都是同样的数据嘛这点须理解下由于缓冲数组的存在多个线程可能操作了同一段数据了} // 3. 读取到最后一个分片时这个分片大小一定小于或等于预设值的分片大小由于缓存数组的存在会不会存在问题呢// 没有问题因为最后一次读取都不够缓存数组的话会返回已读取到的字节数然后写入后继续读取就会返回-1从而退出了循环// 4. 线程的执行顺序也对最终写的文件没有影响需要结合seek的用法来看因为seek本身就能指定超过文件大小的位置// 然后从这个位置开始写入,因此先写入前一段还是后一段都没影响这一点比较重要哦也能从中体会到这个类的作用。latch.countDown();} catch (IOException e) {e.printStackTrace();}});threadList.add(t);}threadList.forEach(t-t.start());latch.await();}
}代码2
上面这样写不好最好改成成固定线程数量每个线程平分不然文件一大创建的线程数量太多了。详细解释请看代码1的注释
package com.zzhua;import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;public class RafTest2 {public static void main(String[] args) throws IOException, InterruptedException {File file new File(D:\\documents\\尚硅谷JavaScript高级教程\\视频1.zip);int threadNum 5; // 指定5个线程// 计算每个分片大小final int shardSize (int)(Math.ceil((double)file.length() / threadNum));CountDownLatch latch new CountDownLatch(threadNum);ListThread threadList new ArrayList();for (int i 0; i threadNum; i) {final int j i;Thread t new Thread(() - {try {// 待读取的文件RandomAccessFile rafsrc new RandomAccessFile(new File(D:\\documents\\尚硅谷JavaScript高级教程\\视频1.zip), r);// 待写入的文件RandomAccessFile raftarget new RandomAccessFile(new File(D:\\documents\\尚硅谷JavaScript高级教程\\视频1_copy.zip), rw);// 每个线程负责读写的开始位置int pos j * shardSize;rafsrc.seek(pos);raftarget.seek(pos);// 缓冲数组byte[] bytes new byte[5 * 1024]; // 5k// 每个线程最多负责读取长度为shardSize,即一个分片大小int totalRead 0; // 记录当前线程已读取的字节数int len 0; // 当次循环中读取的字节数while (true) {len rafsrc.read(bytes);// 读到末尾了没数据了就退出循环if (len -1) {break;}raftarget.write(bytes, 0, len);totalRead len; // 当前线程已读取并且写入的字节数// 每个线程只负责一分片大小if (totalRead shardSize) {break;}}latch.countDown();} catch (IOException e) {e.printStackTrace();}});threadList.add(t);}threadList.forEach(t-t.start());latch.await();}
}
断点续传
FileUtils
以下代码的过程大致与上面相同但是添加了一个ConcurrentHashMap去记录 线程标识 当前线程完成写入的位置位置是相对于文件起始位置开始计算的当线程中的每次循环读取完成时将当次循环读取的数量 加上 当前线程开始时的偏移量这个偏移量在一切正常的情况下是k * part然后写入log日志。当某个时刻程序终止所有线程停止。这个文件就记录了所有线程完成的情况每个线程之前已经处理过的就不需要再处理了而是接着从记录的位置开始读写即可这样每个线程不用从头开始就实现了断点续传。
package com.qikux.utils;import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;public class FileUtils {/*** 支持断点续传* src 拷贝的原文件* desc 拷贝的位置* threadNum 开启的线程数*/public static void transportFile(File src, File desc, int threadNum) throws Exception {// 每一个线程读取的大小int part (int)Math.ceil(src.length() / threadNum);// 存储多个线程、用于阻塞主线程ListThread list new ArrayList();// 定义一个基于多线程 的 hashmapfinal MapInteger, Integer map new ConcurrentHashMap();// 读取 日志文件中的数据String[] $data null ;String logName desc.getCanonicalPath() .log;File fl new File(logName);if (fl.exists()) {BufferedReader reader new BufferedReader(new FileReader(fl));String data reader.readLine();// 拆分 字符串$data data.split(,);reader.close();}final String[] _data $data ;for (int i 0; i threadNum; i) {final int k i ;Thread thread new Thread(() - {// 线程具体要做的事情RandomAccessFile log null ;try {RandomAccessFile in new RandomAccessFile(src, r);RandomAccessFile out new RandomAccessFile(desc, rw);log new RandomAccessFile(logName, rw);// 从指定位置读in.seek(_data null ?k * part : Integer.parseInt(_data[k]) );out.seek(_data null ?k * part : Integer.parseInt(_data[k]) );byte[] bytes new byte[1024 * 2];int len -1, plen 0;while (true) {len in.read(bytes);if (len -1) {break;}// 如果不等于 -1 则 累加求和plen len;// 将读取的字节数放入 到 map 中map.put(k, plen (_data null ?k * part : Integer.parseInt(_data[k])) );// 将读取到的数据、进行写入out.write(bytes, 0, len);// 将 map 中的数据进行写入文件中log.seek(0); // 直接覆盖全部文件StringJoiner joiner new StringJoiner(,);map.forEach((key, val)- joiner.add(String.valueOf(val)));log.write(joiner.toString().getBytes(StandardCharsets.UTF_8));if (plen (_data null ? k * part : Integer.parseInt(_data[k])) (k1) * part ) {break;}}} catch (Exception e) {e.printStackTrace();}finally {try {if (log !null) log.close();} catch (IOException e) {e.printStackTrace();}}});thread.start();// 把这5个线程保存到集合中list.add(thread);}for(Thread t : list) {t.join(); // 将线程加入并阻塞主线程}// 读取完成后、将日志文件删除即可new File(logName).delete();}/*** 支持断点续传* src 拷贝的原文件* desc 拷贝的位置*/public static void transportFile(File src, File desc) throws Exception {transportFile(src, desc, 5);}public static void transportFile(String src, String desc) throws Exception {transportFile(new File(src), new File(desc));}
}