likes
comments
collection
share

Java对象序列化文件追加并读取方案

作者站长头像
站长
· 阅读数 42

近几天打算使用Delayed接口自定义实现一个简单的延迟队列功能,想把任务数据进行一个持久化的实现,但是又不想依赖数据库,又或者其他的第三方工具进行持久化的操作,就考虑到直接持久化到文件中。

而持久化到文件中又考虑到两种方案,一种是使用JSON追加保存,另一种是将对象进行序列化保存。最后,考虑到性能问题选择了序列化的方式。

在处理的过程中发现对象序列化不能像普通文件一样直接进行追加对象,每次写入对象都会被覆盖。

如果想把数据对象进行追加的话,最简单粗暴的方法就是。在追加序列化对象之前,先将对象读取出来,然后封装到一个list集合中,将新的对象添加在list集合里面,将整个集合进行一个序列化的保存。

虽然以上的方案确实可以实现序列化的追加被覆盖的问题,每次在储存对象的时候,都需要将整个对象集合读取出来再写入进去,数据量少的情况下还没啥影响,但是数据量大的时候太消耗性能了,那还不如直接转换成JSON格式储存好了。

最终,根据各种途径的资料查询发现。Java默认的对象序列化是每次都会写入一个头部aced 0005(占4个字节),然后每次读取都是读完头部然后再读取内容。

解决方案就是就储存文件数据之前,先创建一个空的序列化文件,将头部标记在文件中。在写入对象的时候,将对象中的4个头部字节aced 0005截取掉,就可以将对象写入到文件中,并实现了追加的功能。

实现Serializable接口对象


@Data
@AllArgsConstructor
public class Student implements Serializable {

    private String name;
    
    private Integer age;
    
    private String sex;
    
}

初始化序列化空文件


/**
 * 初始化一次即可,为了能够提前存在4个字节的序列化头部信息
 *
 * @param file
 */
private static void spannedFile(File file) {
    if (file.exists()) {
        return;
    }
    try (FileOutputStream fos = new FileOutputStream(file);
         ObjectOutputStream oos = new ObjectOutputStream(fos)) {
    } catch (IOException e) {
        e.printStackTrace();
    }
}

写入追加序列化对象文件


private static void write(File file, Student student) {
    try (FileOutputStream fos = new FileOutputStream(file, true);
         ObjectOutputStream oos = new ObjectOutputStream(fos)) {
        fos.getChannel().truncate(fos.getChannel().position() - 4);
        oos.writeObject(student);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

读取序列化文件对象


private static List<Student> read(File file) {
    List<Student> list = new ArrayList<>();
    try (FileInputStream fis = new FileInputStream(file);
         ObjectInputStream ois = new ObjectInputStream(fis)) {
        while (fis.available() > 0) {
            try {
                Object o = ois.readObject();
                if (o instanceof Student) {
                    list.add((Student) o);
                }
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return list;
}


实现调用


public static void main(String[] args) {

    File file = new File("C:\\Users\\admin\\Desktop\\xxx.txt");

    spannedFile(file);

    write(file, new Student("张三", 18, "男"));
    
    write(file, new Student("李四", 16, "女"));

    read(file).forEach(System.out::println);

}

至此,以上就可以实现序列化文件的对象追加和读取的问题。也通过序列化追加的方式,实现了延迟队列的持久化支持。自定义了一个starter组件,开箱即用,无须任何第三方插件加入,并且支持开启多线程方式,只需要继承AbstractConsumer抽象类,重写里面的execute方法,通过put方法进行推送。

项目地址:github地址

引入依赖

将代码从git拉取下来,install安装到本地maven仓库。


<dependency>
    <groupId>io.github.delayed</groupId>
    <artifactId>delayed-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

yml配置


delay:
  cache:
    open: true
    store: C:\Users\admin\Desktop\cache
  thread:
    open: false

重写AbstractConsumer抽象类


@Slf4j
@Component
public class StudentConsumer extends AbstractConsumer<JSONObject> {

    @Override
    protected void execute(DelayedImpl<JSONObject> delayed) {
        log.info("学生标识 {}, 消费内容 {}", delayed.getId(), delayed.getData().toString());
    }

}

推送任务


@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private StudentConsumer studentConsumer;


    @PostMapping("/add")
    public JSONObject add(@RequestBody JSONObject object) {
        String studentId = studentConsumer.put(object.getLong("active"), object);
        log.info("学生任务 {} 添加成功", studentId);
        return object;
    }


}

实现

指定延迟的时间,延迟的误差在纳秒级别。因为支持持久化,重启之后也会恢复

Java对象序列化文件追加并读取方案

转载自:https://juejin.cn/post/7248827630866563130
评论
请登录