package com.github.eddyzhou.mcache;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
/**
* 不支持key为0的情况<br>
* 只维护索引,不维护数据,数据区需自行处理(2G空间问题)
*
* @author EddyZhou(zhouqian1103@gmail.com)
*
*/
public class MemIndexMap {
public static final int HASH_UNIT_SIZE = 16; // key + pos + next
public static final int HASH_VERSION = 0x3301;
public static final int HEADER_SIZE = 24;
private ByteBuffer buffer;
private IntBuffer headerBuffer;
private ByteBuffer hashBuffer;
private ByteBuffer conflictBucketBuffer;
private ByteBuffer conflictBuffer;
private ByteBuffer dataBucketBuffer;
private MemBucket conflictBucket;
private MemBucket dataBucket;
private int totalSize;
// 以下为header部分
private int hashVersion;
private int hashNum; // 建议使用接近(dataNum*2)的一个质数
private int conflictNum; // 建议使用dataNum
private int dataNum;
private int dataSize;
private int useConflictNum; // 使用中的冲突数量
/**
* 创建前先通过calSize方法预先计算索引文件大小
*
* @param hashNum
* @param conflictNum
* @param dataNum
* @return
*/
public static int calSize(int hashNum, int conflictNum, int dataNum) {
// size = header size + hash size + conflict bucket size + conflict size + data bucket size
int size = HEADER_SIZE + hashNum * HASH_UNIT_SIZE
+ MemBucket.calSize(conflictNum) + (conflictNum + 1)
* HASH_UNIT_SIZE + MemBucket.calSize(dataNum);
return size;
}
public MemIndexMap(ByteBuffer buffer, int bufferSize, int hashNum,
int conflictNum, int dataNum, int dataSize, boolean isInit)
throws MmapException {
if (!(hashNum > 0 && conflictNum > 0 && dataNum > 0 && bufferSize > 0 && dataSize > 12))
throw new IllegalArgumentException("argument err. hashNum:"
+ hashNum + ", confilctNum:" + conflictNum + ", dataNum:"
+ dataNum + ", BufferSize:" + bufferSize);
this.totalSize = calSize(hashNum, conflictNum, dataNum);
if (this.totalSize != bufferSize)
throw new MmapException("bufferSize err. hashNum=" + hashNum
+ ", conflictNum=" + conflictNum + ", dataNum=" + dataNum
+ ", bufferSize=" + bufferSize);
this.buffer = buffer;
this.hashNum = hashNum;
this.conflictNum = conflictNum;
this.dataNum = dataNum;
this.dataSize = dataSize;
this.headerBuffer = this.buffer.asIntBuffer();
ByteBuffer tmpBuffer = this.buffer.duplicate();
tmpBuffer.position(HEADER_SIZE);
tmpBuffer.limit(HEADER_SIZE + hashNum * HASH_UNIT_SIZE);
this.hashBuffer = tmpBuffer.slice();
tmpBuffer = this.buffer.duplicate();
tmpBuffer.position(HEADER_SIZE + hashNum * HASH_UNIT_SIZE);
tmpBuffer.limit(HEADER_SIZE + hashNum * HASH_UNIT_SIZE
+ MemBucket.calSize(conflictNum));
this.conflictBucketBuffer = tmpBuffer.slice();
tmpBuffer = this.buffer.duplicate();
tmpBuffer.position(HEADER_SIZE + hashNum * HASH_UNIT_SIZE
+ MemBucket.calSize(conflictNum));
tmpBuffer.limit(HEADER_SIZE + hashNum * HASH_UNIT_SIZE
+ MemBucket.calSize(conflictNum) + (conflictNum + 1)
* HASH_UNIT_SIZE);
this.conflictBuffer = tmpBuffer.slice();
tmpBuffer = this.buffer.duplicate();
tmpBuffer.position(HEADER_SIZE + hashNum * HASH_UNIT_SIZE
+ MemBucket.calSize(conflictNum) + (conflictNum + 1)
* HASH_UNIT_SIZE);
tmpBuffer.limit(totalSize);
this.dataBucketBuffer = tmpBuffer.slice();
if (isInit)
initialize();
else
check();
}
public boolean isEmpty() {
return dataBucket.isEmpty();
}
public boolean isFull() {
return dataBucket.isFull();
}
public int getUsedNum() {
return dataBucket.getUsedNum();
}
public int getIdleNum() {
return dataBucket.getIdleNum();
}
public int size() {
return dataBucket.size();
}
/**
* 获取key对应的索引位置
*
* @param key
* @return 存在返回position,否则返回0
*/
public int getIndex(long key) {
if (key <= 0)
throw new IllegalArgumentException("key must > 0. key: " + key);
int idx = Math.abs((int) key % hashNum);
ByteBuffer tmpBuffer = hashBuffer.duplicate();
tmpBuffer.position(idx * HASH_UNIT_SIZE);
tmpBuffer.limit(idx * HASH_UNIT_SIZE + HASH_UNIT_SIZE);
ByteBuffer bb = tmpBuffer.slice();
long _key = bb.asLongBuffer().get(0);
int _pos = bb.asIntBuffer().get(2);
int _next = bb.asIntBuffer().get(3);
if (_key == 0) {
assert (_pos == 0 && _next == 0);
return 0;
}
if (key == _key) {
assert (_pos > 0);
return _pos;
}
if (_next == 0) {
return 0;
}
// 到冲突区找
for (int i = 0; i < conflictNum; i++) {
assert (_pos > 0);
assert (conflictBucket.hasLink(_next));
idx = _next;
tmpBuffer = conflictBuffer.duplicate();
tmpBuffer.position(idx * HASH_UNIT_SIZE);
tmpBuffer.limit(idx * HASH_UNIT_SIZE + HASH_UNIT_SIZE);
bb = tmpBuffer.slice();
_key = bb.asLongBuffer().get(0);
_pos = bb.asIntBuffer().get(2);
_next = bb.asIntBuffer().get(3);
assert (_key > 0 && _pos > 0);
if (key == _key) {
assert (_pos > 0);
return _pos;
}
if (_next == 0) {
return 0;
}
}
return 0;
}
/**
* 须先getIndex,当不存在时调用insertData申请一个data空间,然后写数据,然后调用insertIndex写索引,如果写索引失败,
* 需要回收data空间
*
* @return position
* @throws MmapException
*/
public int insertData() throws MmapException {
return dataBucket.alloc();
}
/**
* 先释放索引再释放data
*
* @param pos
* @return
*/
public boolean freeData(int pos) {
return dataBucket.free(pos);
}
public void insertIndex(long key, int pos) throws MmapException {
if (key <= 0 || pos <= 0) {
throw new IllegalArgumentException("arguemnt err. key:" + key
+ ", pos:" + pos);
}
int idx = Math.abs((int) (key % hashNum));
ByteBuffer tmpBuffer = hashBuffer.duplicate();
tmpBuffer.position(idx * HASH_UNIT_SIZE);
tmpBuffer.limit(idx * HASH_UNIT_SIZE + HASH_UNIT_SIZE);
ByteBuffer bb = tmpBuffer.slice();
long _key = bb.asLongBuffer().get(0);
int _pos = bb.asIntBuffer().get(2);
int _next = bb.asIntBuffer().get(3);
assert (key != _key);
int newIdx = 0;
// 如果有冲突,要在冲突区新建一个索引
if (_key > 0) {
assert (_pos > 0);
newIdx = conflictBucket.alloc();
assert (newIdx > 0);
ByteBuffer _tmpBuffer = conflictBuffer.duplicate();
_tmpBuffer.position(newIdx * HASH_UNIT_SIZE);
_tmpBuffer.limit(newIdx * HASH_UNIT_SIZE + HASH_UNIT_SIZE);
ByteBuffer _bb = _tmpBuffer.slice();
_bb.asLongBuffer().put(0, _key);
_bb.asIntBuffer().put(2, _pos);
_bb.asIntBuffer().put(3, _next);
useConflictNum++;
headerBuffer.put(5, useConflictNum);
}
// 写hash区
bb.asLongBuffer().put(0, key);
bb.asIntBuffer().put(2, pos);
bb.asIntBuffer().put(3, newIdx);
}
public boolean freeIndex(long key) {
if (key <= 0)
throw new IllegalArgumentException("agument err. key:" + key);
int idx = Math.abs((int) (key % hashNum));
ByteBuffer tmpBuffer = hashBuffer.duplicate();
tmpBuffer.position(idx * HASH_UNIT_SIZE);
tmpBuffer.limit(idx * HASH_UNIT_SIZE + HASH_UNIT_SIZE);
ByteBuffer bb = tmpBuffer.slice();
long _key = bb.asLongBuffer().get(0);
int _pos = bb.asIntBuffer().get(2);
int _next = bb.asIntBuffer().get(3);
ByteBuffer _bb = bb;
ByteBuffer preBb = null, nextBb = null;
long preKey = 0, nextKey = 0;
int nextIdx = 0;
int nextPos = 0;
int nextNext = 0;
boolean found = false;
int i = 0;
for (; i < conflictNum; i++) {
// not found
if (_key == 0) {
assert (_pos == 0 && _next == 0);
return false;
}
// found
if (key == _key) {
assert (_pos > 0);
found = true;
if (_next > 0) {
assert (conflictBucket.hasLink(_next));
nextIdx = _next;
tmpBuffer = conflictBuffer.duplicate();
tmpBuffer.position(nextIdx * HASH_UNIT_SIZE);
tmpBuffer.limit(nextIdx * HASH_UNIT_SIZE
+ HASH_UNIT_SIZE);
nextBb = tmpBuffer.slice();
nextKey = nextBb.asLongBuffer().get(0);
nextPos = nextBb.asIntBuffer().get(2);
nextNext = nextBb.asIntBuffer().get(3);
assert (nextKey > 0 && nextPos > 0);
}
break;
}
// not found
if (_next == 0) {
return false;
}
assert (conflictBucket.hasLink(_next));
idx = _next;
preBb = bb;
preKey = _key;
tmpBuffer = conflictBuffer.duplicate();
tmpBuffer.position(idx * HASH_UNIT_SIZE);
tmpBuffer.limit(idx * HASH_UNIT_SIZE + HASH_UNIT_SIZE);
bb = tmpBuffer.slice();
_key = bb.asLongBuffer().get(0);
_pos = bb.asIntBuffer().get(2);
_next = bb.asIntBuffer().get(3);
assert (_key > 0 && _pos > 0);
}
assert (i < conflictNum && found);
if (preKey > 0) {
preBb.asIntBuffer().put(3, _next);
conflictBucket.free(idx);
useConflictNum--;
if (useConflictNum < 0)
useConflictNum = 0;
headerBuffer.put(5, useConflictNum);
} else {
_bb.asLongBuffer().put(0, nextKey);
_bb.asIntBuffer().put(2, nextPos);
_bb.asIntBuffer().put(3, nextNext);
if (nextKey > 0) {
conflictBucket.free(nextIdx);
useConflictNum--;
if (useConflictNum < 0)
useConflictNum = 0;
headerBuffer.put(5, useConflictNum);
}
}
return true;
}
@Override
public String toString() {
return "MemIndexMap [version=" + hashVersion + " ,datanum=" + dataNum
+ " ,datasize=" + dataSize + " ,hashNum=" + hashNum
+ " ,conflict=" + conflictNum + " . " + "used=" + getUsedNum()
+ " ,idle=" + getIdleNum() + " ,conflict=" + useConflictNum
+ "]";
}
public void setDataLink(int idx, int next) {
dataBucket.setLink(idx, next);
}
public void setDataLinkUsed(int idx) {
dataBucket.setLinkUsed(idx);
}
public void setDataUsedAndLink(int usedNum, int linkBegin, int linkEnd) {
dataBucket.setUsedAndLink(usedNum, linkBegin, linkEnd);
}
private void initialize() throws MmapException {
if (headerBuffer.get(0) != 0)
throw new MmapException(
"MemIndexMap initialize failed: hashVersion must be 0");
hashVersion = HASH_VERSION;
headerBuffer.put(0, hashVersion);
headerBuffer.put(1, hashNum);
headerBuffer.put(2, conflictNum);
headerBuffer.put(3, dataNum);
headerBuffer.put(4, dataSize);
useConflictNum = 0;
headerBuffer.put(5, useConflictNum);
conflictBucket = new MemBucket(conflictBucketBuffer,
MemBucket.calSize(conflictNum), conflictNum, true);
dataBucket = new MemBucket(dataBucketBuffer,
MemBucket.calSize(dataNum), dataNum, true);
}
private void check() throws MmapException {
hashVersion = headerBuffer.get(0);
int hashNum = headerBuffer.get(1);
int conflictNum = headerBuffer.get(2);
int dataNum = headerBuffer.get(3);
int dataSize = headerBuffer.get(4);
useConflictNum = headerBuffer.get(5);
if (hashVersion != HASH_VERSION) {
throw new MmapException("MemIndexMap Check fail: hashVersion="
+ hashVersion);
}
if (this.hashNum != hashNum) {
throw new MmapException("MemIndexMap Check fail: hashNum="
+ hashNum + "!=" + this.hashNum);
}
if (this.conflictNum != conflictNum) {
throw new MmapException("MemIndexMap Check fail: conflictNum="
+ conflictNum + "!=" + this.conflictNum);
}
if (this.dataNum != dataNum) {
throw new MmapException("MemIndexMap Check fail: dataNum="
+ dataNum + "!=" + this.dataNum);
}
if (this.dataSize != dataSize) {
throw new MmapException("MemIndexMap Check fail: dataSize="
+ dataSize + "!=" + this.dataSize);
}
conflictBucket = new MemBucket(conflictBucketBuffer,
MemBucket.calSize(conflictNum), conflictNum, false);
dataBucket = new MemBucket(dataBucketBuffer,
MemBucket.calSize(dataNum), dataNum, false);
}
public static void main(String[] args) {
int num = 36000000;
System.out.println("size="
+ MemIndexMap.calSize(MmapUtils.getlargerPrime(num * 2),
num / 2, num));
}
}