package com.github.eddyzhou.mcache;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Random;
public class MemBucket {
public static final int BUCKET_LINK_SIZE = 4;
public static final int BUCKET_VERSION = 0x3201;
public static final int HEADER_SIZE = 20;
private static final int INDEX_USEDNUM = 2;
private static final int INDEX_LINK_BEGIN = 3;
private static final int INDEX_LINK_END = 4;
private ByteBuffer buffer;
private IntBuffer headerBuffer;
private IntBuffer linkBuffer;
private int totalSize;
// 以下为header部分
private int bucketVersion;
private int bucketNum;
private int usedNum;
private int linkBegin;
private int linkEnd;
/**
* 创建前先通过calSize方法预先计算大小.<br>
* 需要保留一个bucket不用(下标为0的bucket), 每个bucket需要保留4个字节的指针用来标识使用情况
*
* @param bucketNum
* @return
*/
public static int calSize(int bucketNum) {
int size = HEADER_SIZE + (bucketNum + 1) * BUCKET_LINK_SIZE;
return size;
}
public MemBucket(ByteBuffer buffer, int bufferSize, int bucketNum,
boolean isInit) throws MmapException {
if (bucketNum <= 0 || bucketNum >= 0x7FFFFFFF) {
throw new IllegalArgumentException("bucketNum[" + bucketNum
+ "] not valid.");
}
this.totalSize = calSize(bucketNum);
if (bufferSize != totalSize)
throw new MmapException("MemBucket size invalid: bucketNum="
+ bucketNum + ", bufferSize=" + bufferSize);
this.buffer = buffer;
this.bucketNum = bucketNum;
this.headerBuffer = this.buffer.asIntBuffer();
ByteBuffer tmpBuffer = this.buffer.duplicate();
tmpBuffer.position(HEADER_SIZE);
tmpBuffer.limit(HEADER_SIZE + (bucketNum + 1) * BUCKET_LINK_SIZE);
this.linkBuffer = tmpBuffer.slice().asIntBuffer();
if (isInit) {
initialize();
} else {
check();
}
}
public boolean isEmpty() {
return usedNum == 0;
}
public boolean isFull() {
return usedNum == bucketNum;
}
public int getUsedNum() {
return usedNum;
}
public int getIdleNum() {
return bucketNum - usedNum;
}
public int size() {
return bucketNum;
}
/**
* Note:分配的bucket从1开始计算
*
* @return
* @throws MmapException
*/
public int alloc() throws MmapException {
if (isFull())
throw new MmapException("MemBucket alloc err: MemBucket is full.");
if (linkBegin == 0 || linkEnd == 0)
throw new MmapException("MemBucket alloc err: linkBegin: "
+ linkBegin + ", linkEnd: " + linkEnd);
int pos = linkBegin;
int link = linkBuffer.get(pos);
assert ((link & 0x80000000) == 0);
int next = (link & 0x7FFFFFFF);
linkBuffer.put(pos, 0x80000000);
linkBegin = next;
usedNum = usedNum + 1;
headerBuffer.put(INDEX_LINK_BEGIN, linkBegin);
headerBuffer.put(INDEX_LINK_END, linkEnd);
if (next == 0) {
assert (linkEnd == pos);
linkEnd = 0;
headerBuffer.put(INDEX_LINK_END, linkEnd);
assert (isFull());
}
assert (pos > 0);
return pos;
}
public boolean hasLink(int idx) {
if (idx <= 0 || idx > bucketNum)
throw new IllegalArgumentException("idx[" + idx + "] not valid.");
int link = linkBuffer.get(idx);
if ((link & 0x80000000) == 0)
return false;
return true;
}
public boolean free(int idx) {
if (idx <= 0 || idx > bucketNum)
throw new IllegalArgumentException("idx[" + idx + "] not valid.");
int link = linkBuffer.get(idx);
if ((link & 0x80000000) == 0)
return false;
if (linkBegin == 0) {
assert (linkEnd == 0);
assert (usedNum == bucketNum);
linkBuffer.put(idx, 0);
linkBegin = idx;
linkEnd = idx;
usedNum = usedNum - 1;
headerBuffer.put(INDEX_LINK_BEGIN, linkBegin);
headerBuffer.put(INDEX_LINK_END, linkEnd);
headerBuffer.put(INDEX_USEDNUM, usedNum);
} else {
linkBuffer.put(idx, linkBegin);
linkBegin = idx;
usedNum = usedNum - 1;
headerBuffer.put(INDEX_LINK_BEGIN, linkBegin);
headerBuffer.put(INDEX_USEDNUM, usedNum);
}
return true;
}
public void setLink(int idx, int next) {
if (next == 0) {
linkEnd = idx;
headerBuffer.put(INDEX_LINK_END, linkEnd);
}
linkBuffer.put(idx, next);
}
public void setLinkUsed(int idx) {
linkBuffer.put(idx, 0x80000000);
}
public void setUsedAndLink(int usedNum, int linkBegin, int linkEnd) {
this.usedNum = usedNum;
this.linkBegin = linkBegin;
this.linkEnd = linkEnd;
headerBuffer.put(INDEX_LINK_BEGIN, linkBegin);
headerBuffer.put(INDEX_LINK_END, linkEnd);
headerBuffer.put(INDEX_USEDNUM, usedNum);
}
@Override
public String toString() {
StringBuilder strBu = new StringBuilder();
strBu.append("MemBucket [").append("version=").append(bucketVersion)
.append(" , bucketNum=").append(bucketNum)
.append(" , usedNum=").append(usedNum).append(" , linkBegin=")
.append(linkBegin).append(" , linkEnd=").append(linkEnd);
return strBu.toString();
}
private void initialize() throws MmapException {
if (headerBuffer.get(0) != 0)
throw new MmapException(
"MemBucket Initialize failed: bucketVersion["
+ headerBuffer.get(0) + "] is not 0");
bucketVersion = BUCKET_VERSION;
usedNum = 0;
linkBegin = 1; // 0-保留
linkEnd = bucketNum;
headerBuffer.put(0, bucketVersion);
headerBuffer.put(1, bucketNum);
headerBuffer.put(2, usedNum);
headerBuffer.put(3, linkBegin);
headerBuffer.put(4, linkEnd);
for (int i = 1; i <= bucketNum; i++) {
if (i == bucketNum) {
linkBuffer.put(i, 0);
} else {
linkBuffer.put(i, i + 1);
}
}
}
private void check() throws MmapException {
bucketVersion = headerBuffer.get(0);
int bucketNum = headerBuffer.get(1);
usedNum = headerBuffer.get(2);
linkBegin = headerBuffer.get(3);
linkEnd = headerBuffer.get(4);
if (bucketVersion != BUCKET_VERSION) {
throw new MmapException("MemBucket Check fail: bucketVersion="
+ bucketVersion);
}
if (this.bucketNum != bucketNum) {
throw new MmapException("MemBucket Check fail: bucketNum="
+ bucketNum + "!=" + this.bucketNum);
}
if (usedNum > bucketNum) {
throw new MmapException("MemBucket Check fail: usedNum=" + usedNum
+ ">" + bucketNum);
}
if (linkBegin > bucketNum) {
throw new MmapException("MemBucket Check fail: linkBegin="
+ linkBegin + ">" + bucketNum);
}
if (linkEnd > bucketNum) {
throw new MmapException("MemBucket Check fail: linkEnd=" + linkEnd
+ ">" + bucketNum);
}
fullCheck();
}
private void fullCheck() throws MmapException {
int realUsed = 0;
int idle = 0;
for (int i = 1; i <= bucketNum; i++) {
int link = linkBuffer.get(i);
if ((link & 0x80000000) == 0) {
idle++;
} else {
realUsed++;
}
}
System.out.println("realUsed: " + realUsed + ", idle: " + idle);
System.out.println("usedNum=" + usedNum + ", bucketNum=" + bucketNum
+ ", linkBegin=" + linkBegin + ", linkEnd=" + linkEnd);
}
public static void main(String[] args) throws MmapException {
int bucketNum = 10000000;
int bufferSize = MemBucket.calSize(bucketNum);
System.out.println("bucketNum=" + bucketNum + ", bufferSize="
+ bufferSize);
ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
MemBucket bucket = new MemBucket(buffer, bufferSize, bucketNum, true);
System.out.println("bucket=" + bucket.toString());
Random random = new Random();
for (int i = 0; i < 10000000; i++) {
int idx = random.nextInt(bucketNum);
bucket.hasLink(idx);
if (random.nextInt(10) == 0) {
bucket.free(idx);
} else {
idx = bucket.alloc();
if (idx == 0) {
System.out.println("alloc failed.");
}
}
}
System.out.println("insert over");
MemBucket _bucket = new MemBucket(buffer, bufferSize, bucketNum, false);
System.out.println("_bucket=" + _bucket.toString());
}
}