2018-11-19 15:16:12 +01:00
|
|
|
package org.warp.jcwdb;
|
|
|
|
|
2018-12-20 01:12:46 +01:00
|
|
|
import it.unimi.dsi.fastutil.longs.Long2IntMap;
|
|
|
|
import it.unimi.dsi.fastutil.longs.Long2IntRBTreeMap;
|
|
|
|
import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator;
|
|
|
|
|
2018-11-19 15:16:12 +01:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.nio.channels.SeekableByteChannel;
|
|
|
|
|
2018-11-21 01:02:25 +01:00
|
|
|
public class FileAllocator implements AutoCloseable {
|
2018-12-20 01:12:46 +01:00
|
|
|
private static final int MAXIMUM_UNALLOCATED_ENTRIES = 500000;
|
|
|
|
|
2018-11-20 18:39:48 +01:00
|
|
|
private final SeekableByteChannel dataFileChannel;
|
2018-12-20 01:12:46 +01:00
|
|
|
private volatile long fileSize;
|
2018-11-20 18:39:48 +01:00
|
|
|
private volatile boolean closed;
|
|
|
|
private final Object closeLock = new Object();
|
2018-11-22 23:31:41 +01:00
|
|
|
private final Object allocateLock = new Object();
|
2018-12-20 01:12:46 +01:00
|
|
|
/**
|
|
|
|
* index -> free space size
|
|
|
|
*/
|
|
|
|
private final Long2IntRBTreeMap freeBytes = new Long2IntRBTreeMap((a, b) -> (int) (a - b));
|
2018-12-20 00:16:16 +01:00
|
|
|
|
2018-11-20 18:39:48 +01:00
|
|
|
public FileAllocator(SeekableByteChannel dataFileChannel) throws IOException {
|
|
|
|
this.dataFileChannel = dataFileChannel;
|
2018-12-20 01:12:46 +01:00
|
|
|
this.fileSize = this.dataFileChannel.size();
|
2018-11-20 18:39:48 +01:00
|
|
|
}
|
2018-12-20 00:16:16 +01:00
|
|
|
|
2018-11-19 15:16:12 +01:00
|
|
|
/**
|
2018-11-20 18:39:48 +01:00
|
|
|
* TODO: not implemented
|
2018-12-20 00:16:16 +01:00
|
|
|
*
|
2018-11-20 18:39:48 +01:00
|
|
|
* @param size
|
|
|
|
* @return offset
|
2018-11-19 15:16:12 +01:00
|
|
|
*/
|
2018-11-20 18:39:48 +01:00
|
|
|
public long allocate(int size) {
|
|
|
|
checkClosed();
|
2018-11-22 23:31:41 +01:00
|
|
|
synchronized (allocateLock) {
|
2018-12-20 01:12:46 +01:00
|
|
|
long offset = allocateIntoUnusedParts(size);
|
|
|
|
if (offset == -1) {
|
|
|
|
return allocateToEnd(size);
|
|
|
|
} else {
|
|
|
|
return offset;
|
|
|
|
}
|
2018-11-22 23:31:41 +01:00
|
|
|
}
|
2018-11-19 15:16:12 +01:00
|
|
|
}
|
|
|
|
|
2018-12-20 01:12:46 +01:00
|
|
|
private long allocateIntoUnusedParts(int size) {
|
|
|
|
ObjectBidirectionalIterator<Long2IntMap.Entry> it = freeBytes.long2IntEntrySet().iterator();
|
|
|
|
long holeOffset = -1;
|
|
|
|
int holeSize = 0;
|
|
|
|
while (it.hasNext()) {
|
|
|
|
Long2IntMap.Entry entry = it.next();
|
|
|
|
int currentHoleSize = entry.getIntValue();
|
|
|
|
if (currentHoleSize < size) {
|
|
|
|
freeBytes.remove(holeOffset);
|
|
|
|
if (holeSize > size) {
|
|
|
|
freeBytes.put(holeOffset + size, holeSize - size);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
holeOffset = entry.getLongKey();
|
|
|
|
holeSize = currentHoleSize;
|
|
|
|
}
|
|
|
|
return holeOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
private long allocateToEnd(int size) {
|
|
|
|
long allocatedOffset = fileSize;
|
|
|
|
fileSize += size;
|
|
|
|
return allocatedOffset;
|
|
|
|
}
|
|
|
|
|
2018-12-20 00:16:16 +01:00
|
|
|
|
2018-11-20 18:39:48 +01:00
|
|
|
public void close() throws IOException {
|
2018-12-20 00:16:16 +01:00
|
|
|
if (closed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
synchronized (closeLock) {
|
|
|
|
if (closed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
closed = true;
|
|
|
|
}
|
2018-11-19 15:16:12 +01:00
|
|
|
}
|
2018-12-20 00:16:16 +01:00
|
|
|
|
2018-11-19 15:16:12 +01:00
|
|
|
/**
|
2018-11-20 18:39:48 +01:00
|
|
|
* Frees the unused bytes
|
2018-12-20 00:16:16 +01:00
|
|
|
*
|
2018-11-20 18:39:48 +01:00
|
|
|
* @param startPosition
|
|
|
|
* @param length
|
2018-11-19 15:16:12 +01:00
|
|
|
*/
|
2018-11-20 18:39:48 +01:00
|
|
|
public void markFree(long startPosition, int length) {
|
|
|
|
checkClosed();
|
2018-12-20 01:12:46 +01:00
|
|
|
|
|
|
|
if (freeBytes.containsKey(startPosition + length)) {
|
|
|
|
int secondLength = freeBytes.remove(startPosition + length);
|
|
|
|
freeBytes.put(startPosition, length + secondLength);
|
|
|
|
} else {
|
|
|
|
boolean addedToList = false;
|
|
|
|
for (Long2IntMap.Entry entry : freeBytes.long2IntEntrySet()) {
|
|
|
|
if (entry.getLongKey() + entry.getIntValue() == startPosition) {
|
|
|
|
freeBytes.put(entry.getLongKey(), entry.getIntValue() + length);
|
|
|
|
addedToList = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!addedToList) {
|
|
|
|
freeBytes.put(startPosition, length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (freeBytes.size() > MAXIMUM_UNALLOCATED_ENTRIES) {
|
|
|
|
freeBytes.remove(freeBytes.lastLongKey());
|
|
|
|
}
|
2018-11-19 15:16:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-11-20 18:39:48 +01:00
|
|
|
private void checkClosed() {
|
|
|
|
if (closed) {
|
|
|
|
throw new RuntimeException("Index Manager is closed.");
|
|
|
|
}
|
2018-11-19 15:16:12 +01:00
|
|
|
}
|
|
|
|
}
|