package com.github.eddyzhou.mcache;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* mmap manager.
* <p>
* usage:<br>
* <li>遍历数据(遍历模式取到的ByteBuffer前8个字节为key)
* <li>建立索引
* <li>修改数据块数量或大小
*
* @author EddyZhou(zhouqian1103@gmail.com)
*
*/
public class MmapManager {
private static final int MAX_FILE_SIZE = 0x7FFFFFFF; // 单个数据文件最大支持2G
private String fileName;
private int dataNum;
private int dataSize;
private MmapFile[] dataFiles;
private ByteBuffer[] dataBuffers;
private int dataNumOfOneFile;
private int dataFileNum;
public MmapManager(String fileName, int dataNum, int dataSize) throws MmapException, IOException {
if (dataNum <= 0 || dataSize <= 0)
throw new IllegalArgumentException("argument err. dataNum:" + dataNum + ", dataSize:" + dataSize);
this.fileName = fileName;
this.dataNum = dataNum;
this.dataSize = dataSize + 12;
this.dataNumOfOneFile = (int) (MAX_FILE_SIZE / this.dataSize);
this.dataNum = 0;
long totalSize = 1L * (dataNum + 1) * this.dataSize;
long size = 0;
long lastFileSize = 0;
for (;;) {
if (totalSize <= size) {
break;
}
long left = totalSize - size;
if (left > (this.dataNumOfOneFile * this.dataSize)) {
left = this.dataNumOfOneFile * this.dataSize;
} else {
lastFileSize = left;
}
size += left;
this.dataNum++;
}
System.out.println("dataFileNum:" + dataFileNum + ", dataNumOfOneFile:" + dataNumOfOneFile);
this.dataFiles = new MmapFile[dataFileNum];
dataBuffers = new ByteBuffer[dataFileNum];
for (int i = 0; i < dataFileNum; i++) {
File f = new File(fileName + ".dat" + i);
if (!f.exists())
throw new MmapException(fileName + ".dat" + i + " not exists.");
if (i == dataFileNum - 1) {
dataFiles[i] = new MmapFile(f, (int) lastFileSize);
} else {
dataFiles[i] = new MmapFile(f, this.dataSize * this.dataNumOfOneFile);
}
dataBuffers[i] = dataFiles[i].getBuffer();
}
}
/**
* 用于按以下遍历模式访问数据区,返回null不代表下一个索引位置也没有数据,可继续往下访问
*
* <pre>
* for (int i = 0; i < dataNum; i++) {
* ByteBuffer data = accessDataBuffer(i);
* if (data == null)
* continue;
* // do it,可直接修改数据,注意不要修改key部分
* }
* </pre>
*
* @param idx
* @return ByteBuffer 前8个字节为key,接着是4个字节的长度,再接着是data内容,整个dataSize的最后4个字节是时间戳
*/
public ByteBuffer getDataBuffer(int idx) {
if (idx > dataNum) {
throw new IllegalArgumentException("idx[" + idx + "] > dataNum[" + dataNum + "]");
}
int pos = idx + 1;
ByteBuffer tmpBuffer = dataBuffers[pos / this.dataNumOfOneFile].duplicate();
tmpBuffer.position((pos % this.dataNumOfOneFile) * dataSize);
tmpBuffer.limit((pos % this.dataNumOfOneFile + 1) * dataSize);
return tmpBuffer.slice();
}
public byte[] getNotExpiredData(int pos, int expireTime) {
ByteBuffer tmpBuffer = dataBuffers[pos / this.dataNumOfOneFile].duplicate();
tmpBuffer.position((pos % this.dataNumOfOneFile) * dataSize);
tmpBuffer.limit((pos % this.dataNumOfOneFile) + 12);
long id = tmpBuffer.slice().asLongBuffer().get(0);
int len = tmpBuffer.slice().asIntBuffer().get(2);
if (id == 0) return null;
ByteBuffer _tmpBuffer = dataBuffers[pos / this.dataNumOfOneFile].duplicate();
_tmpBuffer.position((pos % this.dataNumOfOneFile + 1) * dataSize - 4);
_tmpBuffer.position((pos % this.dataNumOfOneFile + 1) * dataSize);
int time = _tmpBuffer.slice().asIntBuffer().get(0);
if (time < expireTime) return null;
tmpBuffer = dataBuffers[pos / this.dataNumOfOneFile].duplicate();
tmpBuffer.position((pos % this.dataNumOfOneFile) * dataSize + 12);
tmpBuffer.limit((pos % this.dataNumOfOneFile) * dataSize + 12 + len);
ByteBuffer bb = tmpBuffer.slice();
if (bb == null) return null;
byte[] bytes = new byte[bb.capacity()];
bb.slice().get(bytes);
return bytes;
}
public void rebuildIndex() throws MmapException, IOException {
int hashNum = MmapUtils.getlargerPrime(dataNum * 2);
int conflictNum = Math.abs(dataNum / 2);
int indexSize = MemIndexMap.calSize(hashNum, conflictNum, dataNum);
File f = new File(fileName + ".idx");
if (f.exists())
throw new MmapException("MmapManager rebuild index failed: index file[" + fileName + ".idx] exists.");
MmapFile indexFile = new MmapFile(f, indexSize);
ByteBuffer buffer = indexFile.getBuffer();
MemIndexMap indexMap = new MemIndexMap(buffer, indexSize, hashNum, conflictNum, dataNum, dataSize, true);
long startTime = System.currentTimeMillis();
System.out.println("rebuild index start at: " + startTime);
int buildNum = 0;
int nextLink = 0;
int beginLink = 0, endLink = 0;
for (int i = dataNum; i > 0; i--) {
ByteBuffer data = this.getDataBuffer(i - 1);
if (data != null) {
long key = data.asLongBuffer().get(0);
if (key > 0) {
++buildNum;
indexMap.insertIndex(key, i);
indexMap.setDataLinkUsed(i);
} else {
System.out.println("warning: data invalid at " + i);
if (nextLink == 0)
endLink = i;
indexMap.setDataLink(i, nextLink);
nextLink = i;
}
} else {
if (nextLink == 0)
endLink = i;
indexMap.setDataLink(i, nextLink);
nextLink = i;
}
}
if (buildNum == 0) {
beginLink = 0;
endLink = 0;
} else {
beginLink = nextLink;
}
indexMap.setDataUsedAndLink(buildNum, beginLink, endLink);
long endTime = System.currentTimeMillis();
System.out.println("rebuild index succ. use " + (endTime - startTime)
+ " ms, buildNum = " + buildNum);
System.out.println(indexMap.toString());
}
public void modifyDataFile(String newFileName, int newDataNum, int newDataSize) throws MmapException, IOException {
if (newDataNum <= 0 && newDataSize <= 0)
throw new IllegalArgumentException("argument err. newDataNum:" + newDataNum + ", newDataSize:" + newDataSize);
long startTime = System.currentTimeMillis();
System.out.println("rebuild index start at: " + startTime);
newDataSize = newDataSize + 16;
int newDataNumOfOneFile = (int) (MAX_FILE_SIZE / newDataSize);
ByteBuffer[] newDataBuffers = createNewFiles(newFileName, newDataNum, newDataSize);
int _num = dataNum > newDataNum ? newDataNum : dataNum;
int _size = dataSize > newDataSize ? newDataSize : dataSize;
int usedNum = 0;
int rebuildNum = 0;
ByteBuffer _buffer = null, buffer_ = null;
int idx = 1;
for (int i = 1; i <= dataNum; i++) {
if (idx > _num) {
if (i < dataNum)
System.out.println("newDataNum < dataNum, maybe not deal over.");
break;
}
_buffer = dataBuffers[i / dataNumOfOneFile].duplicate();
_buffer.position((i % dataNumOfOneFile) * dataSize);
_buffer.limit((i % dataNumOfOneFile) * dataSize + 8);
long _key = _buffer.slice().get(0);
if (_key > 0) {
_buffer = dataBuffers[i / dataNumOfOneFile].duplicate();
_buffer.position((i % dataNumOfOneFile) * dataSize);
_buffer.limit((i % dataNumOfOneFile) * dataSize + _size);
buffer_ = newDataBuffers[idx / newDataNumOfOneFile].duplicate();
buffer_.position((idx % newDataNumOfOneFile) * newDataSize);
buffer_.limit((idx % dataNumOfOneFile) * newDataSize + _size);
buffer_.slice().put(_buffer.slice());
idx++;
usedNum++;
rebuildNum++;
}
}
long endTime = System.currentTimeMillis();
System.out.println("modifyDataFile succ. use " + (endTime - startTime) + " ms");
System.out.println("oldNum=" + dataNum + ", oldSize=" + dataSize
+ ", newNum=" + newDataNum + ", newSize=" + newDataSize
+ " ,usedNum=" + usedNum + " ,rebuildNum=" + rebuildNum);
}
private ByteBuffer[] createNewFiles(String newFileName, int newDataNum, int newDataSize) throws MmapException, IOException {
MmapFile[] newDataFiles;
ByteBuffer[] newDataBuffers;
long newTotalSize = 1L * (newDataNum + 1) * newDataSize;
long newSize = 0;
long newLastFileSize = 0;
int newDataNumOfOneFile = (int) (MAX_FILE_SIZE / newDataSize);
int newDataFileNum = 0;
for (;;) {
if (newTotalSize <= newSize) {
break;
}
long left = newTotalSize - newSize;
if (left > (newDataNumOfOneFile * newDataSize)) {
left = newDataNumOfOneFile * newDataSize;
} else {
newLastFileSize = left;
}
newSize += left;
newDataFileNum++;
}
System.out.println("NewFileNum: " + newDataFileNum + ", newDataNumOfOneFile: " + newDataNumOfOneFile);
newDataFiles = new MmapFile[newDataFileNum];
newDataBuffers = new ByteBuffer[newDataFileNum];
for (int i = 0; i < dataFileNum; i++) {
File f = new File(newFileName + ".dat" + i);
if (f.exists())
throw new MmapException(newFileName + ".dat" + i + " is aready exists.");
if (i == newDataFileNum - 1) {
newDataFiles[i] = new MmapFile(f, (int) newLastFileSize);
} else {
newDataFiles[i] = new MmapFile(f, newDataNumOfOneFile * newDataSize);
}
newDataBuffers[i] = newDataFiles[i].getBuffer();
}
return newDataBuffers;
}
}