介绍
RandomAccessFile是java语言中最丰富的文件访问类。RandomAccessFile类支持随机访问方式,可以跳转到文件的任意位置读写数据,这个类在文件随机读取时有很大的优势,可利用多线程完成对一个大文件的读写,利用seek对文件进行切分,从大文件的不同位置开线程进行读写。
构造,模式,方法
RandomAccessFile他的构造函数只有两种,所以仅限于操作文件,不能访问其他io设备,比如网络,内存映像等。
而底层是使用c/c++进行操作的,所以无法进行自定义操作
如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建 如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
模 式 | 作 用 |
---|---|
r | 表示以只读方式打开,调用结果对象的任何write方法都将导致抛出IOException |
rw | 打开以便读取和写入,如果该文件尚不存在,则尝试创建该文件 |
rws | 打开以便读取和写入,相对于"rw",还要求对文件内容或元数据的每个更新都同步写入到底层存储设备 |
rwd | 打开以便读取和写入,相对于"rw",还要求对文件内容的每个更新都同步写入到底层存储设备 |
方 法 | 作 用 |
---|---|
void close() |
重要,关闭此随机访问文件流并释放与该流关联的所有系统资源 |
FileChannel getChannel() | 返回与此文件关联的唯一FileChannel对象,NIO用到 |
long getFilePointer() |
返回此文件中的当前偏移量(实时) |
long length() |
返回此文件的长度 |
int read() | 从此文件中读取一个数据字节 |
int read(byte[] b) |
将最多b.length个数据字节从此文件读入byte数组,返回读入的总字节数,如果由于已经达到文件末尾而不再有数据,则返回-1。 |
int read(byte[] b, int off, int len) | 将最多len个数据字节从此文件的指定初始偏移量off读入byte数组 |
boolean readBoolean() | 从此文件读取一个boolean,其余readByte()、readChar()、readDouble()等类似 |
String readLine() |
从此文件读取文本的下一行 (会乱码) |
void seek(long pos) |
重要,设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作(字节单位) |
int skipBytes(int n) | 重要,尝试跳过输入的n个字节以丢弃跳过的字节,返回跳过的字节数 |
void write(byte[] b) |
将b.length个字节从指定byte数组写入到此文件中 |
void write(byte[] b, int off, int len) | 将len个字节从指定byte数组写入到此文件,并从偏移量off处开始 |
void write(int b) | 向此文件写入指定的字节 |
void writeBoolean(boolean v) | 按单字节值将boolean写入该文件,其余writeByte(int v)、writeBytes(String s)、writeChar(int v)等都类似 |
问题解决
文件覆盖改追加
在RandomAccessFile作为输入流时候,第一次写的时候会将文件从头覆盖,后面在写就是追加(同一个对象) ,如果想在第一次就追加的话那么就设置写的位置w.seek(w.length());
这样就会从文件的最后一个字节之后开始写了
try ( RandomAccessFile r = new RandomAccessFile(new File(readFile), "r"); RandomAccessFile w = new RandomAccessFile(new File(writeFile), "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(); }
案例文件读写
CodeStartAndStopTimeUtil.creator( () -> { File file = ResourceFileUtil.getFile("mp4/mp4_2.zip"); String readFile = file.getAbsolutePath(); String writeFile = file.getParent() + File.separator + "mp4_11112.zip"; 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); } } }); }
案例多线程文件读写
CodeStartAndStopTimeUtil.creator( () -> { File file = ResourceFileUtil.getFile("mp4/mp4.zip"); String readFile = file.getAbsolutePath(); String writeFile = file.getParent() + File.separator + "mp4_11112.zip"; long length = new File(readFile).length(); // 文件一共大小 int num = (int)length/8; // 每个线程读写多少字节 20971520(20mb) 2.5个g4~5秒左右 Listlongs = DataGroup.dataGroupOnce(length, num); List > futures = new ArrayList<>(); for (long[] aLong : longs) { long l = aLong[0]; // 起始位置 // 创建线程并运行 Future> randomAccessFile = ExecutorUtils.createFuture( "RandomAccessFile", () -> { byte[] bytes = new byte[num]; 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 < num) { // 调整数组 bytes = ArrayByteUtil.getActualBytes(bytes); } // 写入文件 w.seek(l); w.write(bytes); System.out.println(l ); } catch (IOException e) { e.printStackTrace(); } }); futures.add(randomAccessFile); } // 阻塞全部线程执行完毕 ExecutorUtils.waitComplete(futures); });
问题
多线程使用RandomAccessFile
常见的使用多线程的场景: 断点续传和断点下载,或者文件加密解密等
断点续传原理就是:
- 前端将文件安装百分比进行计算,每次上传文件的百分之一(文件分片),给文件分片做上序号
- 后端将前端每次上传的文件,放入到缓存目录
- 等待前端将全部的文件内容都上传完毕后,发送一个合并请求
- 后端使用RandomAccessFile进多线程读取所有的分片文件,一个线程一个分片
- 后端每个线程按照序号将分片的文件写入到目标文件中,
- 在上传文件的过程中发生断网了或者手动暂停了,下次上传的时候发送续传请求,让后端删除最后一个分片
- 前端重新发送上次的文件分片
解决readLine乱码的问题
RandomAccessFile 读写文件时,不管文件中保存的数据编码格式是什么 使用 RandomAccessFile对象方法的 readLine() 都会将编码格式转换成 ISO-8859-1 所以 输出显示是还要在进行一次转码
//需要重新转码才能正常显示 System.out.println( new String(r.readLine().getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8) );
多线程读写乱码的问题
RandomAccessFile对象是不能线程共享的,一个RandomAccessFile对象的文件指针只有一个,多个线程如果都去操作这个指针进行seek
的话,那么读写就会被来回的跳转,导致多个线程的读写混乱,所以每个线程必须在线程内部创建RandomAccessFile,这样每个线程都有一个自己的文件指针了, 正确案例
到此这篇关于Java-Io-RandomAccessFile(任意位置读写数据)的文章就介绍到这了,更多相关Java-Io-RandomAccessFile内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!