package com.github.believe3301.nonheapdb;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.TreeSet;
/**
* buffer block 缓冲块
* 一个内存块管理记录的put和get,remove操作.
* 添加一条记录返回这条记录的索引信息RecordIndex.
*
* 并提供在内存块中寻找合适的空闲块:findFreeBlock,
* 碎片整理:defragment,
* 保留空间:remainToRecord,
* 合并最后一条记录:mergeLastRecord.
*/
public class MemoryBuffer {
private int used; // record used byte
private int count; // record count
private ByteBuffer buf; // record buffer,position is zero
private ByteBuffer buf_append; // sliced record buffer,position is last record,only for append
/*free pool config */
//TODO LongTreeSet(reduce boxing)
// record free pool,sort with size desc(free block not merge and split)
// 空闲块的大小按照升序排列.这样新写入的块要写入空闲块时,可以根据块的大小决定写往满足大小的空闲块
private TreeSet<Long> fp;
public static final int FBMAX = 64; // fb max cout 最多允许64个空闲块
public static final float FBRATIO = 0.75f; // if fbRatio exceed,would process auto defragment
private MemoryBuffer(ByteBuffer buf) {
this.buf = buf;
this.buf_append = buf.slice();
//按照指定的Comparator进行排序.
this.fp = new TreeSet<Long>(RecordIndex.capacityComparator());
this.used = 0;
this.count = 0;
}
public static MemoryBuffer makeNew(int capacity) {
return makeNew(capacity, false);
}
public static MemoryBuffer makeNew(int capacity, byte[] init) {
ByteBuffer b = ByteBuffer.wrap(init);
return new MemoryBuffer(b);
}
public static MemoryBuffer makeNew(int capacity, boolean direct) {
ByteBuffer b;
if (direct) {
b = ByteBuffer.allocateDirect(capacity);
} else {
b = ByteBuffer.allocate(capacity);
}
//b.limit(capacity);
return new MemoryBuffer(b);
}
public int capacity() {
return this.buf.capacity();
}
public int remaining() {
return this.buf_append.remaining();
}
public int count() {
return this.count;
}
public int used() {
return this.used;
}
public int fpsize() {
return this.fp.size();
}
// 将记录的字节数组放入缓冲区中.返回记录的索引信息
// 全内存(Non-Heap)操作如何体现?
// 添加一条记录后,返回这条记录的索引信息. 因为记录直接写在内存中,
// 所以要在内存中知道刚刚写入的这条记录在内存中的位置信息,以及占用的大小
// 没有指定offset的话,会往buf_append末尾追加.也会修改buf的内容!
// 即追加到buf_append中的数据后,通过buf也能获取到写入的数据
// 原始buf和buf_append唯一的不同是:remain和position不同.
// 追加到buf_append后,position增加,remain减少. 而原始buf都没有变化(0和capacity)
public RecordIndex putData(ByteBuffer nbuf) {
RecordIndex record = new RecordIndex()
.setOffset(buf_append.position())
.setCapacity(nbuf.limit());
//追加到buf_append缓冲区中
this.buf_append.put(nbuf);
this.used += record.capacity(); //记录了内存缓冲区使用的字节数
this.count++; //记录的数量
return record;
}
// 有指定offset的话,会在内存缓冲区的指定位置写入数据.
public void putData(ByteBuffer rbuf, int offset) {
//slice()根据现有的缓冲区创建一个子缓冲区:它创建一个新的缓冲区,新缓冲区与原来的缓冲区的一部分共享数据。
ByteBuffer nbuf = this.buf.slice();
//定位到指定位置
nbuf.position(offset);
//写入数据
nbuf.put(rbuf);
this.used += rbuf.limit();
this.count++;
}
public void putData(byte data, int offset) {
ByteBuffer nbuf = this.buf.slice();
nbuf.position(offset);
nbuf.put(data);
}
public void putLong(long data, int offset){
ByteBuffer nbuf = this.buf.slice();
nbuf.position(offset);
nbuf.putLong(data);
}
//在offset开始,读取length个字节
public byte[] getData(int length, int offset) {
ByteBuffer nbuf = this.buf.slice();
nbuf.position(offset);
//要读取的长度比缓冲区剩余的数据还要多, 最多就只读取缓冲区的那些了.
//因为缓冲区的remainning的限制,如果要读取比remainning还要多的数据,是做不到的.
int rl = (length - nbuf.remaining() > 0) ? nbuf.remaining():length;
byte[] data = new byte[rl];
nbuf.get(data);
return data;
}
public byte getData(int offset){
ByteBuffer nbuf = this.buf.slice();
nbuf.position(offset);
return nbuf.get();
}
public Long getLong(int offset) {
ByteBuffer nbuf = this.buf.slice();
nbuf.position(offset);
return nbuf.getLong();
}
/*
* remove record and add to free pool, if record is the last record
* 删除一条记录,要在内存缓冲区中将这条记录(在内存缓冲区的开始位置)的第一个字节标记为空闲
* 然后要将这条记录加入到空闲池中. fp接受的是RecordIndex. 而不是记录本身.
* 通过RecordIndex可以方便地获取到offset用于更新空闲标记.
*/
public void removeRecord(RecordIndex rec, int used) {
this.putData(Record.MAGICFB,rec.offset());
this.used -= used;
this.count --;
this.fp.add(rec.getBucket());
}
/*
* if the memory block is active, before removeRecord you can mergeLastRecord
* 如果当前内存块是激活的,在删除记录前,可以对最后一条记录进行合并
* mergetLastRecord(lastRecordIndex)
* removeRecord(lastRecordIndex)
*/
public boolean mergeLastRecord(RecordIndex rec, int used) {
// the rec is last record. buf_append是内存块的最后一个字节.
// 最后一条记录的offset+capacity=最后一条记录的endpoint.即整个内存块的endpoint
if (rec.offset() + rec.capacity() == buf_append.position()) {
//定位到最后一条记录的开始位置.这样下一条记录如果写数据的话,会直接覆盖最后一条记录.
//我们并没有像removeRecord那样在offset位置标记空闲,并加入到fp中.而是直接覆盖!
this.buf_append.position(rec.offset());
//删除掉最后一条记录.所以计数器都要减少
this.used -= used;
this.count --;
//返回true,表示合并成功,就不会调用removeRecord了.
return true;
}
return false;
}
/*
* add remaining free memory to free pool, and return record
* 添加剩余的空闲内存到空闲块池中,并返回当前记录(RecordIndex)
*/
public RecordIndex remainToRecord(int index) {
//构造RecordIndex时,指定三个字段,其中bucket的计算是根据三个字段组合起来的
RecordIndex record = new RecordIndex()
.setCapacity(this.remaining()) //要把剩余的都给这条记录,应该在内存块的末尾调用该方法
.setOffset(buf_append.position()) //buf_append的位置是要写入记录的offset.
.setIndex(index); //指定是哪个内存块
//在指定位置修改标记位为空闲. 因为剩余的空间要被保留(不够写)
this.putData(Record.MAGICFB, record.offset());
//添加到空闲块池中是record的bucket. 要从空闲池中获取,只要根据bucket反解析即可得到RecordIndex
this.fp.add(record.getBucket());
return record;
}
/*
* find free block by length (best fit), if free block is too large, would to split
* 给定长度,寻找最适合的块. 如果空闲块太大,则进行分裂.
*
* 注意:调用该方法后,如果没有分裂,则找到的那个空闲块会从fp中删除,并返回给客户端
* 如果空闲块太大,分裂后,假设原先有一个空闲块,调用该方法后,还会剩余一个新分裂出来的空闲块.
* 但是注意返回给客户端的是客户端要求长度的空闲块.而不是新分裂出的空闲块.
* 比如原先空闲块=59, 要求length=27, 则返回给客户端的是length=27的空闲块,并分裂出新的空闲块,大小=32
*/
public RecordIndex findFreeBlock(int length) {
//什么时候没有空闲块? 数据一直put,没有remove,则没有空闲块. 空闲块发生在remove的时候,或内存块的最后几个字节
if (this.fp.size() == 0) return null;
//先构造一个大小满足给定长度的RecordIndex. 首先要求记录的长度=length.
RecordIndex fake = new RecordIndex().setCapacity(length);
//向上取整.不能向下,因为不能比要求的还少.少了就不干
Long b = this.fp.ceiling(fake.getBucket());
RecordIndex rec = null;
if (b != null) {
//传给RecordIndex的是bucket,
rec = new RecordIndex(b);
//确保记录的标记位是空闲标记.在放入fp空闲池时,进行了更新操作
assert this.getData(rec.offset()) == Record.MAGICFB;
//根据RecordIndex的bucket可以反解析出RecordIndex的三个字段
//找到空闲块,要从fp中删除. 因为符合条件的空闲块将用于写数据.所以当前空闲块就不是空闲的了
this.fp.remove(rec.getBucket());
int rsiz = rec.capacity(); //record size
// split: 当前找到的空闲块的大小比我们需要释放的块的2倍空间还要多
System.out.println("rsize:"+rsiz + "|length:"+length + "|分裂否:"+(rsiz >= length * 2));
if (rsiz >= length * 2) {
int offset = rec.offset();
//rec是要返回的,nrec是新分裂出的RecordIndex,因为rsiz足够大,所以将空闲块分成rec和nrec
//|<------------rsiz--------->|
//|offset |offset+length
//| rec | nrec |
//|<-length-->|
// |<-rsiz-length->|
RecordIndex nrec = new RecordIndex()
.setCapacity(rsiz - length)
.setOffset(offset + length) //新分裂的offset从要返回的offset+要返回的length开始
.setIndex(rec.index()); //在同一个内存块里
//标记这条记录也是空闲块
this.putData(Record.MAGICFB,nrec.offset());
//将新分裂的空闲块加入池中
this.fp.add(nrec.getBucket());
//最后设置要返回的记录的容量
rec.setCapacity(length);
}
}
return rec;
}
/*
* check defragment
*/
public boolean canDefragment() {
//TODO
//(free -freemax)/free
//什么时候可以整理碎片:空闲块数量超过指定值,
if ((this.fp.size() > FBMAX) && (((this.used() * 1.0f) / this.capacity()) < FBRATIO)) {
return true;
}
return false;
}
private boolean checkRecord(MemoryManager mm, int start, int end) {
int i = start;
while(i < end) {
Record rec = mm.getRecord(this, i);
int capacity = rec.getIndex().capacity();
i += capacity;
}
return i == end;
}
/*
* auto defragment 碎片整理
*/
public void defragment(MemoryManager mm) {
TreeSet<Long> offs = new TreeSet<Long>(RecordIndex.offsetComparator());
offs.addAll(this.fp);
Iterator<Long> iter = offs.iterator();
//第一个空闲块
int off = 0;
RecordIndex rec = null;
if (iter.hasNext()) {
rec = new RecordIndex(iter.next());
}
assert this.getData(rec.offset()) == Record.MAGICFB;
while(iter.hasNext()) {
//下一个空闲块
RecordIndex nrec = new RecordIndex(iter.next());
//shift offset
mm.shiftRecord(this, off + rec.capacity(), rec.offset() + rec.capacity(), nrec.offset());
//move data
ByteBuffer nbuf = this.buf.slice();
nbuf.position(rec.offset() - off);
ByteBuffer kbuf = nbuf.slice();
kbuf.position(off + rec.capacity());
kbuf.limit(off + nrec.offset() - rec.offset());
kbuf.compact();
assert checkRecord(mm, rec.offset() - off, nrec.offset() - rec.capacity() - off);
off += rec.capacity();
rec = nrec;
}
rec.setCapacity(rec.capacity() + off);
rec.setOffset(rec.offset() - off);
this.putData(Record.MAGICFB, rec.offset());
this.fp.clear();
this.fp.add(rec.getBucket());
}
/*
private byte[] getRecordBuffer(byte[] key, byte[] value) {
int siz = key.length + value.length + 1;
byte[] brec = new byte[siz];
brec[0] = Record.MAGICREC;
System.arraycopy(key, 0, brec, 1, key.length);
System.arraycopy(value, 0, brec, 1 + key.length, value.length);
return brec;
}
*/
//二进制备份
public String hexDump() {
ByteBuffer nbuf = this.buf.slice();
//要用buf_append的position,因为put时,数据追加到buf_append,如果用buf.position=0
byte[] arr = new byte[buf_append.position()];
//从buf中获取指定大小的数据赋值到arr字节数组中. 因为buf_append和buf共享数据,所以buf中也有数据
nbuf.get(arr);
return Util.hexDump(arr, 0, arr.length);
}
}