strangedb/src/main/java/org/warp/jcwdb/FileAllocator.java

152 lines
3.9 KiB
Java
Raw Normal View History

2018-11-19 15:16:12 +01:00
package org.warp.jcwdb;
import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2IntMap;
2018-12-20 01:12:46 +01:00
2018-11-19 15:16:12 +01:00
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
2018-12-21 10:03:30 +01:00
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
2018-11-19 15:16:12 +01:00
2018-11-21 01:02:25 +01:00
public class FileAllocator implements AutoCloseable {
2018-12-21 10:03:30 +01:00
private static final int MAXIMUM_UNALLOCATED_ENTRIES = 50000;
2018-12-20 01:12:46 +01:00
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
*/
2018-12-21 10:03:30 +01:00
private final Long2IntMap freeBytes = new Long2IntLinkedOpenHashMap();
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
public FileAllocator(SeekableByteChannel dataFileChannel, long fileSize, Long2IntMap freeBytes) {
2018-12-21 10:03:30 +01:00
this.dataFileChannel = dataFileChannel;
this.fileSize = fileSize;
this.freeBytes.putAll(freeBytes);
}
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) {
long offset;
if ((offset = allocateIntoUnusedParts(size)) != -1) {
if (offset + size > fileSize) {
fileSize = offset + size;
}
2018-12-20 01:12:46 +01:00
return offset;
} else {
return allocateToEnd(size);
2018-12-20 01:12:46 +01:00
}
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) {
if (FileIndexManager.ALWAYS_ALLOCATE_NEW) return -1;
2018-12-21 10:03:30 +01:00
Stream<Map.Entry<Long,Integer>> sorted =
freeBytes.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
final VariableWrapper<Long> holeOffset = new VariableWrapper<>(-1L);
final VariableWrapper<Integer> holeSize = new VariableWrapper<>(0);
sorted.anyMatch((entry) -> {
int currentHoleSize = entry.getValue();
2018-12-20 01:12:46 +01:00
if (currentHoleSize < size) {
2018-12-21 10:03:30 +01:00
return true;
}
holeOffset.var = entry.getKey();
holeSize.var = currentHoleSize;
return false;
});
if (holeOffset.var != -1L) {
freeBytes.remove(holeOffset.var);
if (holeSize.var > size) {
freeBytes.put(holeOffset.var + size, holeSize.var - size);
2018-12-20 01:12:46 +01:00
}
}
2018-12-21 10:03:30 +01:00
return holeOffset.var;
2018-12-20 01:12:46 +01:00
}
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();
if (FileIndexManager.ALWAYS_ALLOCATE_NEW) return;
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;
}
}
2018-12-21 10:03:30 +01:00
if (!addedToList && length > 0) {
2018-12-20 01:12:46 +01:00
freeBytes.put(startPosition, length);
}
}
2018-12-21 10:03:30 +01:00
if (startPosition + length >= fileSize) {
fileSize = startPosition;
}
// Remove the smallest hole in the file
2018-12-20 01:12:46 +01:00
if (freeBytes.size() > MAXIMUM_UNALLOCATED_ENTRIES) {
2018-12-21 10:03:30 +01:00
Stream<Map.Entry<Long,Integer>> sorted =
freeBytes.entrySet().stream()
.sorted(Map.Entry.comparingByValue());
Optional<Map.Entry<Long, Integer>> first = sorted.findFirst();
if (first.isPresent()) {
freeBytes.remove(first.get().getKey());
}
2018-12-20 01:12:46 +01:00
}
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
}
}