You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
736 lines
28 KiB
736 lines
28 KiB
// kelondroBLOBHeap.java
|
|
// (C) 2008 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
|
|
// first published 09.07.2008 on http://yacy.net
|
|
//
|
|
// This is a part of YaCy, a peer-to-peer based web search engine
|
|
//
|
|
// $LastChangedDate: 2008-03-14 01:16:04 +0100 (Fr, 14 Mrz 2008) $
|
|
// $LastChangedRevision: 4558 $
|
|
// $LastChangedBy: orbiter $
|
|
//
|
|
// LICENSE
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
package de.anomic.kelondro;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
import java.util.SortedMap;
|
|
import java.util.TreeMap;
|
|
import java.util.concurrent.ExecutionException;
|
|
|
|
import de.anomic.server.serverMemory;
|
|
import de.anomic.server.logging.serverLog;
|
|
|
|
public final class kelondroBLOBHeap implements kelondroBLOB {
|
|
|
|
|
|
private kelondroBytesLongMap index; // key/seek relation for used records
|
|
private TreeMap<Long, Integer> free; // list of {size, seek} pairs denoting space and position of free records
|
|
private final File heapFile; // the file of the heap
|
|
private final kelondroByteOrder ordering; // the ordering on keys
|
|
private kelondroCachedFileRA file; // a random access to the file
|
|
private HashMap<String, byte[]> buffer; // a write buffer to limit IO to the file; attention: Maps cannot use byte[] as key
|
|
private int buffersize; // bytes that are buffered in buffer
|
|
private int buffermax; // maximum size of the buffer
|
|
|
|
/*
|
|
* This class implements a BLOB management based on a sequence of records in a random access file
|
|
* The data structure is:
|
|
* file :== record*
|
|
* record :== reclen key blob
|
|
* reclen :== <4 byte integer == length of key and blob>
|
|
* key :== <bytes as defined with keylen, if first byte is zero then record is empty>
|
|
* blob :== <bytes of length reclen - keylen>
|
|
* that means that each record has the size reclen+4
|
|
*
|
|
* The elements are organized in two data structures:
|
|
* index<kelondroBytesLongMap> : key/seek relation for used records
|
|
* free<ArrayList<Integer[]>> : list of {size, seek} pairs denoting space and position of free records
|
|
*
|
|
* Because the blob sizes are stored with integers, one entry may not exceed 2GB
|
|
*
|
|
* If a record is removed, it becomes a free record.
|
|
* New records are either appended to the end of the file or filled into a free record.
|
|
* A free record must either fit exactly to the size of the new record, or an old record is splitted
|
|
* into a filled and a new, smaller empty record.
|
|
*/
|
|
|
|
/**
|
|
* create a heap file: a arbitrary number of BLOBs, indexed by an access key
|
|
* The heap file will be indexed upon initialization.
|
|
* @param heapFile
|
|
* @param keylength
|
|
* @param ordering
|
|
* @throws IOException
|
|
*/
|
|
public kelondroBLOBHeap(final File heapFile, final int keylength, final kelondroByteOrder ordering, int buffermax) throws IOException {
|
|
this.ordering = ordering;
|
|
this.heapFile = heapFile;
|
|
this.buffermax = buffermax;
|
|
|
|
this.index = null; // will be created as result of initialization process
|
|
this.free = new TreeMap<Long, Integer>();
|
|
this.buffer = new HashMap<String, byte[]>();
|
|
this.buffersize = 0;
|
|
this.file = new kelondroCachedFileRA(heapFile);
|
|
byte[] key = new byte[keylength];
|
|
int reclen;
|
|
long seek = 0;
|
|
kelondroBytesLongMap.initDataConsumer indexready = kelondroBytesLongMap.asynchronusInitializer(keylength, this.ordering, 0, Math.max(10, (int) (Runtime.getRuntime().freeMemory() / (10 * 1024 * 1024))));
|
|
|
|
loop: while (true) { // don't test available() here because this does not work for files > 2GB
|
|
|
|
try {
|
|
// go to seek position
|
|
file.seek(seek);
|
|
|
|
// read length of the following record without the length of the record size bytes
|
|
reclen = file.readInt();
|
|
//assert reclen > 0 : " reclen == 0 at seek pos " + seek;
|
|
if (reclen == 0) {
|
|
// very bad file inconsistency
|
|
serverLog.logSevere("kelondroBLOBHeap", "reclen == 0 at seek pos " + seek + " in file " + heapFile);
|
|
this.file.setLength(seek); // delete everything else at the remaining of the file :-(
|
|
break loop;
|
|
}
|
|
|
|
// read key
|
|
file.readFully(key, 0, key.length);
|
|
|
|
} catch (final IOException e) {
|
|
// EOF reached
|
|
break loop; // terminate loop
|
|
}
|
|
|
|
// check if this record is empty
|
|
if (key == null || key[0] == 0) {
|
|
// it is an empty record, store to free list
|
|
if (reclen > 0) free.put(seek, reclen);
|
|
} else {
|
|
if (this.ordering.wellformed(key)) {
|
|
indexready.consume(key, seek);
|
|
key = new byte[keylength];
|
|
} else {
|
|
serverLog.logWarning("kelondroBLOBHeap", "BLOB " + heapFile.getName() + ": skiped not wellformed key " + new String(key) + " at seek pos " + seek);
|
|
}
|
|
}
|
|
// new seek position
|
|
seek += 4L + reclen;
|
|
}
|
|
indexready.finish();
|
|
|
|
// try to merge free entries
|
|
if (this.free.size() > 1) {
|
|
int merged = 0;
|
|
Map.Entry<Long, Integer> lastFree, nextFree;
|
|
final Iterator<Map.Entry<Long, Integer>> i = this.free.entrySet().iterator();
|
|
lastFree = i.next();
|
|
while (i.hasNext()) {
|
|
nextFree = i.next();
|
|
//System.out.println("*** DEBUG BLOB: free-seek = " + nextFree.seek + ", size = " + nextFree.size);
|
|
// check if they follow directly
|
|
if (lastFree.getKey() + lastFree.getValue() + 4 == nextFree.getKey()) {
|
|
// merge those records
|
|
file.seek(lastFree.getKey());
|
|
lastFree.setValue(lastFree.getValue() + nextFree.getValue() + 4); // this updates also the free map
|
|
file.writeInt(lastFree.getValue());
|
|
file.seek(nextFree.getKey());
|
|
file.writeInt(0);
|
|
i.remove();
|
|
merged++;
|
|
} else {
|
|
lastFree = nextFree;
|
|
}
|
|
}
|
|
serverLog.logInfo("kelondroBLOBHeap", "BLOB " + heapFile.getName() + ": merged " + merged + " free records");
|
|
}
|
|
|
|
try {
|
|
this.index = indexready.result();
|
|
} catch (InterruptedException e) {
|
|
e.printStackTrace();
|
|
} catch (ExecutionException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
/*
|
|
// DEBUG
|
|
Iterator<byte[]> i = index.keys(true, null);
|
|
//byte[] b;
|
|
int c = 0;
|
|
while (i.hasNext()) {
|
|
key = i.next();
|
|
System.out.println("*** DEBUG BLOBHeap " + this.name() + " KEY=" + new String(key));
|
|
//b = get(key);
|
|
//System.out.println("BLOB=" + new String(b));
|
|
//System.out.println();
|
|
c++;
|
|
if (c >= 20) break;
|
|
}
|
|
System.out.println("*** DEBUG - counted " + c + " BLOBs");
|
|
*/
|
|
}
|
|
|
|
public String name() {
|
|
return this.heapFile.getName();
|
|
}
|
|
|
|
/**
|
|
* the number of BLOBs in the heap
|
|
* @return the number of BLOBs in the heap
|
|
*/
|
|
public synchronized int size() {
|
|
return this.index.size() + this.buffer.size();
|
|
}
|
|
|
|
public kelondroByteOrder ordering() {
|
|
return this.ordering;
|
|
}
|
|
|
|
/**
|
|
* test if a key is in the heap file. This does not need any IO, because it uses only the ram index
|
|
* @param key
|
|
* @return true if the key exists, false otherwise
|
|
*/
|
|
public synchronized boolean has(final byte[] key) {
|
|
assert index != null;
|
|
assert index.row().primaryKeyLength == key.length : index.row().primaryKeyLength + "!=" + key.length;
|
|
|
|
// check the buffer
|
|
if (this.buffer.containsKey(new String(key))) return true;
|
|
|
|
// check if the file index contains the key
|
|
try {
|
|
return index.getl(key) >= 0;
|
|
} catch (final IOException e) {
|
|
e.printStackTrace();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add a BLOB to the heap: this adds the blob always to the end of the file
|
|
* @param key
|
|
* @param blob
|
|
* @throws IOException
|
|
*/
|
|
private void add(final byte[] key, final byte[] blob) throws IOException {
|
|
assert blob.length > 0;
|
|
assert index.row().primaryKeyLength == key.length : index.row().primaryKeyLength + "!=" + key.length;
|
|
if ((blob == null) || (blob.length == 0)) return;
|
|
final int pos = (int) file.length();
|
|
file.seek(pos);
|
|
file.writeInt(key.length + blob.length);
|
|
file.write(key);
|
|
file.write(blob, 0, blob.length);
|
|
index.putl(key, pos);
|
|
}
|
|
|
|
/**
|
|
* flush the buffer completely
|
|
* this is like adding all elements of the buffer, but it needs only one IO access
|
|
* @throws IOException
|
|
*/
|
|
private void flushBuffer() throws IOException {
|
|
// check size of buffer
|
|
Iterator<Map.Entry<String, byte[]>> i = this.buffer.entrySet().iterator();
|
|
int l = 0;
|
|
while (i.hasNext()) l += i.next().getValue().length;
|
|
assert l == this.buffersize;
|
|
|
|
// append all contents of the buffer into one byte[]
|
|
i = this.buffer.entrySet().iterator();
|
|
final int pos = (int) file.length();
|
|
int posFile = pos;
|
|
int posBuffer = 0;
|
|
byte[] ba = new byte[this.buffersize + (4 + this.index.row().primaryKeyLength) * this.buffer.size()];
|
|
Map.Entry<String, byte[]> entry;
|
|
byte[] key, blob, b;
|
|
while (i.hasNext()) {
|
|
entry = i.next();
|
|
key = entry.getKey().getBytes();
|
|
blob = entry.getValue();
|
|
index.putl(key, posFile);
|
|
b = kelondroAbstractRA.int2array(key.length + blob.length);
|
|
assert b.length == 4;
|
|
System.arraycopy(b, 0, ba, posBuffer, 4);
|
|
System.arraycopy(key, 0, ba, posBuffer + 4, key.length);
|
|
System.arraycopy(blob, 0, ba, posBuffer + 4 + key.length, blob.length);
|
|
posFile += 4 + key.length + blob.length;
|
|
posBuffer += 4 + key.length + blob.length;
|
|
}
|
|
assert ba.length == posBuffer; // must fit exactly
|
|
this.file.seek(pos);
|
|
this.file.write(ba);
|
|
this.buffer.clear();
|
|
this.buffersize = 0;
|
|
}
|
|
|
|
/**
|
|
* read a blob from the heap
|
|
* @param key
|
|
* @return
|
|
* @throws IOException
|
|
*/
|
|
public synchronized byte[] get(final byte[] key) throws IOException {
|
|
assert index.row().primaryKeyLength == key.length : index.row().primaryKeyLength + "!=" + key.length;
|
|
|
|
// check the buffer
|
|
byte[] blob = this.buffer.get(new String(key));
|
|
if (blob != null) return blob;
|
|
|
|
// check if the index contains the key
|
|
final long pos = index.getl(key);
|
|
if (pos < 0) return null;
|
|
|
|
// access the file and read the container
|
|
file.seek(pos);
|
|
final int len = file.readInt() - index.row().primaryKeyLength;
|
|
if (serverMemory.available() < len) {
|
|
if (!serverMemory.request(len, false)) return null; // not enough memory available for this blob
|
|
}
|
|
|
|
// read the key
|
|
final byte[] keyf = new byte[index.row().primaryKeyLength];
|
|
file.readFully(keyf, 0, keyf.length);
|
|
assert this.ordering.compare(key, keyf) == 0;
|
|
|
|
// read the blob
|
|
blob = new byte[len];
|
|
file.readFully(blob, 0, blob.length);
|
|
|
|
return blob;
|
|
}
|
|
|
|
/**
|
|
* retrieve the size of the BLOB
|
|
* @param key
|
|
* @return the size of the BLOB or -1 if the BLOB does not exist
|
|
* @throws IOException
|
|
*/
|
|
public long length(byte[] key) throws IOException {
|
|
assert index.row().primaryKeyLength == key.length : index.row().primaryKeyLength + "!=" + key.length;
|
|
|
|
// check the buffer
|
|
byte[] blob = this.buffer.get(new String(key));
|
|
if (blob != null) return blob.length;
|
|
|
|
// check if the index contains the key
|
|
final long pos = index.getl(key);
|
|
if (pos < 0) return -1;
|
|
|
|
// access the file and read the size of the container
|
|
file.seek(pos);
|
|
return file.readInt() - index.row().primaryKeyLength;
|
|
}
|
|
|
|
/**
|
|
* clears the content of the database
|
|
* @throws IOException
|
|
*/
|
|
public synchronized void clear() throws IOException {
|
|
this.buffer.clear();
|
|
this.buffersize = 0;
|
|
this.index.clear();
|
|
this.free.clear();
|
|
try {
|
|
this.file.close();
|
|
} catch (final IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
this.heapFile.delete();
|
|
this.file = new kelondroCachedFileRA(heapFile);
|
|
}
|
|
|
|
/**
|
|
* close the BLOB table
|
|
* @throws
|
|
*/
|
|
public synchronized void close() {
|
|
shrinkWithGapsAtEnd();
|
|
try {
|
|
flushBuffer();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
index.close();
|
|
free.clear();
|
|
try {
|
|
file.close();
|
|
} catch (final IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
index = null;
|
|
free = null;
|
|
file = null;
|
|
}
|
|
|
|
/**
|
|
* ask for the length of the primary key
|
|
* @return the length of the key
|
|
*/
|
|
public int keylength() {
|
|
return this.index.row().primaryKeyLength;
|
|
}
|
|
|
|
/**
|
|
* write a whole byte array as BLOB to the table
|
|
* @param key the primary key
|
|
* @param b
|
|
* @throws IOException
|
|
*/
|
|
public synchronized void put(final byte[] key, final byte[] b) throws IOException {
|
|
assert index.row().primaryKeyLength == key.length : index.row().primaryKeyLength + "!=" + key.length;
|
|
|
|
// we do not write records of length 0 into the BLOB
|
|
if (b.length == 0) return;
|
|
|
|
// first remove the old entry (removes from buffer and file)
|
|
this.remove(key);
|
|
|
|
// then look if we can use a free entry
|
|
if (putToGap(key, b)) return;
|
|
|
|
// if there is not enough space in the buffer, flush all
|
|
if (this.buffersize + b.length > buffermax) {
|
|
// this is too big. Flush everything
|
|
shrinkWithGapsAtEnd();
|
|
flushBuffer();
|
|
if (b.length > buffermax) {
|
|
this.add(key, b);
|
|
} else {
|
|
this.buffer.put(new String(key), b);
|
|
this.buffersize += b.length;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// add entry to buffer
|
|
this.buffer.put(new String(key), b);
|
|
this.buffersize += b.length;
|
|
}
|
|
|
|
private boolean putToGap(final byte[] key, final byte[] b) throws IOException {
|
|
assert index.row().primaryKeyLength == key.length : index.row().primaryKeyLength + "!=" + key.length;
|
|
|
|
// we do not write records of length 0 into the BLOB
|
|
if (b.length == 0) return true;
|
|
|
|
// then look if we can use a free entry
|
|
if (this.free.size() == 0) return false;
|
|
|
|
// find the largest entry
|
|
long lseek = -1;
|
|
int lsize = 0;
|
|
final int reclen = b.length + index.row().primaryKeyLength;
|
|
Map.Entry<Long, Integer> entry;
|
|
Iterator<Map.Entry<Long, Integer>> i = this.free.entrySet().iterator();
|
|
while (i.hasNext()) {
|
|
entry = i.next();
|
|
if (entry.getValue().intValue() == reclen) {
|
|
// we found an entry that has exactly the size that we need!
|
|
// we use that entry and stop looking for a larger entry
|
|
file.seek(entry.getKey());
|
|
final int reclenf = file.readInt();
|
|
assert reclenf == reclen;
|
|
file.write(key);
|
|
file.write(b);
|
|
|
|
// add the entry to the index
|
|
this.index.putl(key, entry.getKey());
|
|
|
|
// remove the entry from the free list
|
|
i.remove();
|
|
|
|
//System.out.println("*** DEBUG BLOB: replaced-fit record at " + entry.seek + ", reclen=" + reclen + ", key=" + new String(key));
|
|
|
|
// finished!
|
|
return true;
|
|
}
|
|
// look for the biggest size
|
|
if (entry.getValue() > lsize) {
|
|
lseek = entry.getKey();
|
|
lsize = entry.getValue();
|
|
}
|
|
}
|
|
|
|
// check if the found entry is large enough
|
|
if (lsize > reclen + 4) {
|
|
// split the free entry into two new entries
|
|
// if would be sufficient if lsize = reclen + 4, but this would mean to create
|
|
// an empty entry with zero next bytes for BLOB and key, which is not very good for the
|
|
// data structure in the file
|
|
|
|
// write the new entry
|
|
file.seek(lseek);
|
|
file.writeInt(reclen);
|
|
file.write(key);
|
|
file.write(b);
|
|
|
|
// add the index to the new entry
|
|
index.putl(key, lseek);
|
|
|
|
// define the new empty entry
|
|
final int newfreereclen = lsize - reclen - 4;
|
|
assert newfreereclen > 0;
|
|
file.writeInt(newfreereclen);
|
|
|
|
// remove the old free entry
|
|
this.free.remove(lseek);
|
|
|
|
// add a new free entry
|
|
this.free.put(lseek + 4 + reclen, newfreereclen);
|
|
|
|
//System.out.println("*** DEBUG BLOB: replaced-split record at " + lseek + ", reclen=" + reclen + ", new reclen=" + newfreereclen + ", key=" + new String(key));
|
|
|
|
// finished!
|
|
return true;
|
|
}
|
|
// could not insert to gap
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* remove a BLOB
|
|
* @param key the primary key
|
|
* @throws IOException
|
|
*/
|
|
public synchronized void remove(final byte[] key) throws IOException {
|
|
assert index.row().primaryKeyLength == key.length : index.row().primaryKeyLength + "!=" + key.length;
|
|
|
|
// check the buffer
|
|
byte[] blob = this.buffer.remove(new String(key));
|
|
if (blob != null) {
|
|
this.buffersize -= blob.length;
|
|
return;
|
|
}
|
|
|
|
// check if the index contains the key
|
|
final long seek = index.getl(key);
|
|
if (seek < 0) return;
|
|
|
|
// access the file and read the container
|
|
this.file.seek(seek);
|
|
int size = file.readInt();
|
|
//assert seek + size + 4 <= this.file.length() : heapFile.getName() + ": too long size " + size + " in record at " + seek;
|
|
long filelength = this.file.length(); // put in separate variable for debugging
|
|
if (seek + size + 4 > filelength) {
|
|
serverLog.logSevere("BLOBHeap", heapFile.getName() + ": too long size " + size + " in record at " + seek);
|
|
throw new IOException(heapFile.getName() + ": too long size " + size + " in record at " + seek);
|
|
}
|
|
|
|
// add entry to free array
|
|
this.free.put(seek, size);
|
|
|
|
// fill zeros to the content
|
|
int l = size; byte[] fill = new byte[size];
|
|
while (l-- > 0) fill[l] = 0;
|
|
this.file.write(fill, 0, size);
|
|
|
|
// remove entry from index
|
|
this.index.removel(key);
|
|
|
|
// recursively merge gaps
|
|
tryMergeNextGaps(seek, size);
|
|
tryMergePreviousGap(seek);
|
|
}
|
|
|
|
private void tryMergePreviousGap(final long thisSeek) throws IOException {
|
|
// this is called after a record has been removed. That may cause that a new
|
|
// empty record was surrounded by gaps. We merge with a previous gap, if this
|
|
// is also empty, but don't do that recursively
|
|
// If this is successful, it removes the given marker for thisSeed and
|
|
// because of this, this method MUST be called AFTER tryMergeNextGaps was called.
|
|
|
|
// first find the gap entry for the closest gap in front of the give gap
|
|
SortedMap<Long, Integer> head = this.free.headMap(thisSeek);
|
|
if (head.size() == 0) return;
|
|
long previousSeek = head.lastKey().longValue();
|
|
int previousSize = head.get(previousSeek).intValue();
|
|
|
|
// check if this is directly in front
|
|
if (previousSeek + previousSize + 4 == thisSeek) {
|
|
// right in front! merge the gaps
|
|
Integer thisSize = this.free.get(thisSeek);
|
|
assert thisSize != null;
|
|
mergeGaps(previousSeek, previousSize, thisSeek, thisSize.intValue());
|
|
}
|
|
}
|
|
|
|
private void tryMergeNextGaps(final long thisSeek, final int thisSize) throws IOException {
|
|
// try to merge two gaps if one gap has been processed already and the position of the next record is known
|
|
// if the next record is also a gap, merge these gaps and go on recursively
|
|
|
|
// first check if next gap position is outside of file size
|
|
long nextSeek = thisSeek + thisSize + 4;
|
|
if (nextSeek >= this.file.length()) return; // end of recursion
|
|
|
|
// move to next position and read record size
|
|
Integer nextSize = this.free.get(nextSeek);
|
|
if (nextSize == null) return; // finished, this is not a gap
|
|
|
|
// check if the record is a gap-record
|
|
assert nextSize.intValue() > 0;
|
|
if (nextSize.intValue() == 0) {
|
|
// a strange gap record: we can extend the thisGap with four bytes
|
|
// the nextRecord is a gap record; we remove that from the free list because it will be joined with the current gap
|
|
mergeGaps(thisSeek, thisSize, nextSeek, 0);
|
|
|
|
// recursively go on
|
|
tryMergeNextGaps(thisSeek, thisSize + 4);
|
|
} else {
|
|
// check if this is a true gap!
|
|
this.file.seek(nextSeek + 4);
|
|
byte[] o = new byte[1];
|
|
this.file.readFully(o, 0, 1);
|
|
int t = o[0];
|
|
assert t == 0;
|
|
if (t == 0) {
|
|
// the nextRecord is a gap record; we remove that from the free list because it will be joined with the current gap
|
|
mergeGaps(thisSeek, thisSize, nextSeek, nextSize.intValue());
|
|
|
|
// recursively go on
|
|
tryMergeNextGaps(thisSeek, thisSize + 4 + nextSize.intValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void mergeGaps(final long seek0, final int size0, final long seek1, final int size1) throws IOException {
|
|
//System.out.println("*** DEBUG-BLOBHeap " + heapFile.getName() + ": merging gap from pos " + seek0 + ", len " + size0 + " with next record of size " + size1 + " (+ 4)");
|
|
|
|
Integer g = this.free.remove(seek1); // g is only used for debugging
|
|
assert g != null;
|
|
assert g.intValue() == size1;
|
|
|
|
// overwrite the size bytes of next records with zeros
|
|
this.file.seek(seek1);
|
|
this.file.writeInt(0);
|
|
|
|
// the new size of the current gap: old size + len + 4
|
|
int newSize = size0 + 4 + size1;
|
|
this.file.seek(seek0);
|
|
this.file.writeInt(newSize);
|
|
|
|
// register new gap in the free array; overwrite old gap entry
|
|
g = this.free.put(seek0, newSize);
|
|
assert g != null;
|
|
assert g.intValue() == size0;
|
|
}
|
|
|
|
private void shrinkWithGapsAtEnd() {
|
|
// find gaps at the end of the file and shrink the file by these gaps
|
|
try {
|
|
while (this.free.size() > 0) {
|
|
Long seek = this.free.lastKey();
|
|
int size = this.free.get(seek).intValue();
|
|
if (seek.longValue() + size + 4 != this.file.length()) return;
|
|
// shrink the file
|
|
this.file.setLength(seek.longValue());
|
|
this.free.remove(seek);
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* iterator over all keys
|
|
* @param up
|
|
* @param rotating
|
|
* @return
|
|
* @throws IOException
|
|
*/
|
|
public synchronized kelondroCloneableIterator<byte[]> keys(final boolean up, final boolean rotating) throws IOException {
|
|
return new kelondroRotateIterator<byte[]>(this.index.keys(up, null), null, this.index.size());
|
|
}
|
|
|
|
/**
|
|
* iterate over all keys
|
|
* @param up
|
|
* @param firstKey
|
|
* @return
|
|
* @throws IOException
|
|
*/
|
|
public synchronized kelondroCloneableIterator<byte[]> keys(final boolean up, final byte[] firstKey) throws IOException {
|
|
return this.index.keys(up, firstKey);
|
|
}
|
|
|
|
public long length() throws IOException {
|
|
return this.heapFile.length() + this.buffersize;
|
|
}
|
|
|
|
public static void heaptest() {
|
|
final File f = new File("/Users/admin/blobtest.heap");
|
|
try {
|
|
//f.delete();
|
|
final kelondroBLOBHeap heap = new kelondroBLOBHeap(f, 12, kelondroNaturalOrder.naturalOrder, 1024 * 512);
|
|
heap.put("aaaaaaaaaaaa".getBytes(), "eins zwei drei".getBytes());
|
|
heap.put("aaaaaaaaaaab".getBytes(), "vier fuenf sechs".getBytes());
|
|
heap.put("aaaaaaaaaaac".getBytes(), "sieben acht neun".getBytes());
|
|
heap.put("aaaaaaaaaaad".getBytes(), "zehn elf zwoelf".getBytes());
|
|
// iterate over keys
|
|
Iterator<byte[]> i = heap.index.keys(true, null);
|
|
while (i.hasNext()) {
|
|
System.out.println("key_a: " + new String(i.next()));
|
|
}
|
|
i = heap.keys(true, false);
|
|
while (i.hasNext()) {
|
|
System.out.println("key_b: " + new String(i.next()));
|
|
}
|
|
heap.remove("aaaaaaaaaaab".getBytes());
|
|
heap.remove("aaaaaaaaaaac".getBytes());
|
|
heap.put("aaaaaaaaaaaX".getBytes(), "WXYZ".getBytes());
|
|
heap.close();
|
|
} catch (final IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
private static Map<String, String> map(String a, String b) {
|
|
HashMap<String, String> m = new HashMap<String, String>();
|
|
m.put(a, b);
|
|
return m;
|
|
}
|
|
|
|
public static void maptest() {
|
|
final File f = new File("/Users/admin/blobtest.heap");
|
|
try {
|
|
//f.delete();
|
|
final kelondroMap heap = new kelondroMap(new kelondroBLOBHeap(f, 12, kelondroNaturalOrder.naturalOrder, 1024 * 512), 500);
|
|
heap.put("aaaaaaaaaaaa", map("aaaaaaaaaaaa", "eins zwei drei"));
|
|
heap.put("aaaaaaaaaaab", map("aaaaaaaaaaab", "vier fuenf sechs"));
|
|
heap.put("aaaaaaaaaaac", map("aaaaaaaaaaac", "sieben acht neun"));
|
|
heap.put("aaaaaaaaaaad", map("aaaaaaaaaaad", "zehn elf zwoelf"));
|
|
heap.remove("aaaaaaaaaaab");
|
|
heap.remove("aaaaaaaaaaac");
|
|
heap.put("aaaaaaaaaaaX", map("aaaaaaaaaaad", "WXYZ"));
|
|
heap.close();
|
|
} catch (final IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public static void main(final String[] args) {
|
|
//heaptest();
|
|
maptest();
|
|
}
|
|
|
|
}
|