简述
对于提高大文件读写效率的方法网上数不胜数,我这里选择了使用NIO结合多线程处理的方式。
通过 io 的 RandomAccessFile 实现对大文件按行分片。
通过 nio 的 MappedByteBuffer 和 ByteBuffer 对缓冲区内的数据进行处理
通过nio 的 FileChannel,实现大文件的读写。
分片
实现在限制的分片大小(partitionSize)范围内,找出数据完整的切片,即行完整的切片。
大体逻辑是:
- 确认分片起始位置(start)
- 分片起始位置(start)+分片最大限制(partitionSize)得到待修正结束位置(preEnd)
- 定位修正结束位置字符,向前遍历直到等于\r或\n
- 以此作为分片结束位置(end)
- 传递分片结束位置+1为下一次迭代开始位置
代码:
/**
* 递归解决分区
* @param result 结果集
* @param filePointer 文件指针
* @param start 本次开始位置
* @param fileSize 文件大小
* @param partitionSize 分区最大大小
* @throws IOException 异常
*/
private static void partitionHandle(LinkedList<FilePartition> result,
RandomAccessFile filePointer,
long start,
long fileSize,
long partitionSize) throws IOException{
//新建分区 设置分区开始
FilePartition partition = new FilePartition();
partition.setStart(start);
//判断结束是否在文件大小范围内
long preEnd = start + partitionSize;
if(preEnd < fileSize){
//不越界获取preEnd-1位置字符,不是\r或者\n就倒车
filePointer.seek(preEnd-1);
byte b = filePointer.readByte();
while (b != 13 && b != 10){
preEnd = preEnd -1;
filePointer.seek(preEnd);
b = filePointer.readByte();
}
partition.setEnd(preEnd);
result.add(partition);
partitionHandle(result,filePointer,preEnd+1,fileSize,partitionSize);
} else {
//越界则以fileSize-1作为最后结束
partition.setEnd(fileSize-1);
result.add(partition);
}
读取
使用 RandomAccessFile 打开文件,获得其 MappedByteBuffer
RandomAccessFile source = new RandomAccessFile(sourcePath,"r");
// 打开读取内存映射
MappedByteBuffer input = source.getChannel().map(
FileChannel.MapMode.READ_ONLY,
partition.getStart(),
partition.getEnd()-partition.getStart());
RandomAccessFile 获得 FileChannel 在进而获得 MappedByteBuffer,使用只读模式,从分片开始,读取分片大小的数据进行映射。
使用 byte[] 建立缓冲区从映射中拿出需要的数据,变成可处理的字符串
byte[] block = new byte[buffer_size];
while (offset < input.capacity()) {
int block_size = input.capacity() - offset >= buffer_size ? buffer_size : input.capacity() - offset;
for(int idx=0; idx < block_size;idx ++){
block[idx] = input.get(offset+idx);
}
String data = new String(block,0,block_size);
String[] lines = data.split("\n");
... ...
offset = offset + buffer_size;
}
写入
使用追加写的 FileOutputStream 获得FileChannel,使用 ByteBuffer 写入
FileOutputStream passFOS = new FileOutputStream(goodPath,true);
FileChannel passFC = passFOS.getChannel();
ByteBuffer pass_buffer = ByteBuffer.wrap(block);
pass_buffer.put(block);
pass_buffer.flip();
passFC.write(pass_buffer);
passFOS.flush();
性能
文件大小1281651KB约1.22G
对文件内数据行包含‘男性’和包含‘女性’字样的数据进行分堆
分区大小 268435456 约256M
缓冲区大小 26843546 约25.6M
耗时 11773ms 速度约为106M/S