synchronized void save() {
   Long lastKey = this.lastSyncKey;
   Set<Entry<Long, RedoLogValue>> entrySet =
       lastKey == null ? skipListMap.entrySet() : skipListMap.tailMap(lastKey, false).entrySet();
   if (!entrySet.isEmpty()) {
     WriteBuffer buff = WriteBufferPool.poll();
     try {
       for (Entry<Long, RedoLogValue> e : entrySet) {
         lastKey = e.getKey();
         keyType.write(buff, lastKey);
         valueType.write(buff, e.getValue());
       }
       int chunkLength = buff.position();
       if (chunkLength > 0) {
         buff.limit(chunkLength);
         buff.position(0);
         fileStorage.writeFully(pos, buff.getBuffer());
         pos += chunkLength;
         fileStorage.sync();
       }
       this.lastSyncKey = lastKey;
     } finally {
       WriteBufferPool.offer(buff);
     }
   }
 }
 RedoLogChunk(int id, Map<String, String> config) {
   this.id = id;
   // 不使用ObjectDataType,因为ObjectDataType需要自动侦测,会有一些开销
   this.keyType = new RedoLogKeyType();
   this.valueType = new RedoLogValueType();
   skipListMap = new ConcurrentSkipListMap<>(new KeyComparator<Long>(keyType));
   String chunkFileName = getChunkFileName(config, id);
   fileStorage = new FileStorage();
   fileStorage.open(chunkFileName, config);
   pos = fileStorage.size();
   if (pos > 0) read();
 }
 private void read() {
   ByteBuffer buffer = fileStorage.readFully(0, (int) pos);
   while (buffer.remaining() > 0) {
     Long k = (Long) keyType.read(buffer);
     RedoLogValue v = (RedoLogValue) valueType.read(buffer);
     skipListMap.put(k, v);
     lastSyncKey = k;
   }
 }
 @Override
 public String toString() {
   return "RedoLogChunk[" + id + ", " + fileStorage.getFileName() + "]";
 }
 void close() {
   save();
   fileStorage.close();
 }