Java IO 文件处理类RandomAccessFile
1. RandomAccessFile介绍 RandomAccessFile支持对文件的读取和写入随机访问(其他的输入流或者输出流只能执行一种操作,要么输入,要么输出)。RandomAccessFile把随机访问的文件对象看作存储在文件系统中的一个大型byte数组,然后通过指向该 byte数组的光标或者索引(即:文件指针FilePointer)在该数组任意位置读取或者写入数据。
随机访问:可以跳转到文件的任意位置出进行读取
输入操作从文件指针开始读取字节(以字节为单位进行读取),并随着对字节的读取而前移此文件指针。如果RandomAccessFile访问文件以读取/写入模式创建,则输出操作可以使用;输出操作从文件之后指针开始写入字节,并随着对字节的写入而前移此文件指针。
在磁盘和内存中,所有的存储都是以右边为开始点(低),左边为结束点(高),字节的读取或者写入都是从右到左的顺序。
缺点:该类仅限于操作文件,不能访问其他的I/O设备,如网络、内存映像等。
2. 构造方法
RandomAccessFil(File file, String mode)
RandomAccessFil(String name, String mode)
**mode
**:表示以什么模式创建读写流,此参数右固定的输入值,必须是:r
、rw
、rws
、rwd
其中一个。
r
: 以只读方式打开文件,如果试图对该RandomAccessFile指定的文件执行写入方法则会抛出IOException
rw
:以读取、写入方式打开文件,如果该文件不存在,则尝试创建文件
rws
:以读取、写入方式打开文件,相对于rw
模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备,默认情况下(rw模式)是使用buffer的,只有cache满的或者使用RandomAccessFile.close()
关闭流的时候才真正写入到文件
rwd
:与rws类似,只是仅对文件的内容同步更新到磁盘,而不修改文件的元数据
3. 文件指针FilePointer RandomAccessFile包含三个方法来操作文件记录指针:
long getFilePointer
: 返回文件记录指针的当前位置
void seek(long pos)
:将文件记录指针定位到pos位置
skipBytes(int n)
:尝试跳过输入的n个字节,当n大于文件的字节数读取会抛出EOFException
,如果n为负数,则不跳过任何字节
其他常用方法:
FileDescriptor getFD()
:返回文件的文件描述符
native void setLength(long newLength)
: 设置文件的长度
native long length()
:返回文件的长度
close()
: RandomAccessFile对文件访问操作全部结束后,需要调用close方法来释放与其关联的所有资源
setLength:为什么需要设置文件长度?
可以理解为这是一个”动态数组”,假设你想要设置newLength长度
如果长度小于实际长度(length方法返回的值),文件被截断,并且如果getFilePointer大于newLength,那么它就变成newLength
如果长度大于实际长度(length方法返回的值),则该文件被扩展,再次情况下,未定义文件扩展部分的内容
seek方法设置的片一两,下一次的读写将从这个文件开始,偏移量的设置可能会超出文件末尾,这并不会改变什么,但是一旦在这个超出文件末尾的偏移量写入数据,长度将会改变
4. 读方法介绍
int read()
: 从此文件中读取一个数据字节
int read(byte[] b)
: 将最多b.length个数据字节从文件读入byte数组
int read(byte[]b, int off, int len)
: 将最多len个数据字节从此文件中读入byte数组
boolean readBoolean()
: 从此文件中读取一个boolean,如果是0则是false,否则为true
byte readByte()
: 从此文件中读取一个有符号的八位值
Char readChar()
: 从此文件中读取一个字符
double readDouble()
: 从此文件中读取一个double
float readFloat()
: 从此文件中读取一个Float
void readFull(byte[] b)
: 将b.length个字节从此文件读入byte数组,并从当前文件指针开始
void read(byte[]b, int off, int len)
: 将正好len个字节从此文件读入byte数组,并从当前文件指针开始
int readInt()
: 从此文件中读取一个有符号的32位整数
String readLine()
: 从此文件中读取文本的下一行
long readLong()
: 从此文件中读取一个有符号的64位数
short readShort()
: 从此文件中读取一个无符号的16位数
5. 写方法介绍
void write(byte[] b)
: 将 b.length 个字节从指定 byte 数组写入到此文件,并从当前文件指针开始。
void write(byte[] b, int off, int len)
: 将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始
void write(int b)
: 向此文件写入指定的字节
void write(boolean b)
: 按单字节值将 boolean 写入该文件。
void writeByte(int b)
: 单字节值将 byte 写入该文件。
void writeBytes(String s)
: 字节序列将该字符串写入该文件。
void writeChar(int v)
: 按双字节值将 char 写入该文件,先写高字节。
void writeDouble(double v)
: 使用 Double 类中的 doubleToLongBits 方法将双精度参数转换为一个 long,然后按八字节数量将该 long 值写入该文件,先定高字节
void writeFloat(float v)
: 用 Float 类中的 floatToIntBits 方法将浮点参数转换为一个 int,然后按四字节数量将该 float 值写入该文件,先定高字节
6. 示例代码 6.1 基本方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package org.ygb.file;import java.io.IOException;import java.io.RandomAccessFile;import java.nio.charset.StandardCharsets;import java.util.ArrayList;import java.util.List;public class RandomAccessFileDemo { public static void main (String[] args) { RandomAccessFile randomAccessFile = null ; try { randomAccessFile = new RandomAccessFile("/Users/xiaoyuge/Desktop/random_access_file.txt" , "rw" ); for (int i = 0 ; i < 10 ; i++) { randomAccessFile.write(("设备名: 测试" +i+", 设备数量:" +i+"\n\r" ).getBytes()); } randomAccessFile.seek(0 ); byte [] b = new byte [1024 ]; int len; while ((len = randomAccessFile.read(b)) != -1 ){ System.out.println(new String(b, 0 , len)); } randomAccessFile.seek(randomAccessFile.length()); randomAccessFile.write(("设备名: 测试" +10 +", 设备数量:" +10 +"\n\r" ).getBytes()); int pos = 120 ; String insertStr = "-----------------------" ; randomAccessFile.seek(pos); List<byte []> bytes = new ArrayList<>(); byte [] bs1 = new byte [1024 ]; while (randomAccessFile.read(bs1) != -1 ){ bytes.add(bs1); } randomAccessFile.seek(pos); randomAccessFile.write(insertStr.getBytes(StandardCharsets.UTF_8)); for (byte [] cacheStr : bytes) { randomAccessFile.write(cacheStr); } } catch (Exception ex) { ex.printStackTrace(); }finally { if (randomAccessFile != null ){ try { randomAccessFile.close(); } catch (IOException ioException) { ioException.printStackTrace(); } } } } }
可以查看生成的文件内容如下:
6.2 多线程下载文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package org.ygb.file;import java.io.File;import java.io.RandomAccessFile;public class DownloadThread extends Thread { private long start; private File src; private long total; private File target; public DownloadThread (long start, File src, File target, long total) { this .start = start; this .src = src; this .target = target; this .total = total; } @Override public void run () { try { RandomAccessFile src = new RandomAccessFile(this .src, "rw" ); RandomAccessFile target = new RandomAccessFile(this .target, "rw" ); src.seek(start); target.seek(start); byte [] arr = new byte [1024 ]; int len; long count = 0 ; while ((len = src.read(arr)) != -1 ) { if (len + count > this .total) { len = (int ) (total - count); target.write(arr, 0 , len); break ; } else if (len + count < total) { target.write(arr, 0 , len); count += arr.length; } else { target.write(arr, 0 , len); break ; } } src.close(); target.close(); } catch (Exception e) { e.printStackTrace(); } } public static void main (String[] args) { File src = new File("/Users/xiaoyuge/Desktop/src.txt" ); File target = new File("/Users/xiaoyuge/Desktop/target.txt" ); long len = src.length(); new DownloadThread(0 , src, target, len / 2 ).start(); new DownloadThread(len / 2 , src, target, len - (len / 2 )).start(); } }
6.3 大文件分割合并
面向对象思想封装:分割文件并合并
根据数据源src,输出文件夹destDir,分割后文件存储路径destPaths,块大小blockSize, size块数,初始化分割文件对象
构造方法(数据源,输出源);构造方法(数据源,输出源, 分割块大小)
初始化文件分割对象时,调用init()方法计算分块数,切分后所有文件名,如果保存切分后的文件目录不存在,则创建
调用split()方法根据文件总长度以及分块大小blockSize,调用splitDetails()将分割后的每一块文件输出到destDir下面
调用merge(String destPath)将destPaths每一个分割文件声明一个缓冲输入流,保存到Vector中,然后使用SequenceInputStream将Vector合并到一个输入流中,最后使用destPath,创建一个缓冲输出流,将合并输入流读取字节,全部写入输出流中
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 package org.ygb.file;import java.io.*;import java.util.ArrayList;import java.util.List;import java.util.Objects;import java.util.Vector;public class SplitFileUtils { private File src; private String destDir; private List<String> destPaths; private int blockSize; private int size; public SplitFileUtils (String src, String destDir) { this (src, destDir, 1024 ); } public SplitFileUtils (String src, String destDir, int blockSize) { this .src = new File(src); this .destDir = destDir; this .destPaths = new ArrayList<>(16 ); this .blockSize = blockSize; init(); System.out.println("初始化完成" ); } private void init () { long len = this .src.length(); this .size = (int ) Math.ceil(len * 1.0 / this .blockSize); for (int i = 0 ; i < size; i++) { this .destPaths.add(this .destDir + File.separator + i + "-" + this .src.getName()); } if (len > 0 ) { File destFile = new File(this .destDir); if (!destFile.exists()) { destFile.mkdirs(); } } } public void split () { long len = this .src.length(); int beginPos = 0 ; int actualSize = blockSize > len ? (int ) len : blockSize; for (int i = 0 ; i < size; i++) { beginPos = i * blockSize; if (i == size - 1 ) { actualSize = (int ) len; } else { actualSize = blockSize; len -= actualSize; } splitDetail(i, beginPos, actualSize); } System.out.println("子文件切分后保存至" + this .destDir); } private void splitDetail (int i, int beginPos, int actualSize) { try ( RandomAccessFile readRaf = new RandomAccessFile(this .src, "r" ); RandomAccessFile writeRaf = new RandomAccessFile(this .destPaths.get(i), "rw" ); ) { readRaf.seek(beginPos); byte [] buffer = new byte [actualSize]; int len = -1 ; while ((len = readRaf.read(buffer)) != -1 ) { if (actualSize > len) { writeRaf.write(buffer, 0 , len); actualSize -= len; } else { writeRaf.write(buffer, 0 , actualSize); break ; } } } catch (IOException e) { e.printStackTrace(); } } public void merge (String destPath) { Vector<InputStream> vis = new Vector<>(); SequenceInputStream sis = null ; BufferedOutputStream bos = null ; try { for (String path : this .destPaths) { vis.add(new BufferedInputStream(new FileInputStream(path))); } sis = new SequenceInputStream(vis.elements()); bos = new BufferedOutputStream(new FileOutputStream(destPath, true )); byte [] buffer = new byte [1024 ]; int len = -1 ; while ((len = sis.read(buffer)) != -1 ) { bos.write(buffer, 0 , len); } bos.flush(); System.out.println("子文件" + this .destDir + "合并完成" ); delFileByPath(new File(this .destDir)); System.out.println("子文件夹" + this .destDir + "删除成功" ); } catch (Exception ex) { ex.printStackTrace(); } finally { try { if (bos != null ) { bos.close(); } if (sis != null ) { sis.close(); } } catch (IOException io) { io.printStackTrace(); } } } public void delFileByPath (File src) { if (null == src || !src.exists()) { return ; } if (src.isFile()) { src.deleteOnExit(); } if (src.isDirectory()) { for (File sub : Objects.requireNonNull(src.listFiles())) { delFileByPath(sub); } src.deleteOnExit(); } } public static void main (String[] args) { String srcDir = "/Users/xiaoyuge/Desktop/src.txt" ; String destDir = "/Users/xiaoyuge/Desktop/random" ; String destPath = "/Users/xiaoyuge/Desktop/target.txt" ; SplitFileUtils splitFileUtils = new SplitFileUtils(srcDir, destDir); splitFileUtils.split(); splitFileUtils.merge(destPath); } }
当在调用splitFileUtils.merge(destPath);
方法时增加断点,可以看到子文件目录下的切分文件
7. 参考文章