自学内容网 自学内容网

java文件按行写入数据后并创建行索引及查询

背景

        当有很多数据需要存储,这些数据只是想要简单的按行存储和查询,不需要进行其他条件搜索,此时就可以考虑不需把这些数据存储在数据库,而是直接写入文件,然后从文件中查询

        但是正常情况下,如果仅仅只是按行写入文件,读取的时候如果不是从第一行开始读,java api是不支持直接定位到某行开始查询,比如如果想查出第1000行之后的数据,此时需要先读取前面999行的数据之后才能读取第1000行的数据,可想而知性能好不到哪去。

        有些人可能会说用 RandomAccessFile 可以直接跳过一定偏移量定位到999行,可是正常情况下,我们并不能知道999行对应的偏移量,也就无法知道要跳过多少偏移量直接定位到999行。

        本文主要是提供了解决上面问题的工具。

实现功能

1.可以按行写入数据,并对写入的每一行创建索引

2.基于索引快速定位到行所在位置,不需要一行行遍历
3.支持对写入数据进行分片。比如写入1W条数据,1000条数据一个文件

4.支持分页查询

5.支持指定行号查询

6.目前仅支持一次性写入全部数据,不支持写一半后继续往后添加(后续考虑支持)

7.不支持从中间插入数据(后续不会支持,因为目前的实现机制从中间插入,插入行后面的所有行都得重新构造索引)

 代码地址

g5zhu5896/file-storage · GitHub

介绍

核心类

FileDataWriter

用于往文件中写入数据并创建索引

使用:

String path= "/file/张三/";
Integer totalSize = 10000;
Integer fileMaxSize=1000;
String charset=CharsetUtil.UTF_8;
Long indexFixedWidthFactor = 3l;
//如果文件已存在写入会报错
if (!new File(path).exists()) {
    try (FileDataWriter fileDataWriter = FileDataWriter.builder().withFileMaxSize(fileMaxSize)
.withIndexFixedWidthFactor(indexFixedWidthFactor )
.withCharset(charset).build(filePath)) {
for (Long i = 1L; i <= totalSize; i++) {
fileDataWriter.write(i + "");
}
} catch (IOException e) {
e.printStackTrace();
}
}

相关配置

fileMaxSize:配置一个文件最大存储的行数
path:文件要存储的路径。可以自定义规则
indexFixedWidthFactor: 索引固定宽度因子,用于计算索引文件中索引的固定宽度。配的越大越浪费空间,配小了更容易触发调整固定宽度
charset:配置写文件的编码

FileDataPageReader

 使用:

String path= "/file/张三/";
//如果文件不存在则无法读取
if (!new File(path).exists()) {
    FileDataPageReader fileDataPageReader = FileDataPageReader.builder().build(path);
    //分页查询
    //从第二页开始查询
    Integer page = 2;
    //一页查20条
    Integer pageSize=20
    List<String> strings = fileDataPageReader.readPage(page, pageSize);
    //将行内容转成Interger,此处也可以把json转成对象
    List<Integer> strings = fileDataPageReader.readPage(page, pageSize,item->{
        return new Integer(item);
    });

    //指定行数查询
    //从第5行开始查
    Integer startIndex = 5;
    //共查100条
    Integer size = 100;
    List<String> strings = fileDataPageReader.read(startIndex, size);
    //将行内容转成Interger,此处也可以把json转成对象
    List<Integer> strings = fileDataPageReader.read(startIndex, size,item->{
        return new Integer(item);
    });

    //获取总行数
    Integer totalSize=fileDataPageReader.getTotalSize();
    
}

文件介绍

(目前文件后缀只能写死,可以通过改 Constants 类调整生成后缀)

.dat文件

数据文件,按行写入。会有多个,文件如下图所示

.idx

行索引文件,会有多个,存储每一行对应的文件偏移量,通过该文件快速定位数据文件的行偏移量,如下图所示

cfg

配置文件,记录当前文件的一些配置信息,如下图所示

相关配置

fileMaxSize:配置每一个文件最大存储的行数
totalSize:总行数
charset:配置写文件的编码
columnIndexWidthMap:行索引动态固定宽度map,主要用于减少索引文件的大小。有序,{1:4,1852:8}表示1-1851行的间距是4,1852及之后的行的间距是8

相关依赖

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.19</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.26</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>

注意

        本文代码仅基于 Test.main 中的 demo 进行测试验证过,所以依然可能存在 bug ,但个人感觉测试的 demo 已经挺多的了.

核心逻辑 

定位数据行

主要通过 RandomAccessFile.seek 快速定位到行偏移量,然后通过索引文件获取具体的行偏移量。这没什么好说的,

从索引文件中获取数据行偏移量

        索引文件需要存储数据行中的偏移量,但是索引文件中包含很多行,我们想知道数据行的偏移量在索引文件中的哪个位置,也无法直接定位。

        所以为了快速从索引文件中定位到数据行的偏移量,采用了索引固定宽度的做法。具体如下

        假设 indexFixedWidthFactor=3

        step1: 写入第1行数据,假设占用行偏移量9,宽度为1,则当前索引固定宽度为curFixedWidth=1+indexFixedWidthFactor=4,写入索引文件第1行的偏移量为0000(记录的是写入前的偏移量,那才是行开始位置)代码在 FileDataWriter.write)

        step2: 写入第2行数据,假设占用10个行偏移量,加上第1行的偏移量后为19,写入第2行偏移量0009,以此类推,第3行为0019

        step3: 直到写入103行,假设写入后的行偏移量的行偏移量为10009,由于10009宽度为5,大于 curFixedWidth ,于是重新计算当前索引固定宽度为 curFixedWidth=5+indexFixedWidthFactor=8 ,所以第104行的行偏移量是00010009。以此类推

        step4:假设150行宽度依然小于8,然后写到151行,当前文件写满,需要写下一个文件,则会从头如 step1 一样计算索引固定宽度curFixedWidth=1+indexFixedWidthFactor=4。

        step5:假设只写到179行,最后的 curFixedWidth=4 ,此时会将配置信息写入 cfg 文件,其中包括 columnIndexWidthMap={1:4, 104:8, 151:4} (代码在 FileDataWriter.close)

        step6:,由于是每一行的行偏移量是固定宽度,读取的时候就可以基于 columnIndexWidthMap 和要读取的行号,计算出行数据文件的行偏移量在索引文件中的偏移量,直接读取出数据文件的行偏移量(代码在FileDataPageReader.computeSeek)(如假设要从120行开始读取,则行偏移量为4*(104-1)+8*(120-104),从此处往后读取8位及120行的数据文件行偏移量


原文地址:https://blog.csdn.net/g5zhu5896/article/details/145310317

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!