/**
*
*/
package com.github.seanlinwang.fkv;
import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Fixed length key-value store implement.
*
* @author sean.wang
* @since Nov 16, 2011
*/
public class FkvImpl implements Fkv {
//一条记录的格式是:标记位|key|value|分隔符
private int keyLength;
private int valueLength;
private int recordLength;
private int maxRecordSize;
//字节数组和store的实现类FkvFileStore是息息相关的.因为最终是将buffer的数据写到文件中
private FkvStore store;
private byte[] writeBuffer;
private Map<String, Record> activeCache;
private Deque<Record> deletedCache;
private final Lock writeLock = new ReentrantLock();
private int endIndex = 0;
private static final byte STATUS_DELETE = '0';
private static final byte STATUS_ACTIVE = '1';
private static final byte ENDER = '\n';
private static final int STATUS_LENGTH = 1;
private static final int ENDER_LENGTH = 1;
public FkvImpl(File dbFile, int fixedKeyLength, int fixedValueLength) throws IOException {
this(dbFile, 0, fixedKeyLength, fixedValueLength);
}
public FkvImpl(File dbFile, int maxRecordSize, int keyLength, int valueLength) throws IOException {
this.keyLength = keyLength;
this.valueLength = valueLength;
this.recordLength = STATUS_LENGTH + keyLength + valueLength + ENDER_LENGTH;
this.writeBuffer = new byte[recordLength];
this.maxRecordSize = maxRecordSize;
// init store
this.store = new FkvFileStore(dbFile, this.recordLength * maxRecordSize);
// init active cache
this.activeCache = new HashMap<String, Record>(maxRecordSize);
// init deleted stack
this.deletedCache = new ArrayDeque<Record>();
deserial();
}
@Override
public void put(String key, String value) {
int keyLength = this.keyLength;
if (key == null || key.getBytes().length != keyLength) {
throw new IllegalArgumentException("key:" + key);
}
if (value == null || value.getBytes().length != this.valueLength) {
throw new IllegalArgumentException("value:" + value);
}
if (size() >= this.maxRecordSize) {
throw new StackOverflowError("key:" + key + " vlaue:" + value + " size:" + size());
}
//put时如果缓存里没有,则新增并放到缓存里.如果缓存里已经有,则更新!
try {
writeLock.lock();
Record record = this.activeCache.get(key);
if (record == null) { //新增
putNewRecord(key, value);
} else { //更新
record.setValue(value);
//定位到这条记录的offset,然后再定位到value的开始位置,覆盖value数据
this.store.put(record.getIndex() + STATUS_LENGTH + keyLength, value.getBytes());
}
} finally {
writeLock.unlock();
}
}
private void putNewRecord(String key, String value) {
int index;
// no deleted record 没有要删除的记录,直接追加在文件末尾, 否则先写在删除的位置.
if (this.deletedCache.isEmpty()) {
index = endIndex;
endIndex += this.recordLength;
} else {
//为什么可以先在delete的位置开始覆盖数据(尽管key不相同)?因为key,value的长度都是固定的!
//被删除的数据占用的空间和要写入的数据的空间是一样的,所以可以直接覆盖掉被标记为删除的记录.
Record deletedRecord = this.deletedCache.pop();
index = deletedRecord.getIndex();
}
// 一条新记录的诞生,经过了create, store, cache三个过程: 构造新对象,存储新对象,索引新对象
Record newRecord = createNewRecord(key, value, index);
storeNewRecord(newRecord); // first store record
cacheNewRecord(key, newRecord); // second cache record
}
/**
* 创建一条记录
* @param key
* @param value
* @param index 实际上是Record在文件中的offset
* @return
*/
private Record createNewRecord(String key, String value, int index) {
Record newRecord = new Record();
newRecord.setValue(value);
newRecord.setKey(key);
newRecord.setIndex(index);
return newRecord;
}
// 写到文件里
private void storeNewRecord(Record newRecord) {
//每条记录的第一个字节是标记位
writeBuffer[0] = STATUS_ACTIVE;
byte[] key = newRecord.getKey().getBytes();
//复制新创建的记录的key到writeBuffer的第二个字节(索引从0开始)开始
System.arraycopy(key, 0, writeBuffer, 1, key.length);
byte[] value = newRecord.getValue().getBytes();
//复制value到writeBuffer中
System.arraycopy(value, 0, writeBuffer, 1 + key.length, value.length);
//writeBuffer的长度=this.recordLength,在初始化时指定
writeBuffer[writeBuffer.length - 1] = ENDER;
//放进FileStore里
store.put(newRecord.getIndex(), writeBuffer);
}
// 放进缓存里
private void cacheNewRecord(String key, Record newRecord) {
this.activeCache.put(key, newRecord);
}
@Override
public int size() {
return this.activeCache.size();
}
@Override
public void delete(String key) {
try {
writeLock.lock();
Record r = this.activeCache.get(key);
if (r != null) {
//从active map中移出
Record deletedRecord = this.activeCache.remove(key);
//添加到delete queue中
this.deletedCache.add(deletedRecord);
//更新store file的标记: 定位到文件的index位置,覆盖这个位置的数据
this.store.put(deletedRecord.getIndex(), STATUS_DELETE);
}
} finally {
writeLock.unlock();
}
}
// 反序列化. 在构造函数时调用. 还原数据到activeCache和deletedCache中
protected void deserial() {
if (this.store.isNeedDeserial()) {
byte[] recordBuf = new byte[recordLength];
int index = 0;
// 循环每条记录
while (this.store.remaining() > 0) {
store.get(recordBuf);
if (isValidRecord(recordBuf)) {
byte[] keyBuf = new byte[keyLength];
System.arraycopy(recordBuf, STATUS_LENGTH, keyBuf, 0, keyLength);
byte[] valueBuf = new byte[valueLength];
System.arraycopy(recordBuf, STATUS_LENGTH + keyLength, valueBuf, 0, valueLength);
// 还原文件里的记录
Record record = this.createNewRecord(new String(keyBuf), new String(valueBuf), index);
if (isDelete(recordBuf)) {
this.deletedCache.push(record);
} else {
this.activeCache.put(record.getKey(), record);
}
index += recordLength;
} else {
// reach ender, break
break;
}
}
this.endIndex = index;
}
}
public Record getRecord(String key) {
Record record = null;
record = this.activeCache.get(key);
return record;
}
@Override
public String get(String key) {
//获取数据时,直接从缓存中获取
Record record = this.activeCache.get(key);
//如果缓存中没有,那就是没有了.因为在put的时候有放进去,一定放到缓存里.不在缓存里的,一定没有put过
if (record == null) return null;
return record.getValue();
}
public Map<String, Record> getActiveCache() {
return this.activeCache;
}
public Deque<Record> getDeletedCache() {
return this.deletedCache;
}
public int getDeletedSize() {
return this.deletedCache.size();
}
public int getEndIndex() {
return endIndex;
}
public int getKeyLength() {
return keyLength;
}
public int getMaxRecordSize() {
return maxRecordSize;
}
public int getRecordLength() {
return recordLength;
}
public FkvStore getStore() {
return store;
}
public int getValueLength() {
return valueLength;
}
private boolean isDelete(byte[] record) {
if (record[0] == STATUS_DELETE) {
return true;
}
return false;
}
// 判断是否有效: 标记位必须是0或1, 最后一个字节必须是分隔符\n
private boolean isValidRecord(byte[] recordBuf) {
if (recordBuf[0] != STATUS_ACTIVE && recordBuf[0] != STATUS_DELETE) {
return false;
}
if (recordBuf[recordBuf.length - 1] != ENDER) {
return false;
}
return true;
}
@Override
public void close() throws IOException {
this.store.close();
}
@Override
public void clear() {
try {
writeLock.lock();
Set<Entry<String, Record>> set = this.activeCache.entrySet();
for (Entry<String, Record> entry : set) {
Record deletedRecord = entry.getValue();
this.deletedCache.add(deletedRecord);
this.store.put(deletedRecord.getIndex(), STATUS_DELETE);
}
this.activeCache.clear();
} finally {
writeLock.unlock();
}
}
}