天天看點

Java對象序列化檔案追加并讀取方案

作者:JAVA互聯搬磚勞工

近幾天打算使用Delayed接口自定義實作一個簡單的延遲隊列功能,想把任務資料進行一個持久化的實作,但是又不想依賴資料庫,又或者其他的第三方工具進行持久化的操作,就考慮到直接持久化到檔案中。

而持久化到檔案中又考慮到兩種方案,一種是使用JSON追加儲存,另一種是将對象進行序列化儲存。最後,考慮到性能問題選擇了序列化的方式。

在處理的過程中發現對象序列化不能像普通檔案一樣直接進行追加對象,每次寫入對象都會被覆寫。

如果想把資料對象進行追加的話,最簡單粗暴的方法就是。在追加序列化對象之前,先将對象讀取出來,然後封裝到一個list集合中,将新的對象添加在list集合裡面,将整個集合進行一個序列化的儲存。

雖然以上的方案确實可以實作序列化的追加被覆寫的問題,每次在儲存對象的時候,都需要将整個對象集合讀取出來再寫入進去,資料量少的情況下還沒啥影響,但是資料量大的時候太消耗性能了,那還不如直接轉換成JSON格式儲存好了。

最終,根據各種途徑的資料查詢發現。Java預設的對象序列化是每次都會寫入一個頭部aced 0005(占4個位元組),然後每次讀取都是讀完頭部然後再讀取内容。

解決方案就是就儲存檔案資料之前,先建立一個空的序列化檔案,将頭部标記在檔案中。在寫入對象的時候,将對象中的4個頭部位元組aced 0005截取掉,就可以将對象寫入到檔案中,并實作了追加的功能。

實作Serializable接口對象

java複制代碼
@Data
@AllArgsConstructor
public class Student implements Serializable {

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

           

初始化序列化空檔案

java複制代碼
/**
 * 初始化一次即可,為了能夠提前存在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();
    }
}

           

寫入追加序列化對象檔案

java複制代碼
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();
    }
}

           

讀取序列化檔案對象

java複制代碼
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;
}


           

實作調用

java複制代碼
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倉庫。

xml複制代碼
<dependency>
    <groupId>io.github.delayed</groupId>
    <artifactId>delayed-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

           

yml配置

yml複制代碼
delay:
  cache:
    open: true
    store: C:\Users\admin\Desktop\cache
  thread:
    open: false

           

重寫AbstractConsumer抽象類

java複制代碼
@Slf4j
@Component
public class StudentConsumer extends AbstractConsumer<JSONObject> {

    @Override
    protected void execute(DelayedImpl<JSONObject> delayed) {
        log.info("學生辨別 {}, 消費内容 {}", delayed.getId(), delayed.getData().toString());
    }

}

           

推送任務

java複制代碼
@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對象序列化檔案追加并讀取方案

作者:苦瓜不苦077

連結:https://juejin.cn/post/7248827630866563130

繼續閱讀