提交 a7d2ed06 authored 作者: Thomas Mueller's avatar Thomas Mueller

A persistent tree map (work in progress).

上级 3ff388a0
...@@ -4,9 +4,11 @@ ...@@ -4,9 +4,11 @@
* (http://h2database.com/html/license.html). * (http://h2database.com/html/license.html).
* Initial Developer: H2 Group * Initial Developer: H2 Group
*/ */
package org.h2.dev.store.btree; package org.h2.test.store;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.DataUtils;
/** /**
* An integer type. * An integer type.
...@@ -21,6 +23,14 @@ class IntegerType implements DataType { ...@@ -21,6 +23,14 @@ class IntegerType implements DataType {
return DataUtils.getVarIntLen((Integer) obj); return DataUtils.getVarIntLen((Integer) obj);
} }
public int getMaxLength(Object obj) {
return DataUtils.MAX_VAR_INT_LEN;
}
public int getMemory(Object obj) {
return 20;
}
public Integer read(ByteBuffer buff) { public Integer read(ByteBuffer buff) {
return DataUtils.readVarInt(buff); return DataUtils.readVarInt(buff);
} }
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.store;
import java.nio.ByteBuffer;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.DataTypeFactory;
import org.h2.dev.store.btree.DataUtils;
import org.h2.util.StringUtils;
/**
* A row type.
*/
public class RowType implements DataType {
private final DataType[] types;
private RowType(DataType[] types) {
this.types = types;
}
public int compare(Object a, Object b) {
if (a == b) {
return 0;
}
Object[] ax = (Object[]) a;
Object[] bx = (Object[]) b;
int al = ax.length;
int bl = bx.length;
int len = Math.min(al, bl);
for (int i = 0; i < len; i++) {
int comp = types[i].compare(ax[i], bx[i]);
if (comp != 0) {
return comp;
}
}
if (len < al) {
return -1;
} else if (len < bl) {
return 1;
}
return 0;
}
public int length(Object obj) {
Object[] x = (Object[]) obj;
int len = x.length;
int result = DataUtils.getVarIntLen(len);
for (int i = 0; i < len; i++) {
result += types[i].length(x[i]);
}
return result;
}
public int getMaxLength(Object obj) {
Object[] x = (Object[]) obj;
int len = x.length;
int result = DataUtils.MAX_VAR_INT_LEN;
for (int i = 0; i < len; i++) {
result += types[i].getMaxLength(x[i]);
}
return result;
}
public int getMemory(Object obj) {
Object[] x = (Object[]) obj;
int len = x.length;
int memory = 0;
for (int i = 0; i < len; i++) {
memory += types[i].getMemory(x[i]);
}
return memory;
}
public Object[] read(ByteBuffer buff) {
int len = DataUtils.readVarInt(buff);
Object[] x = new Object[len];
for (int i = 0; i < len; i++) {
x[i] = types[i].read(buff);
}
return x;
}
public void write(ByteBuffer buff, Object obj) {
Object[] x = (Object[]) obj;
int len = x.length;
DataUtils.writeVarInt(buff, len);
for (int i = 0; i < len; i++) {
types[i].write(buff, x[i]);
}
}
public String asString() {
StringBuilder buff = new StringBuilder();
buff.append('r');
buff.append('(');
for (int i = 0; i < types.length; i++) {
if (i > 0) {
buff.append(',');
}
buff.append(types[i].asString());
}
buff.append(')');
return buff.toString();
}
static RowType fromString(String t, DataTypeFactory factory) {
if (!t.startsWith("r(") || !t.endsWith(")")) {
throw new RuntimeException("Unknown type: " + t);
}
t = t.substring(2, t.length() - 1);
String[] array = StringUtils.arraySplit(t, ',', false);
DataType[] types = new DataType[array.length];
for (int i = 0; i < array.length; i++) {
types[i] = factory.fromString(array[i]);
}
return new RowType(types);
}
}
...@@ -29,6 +29,7 @@ public class TestTreeMapStore extends TestBase { ...@@ -29,6 +29,7 @@ public class TestTreeMapStore extends TestBase {
} }
public void test() throws Exception { public void test() throws Exception {
testLargeImport();
testBtreeStore(); testBtreeStore();
testDefragment(); testDefragment();
testReuseSpace(); testReuseSpace();
...@@ -38,10 +39,39 @@ public class TestTreeMapStore extends TestBase { ...@@ -38,10 +39,39 @@ public class TestTreeMapStore extends TestBase {
testSimple(); testSimple();
} }
private void testLargeImport() throws Exception {
String fileName = getBaseDir() + "/testCsv.h3";
int len = 10000;
for (int j = 0; j < 5; j++) {
FileUtils.delete(fileName);
BtreeMapStore s = openStore(fileName);
s.setMaxPageSize(40);
RowType rowType = RowType.fromString("r(i,,)", new TestTypeFactory());
BtreeMap<Integer, Object[]> m = s.openMap("data", new IntegerType(), rowType);
int i = 0;
// long t = System.currentTimeMillis();
for (; i < len;) {
Object[] o = new Object[3];
o[0] = i;
o[1] = "Hello";
o[2] = "World";
m.put(i, o);
i++;
if (i % 10000 == 0) {
s.store();
}
}
s.store();
s.close();
// System.out.println("store time " + (System.currentTimeMillis() - t));
// System.out.println("store size " + FileUtils.size(fileName));
}
}
private void testBtreeStore() { private void testBtreeStore() {
String fileName = getBaseDir() + "/testBtreeStore.h3"; String fileName = getBaseDir() + "/testBtreeStore.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
BtreeMapStore s = BtreeMapStore.open(fileName); BtreeMapStore s = openStore(fileName);
BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class); BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
int count = 2000; int count = 2000;
// Profiler p = new Profiler(); // Profiler p = new Profiler();
...@@ -64,8 +94,9 @@ public class TestTreeMapStore extends TestBase { ...@@ -64,8 +94,9 @@ public class TestTreeMapStore extends TestBase {
for (int i = 1; i < count; i++) { for (int i = 1; i < count; i++) {
assertEquals("hello " + i, m.get(i)); assertEquals("hello " + i, m.get(i));
} }
s.store();
s.close(); s.close();
s = BtreeMapStore.open(fileName); s = openStore(fileName);
m = s.openMap("data", Integer.class, String.class); m = s.openMap("data", Integer.class, String.class);
assertNull(m.get(0)); assertNull(m.get(0));
for (int i = 1; i < count; i++) { for (int i = 1; i < count; i++) {
...@@ -87,7 +118,7 @@ public class TestTreeMapStore extends TestBase { ...@@ -87,7 +118,7 @@ public class TestTreeMapStore extends TestBase {
FileUtils.delete(fileName); FileUtils.delete(fileName);
long initialLength = 0; long initialLength = 0;
for (int j = 0; j < 20; j++) { for (int j = 0; j < 20; j++) {
BtreeMapStore s = BtreeMapStore.open(fileName); BtreeMapStore s = openStore(fileName);
BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class); BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
m.put(j + i, "Hello " + j); m.put(j + i, "Hello " + j);
...@@ -103,9 +134,9 @@ public class TestTreeMapStore extends TestBase { ...@@ -103,9 +134,9 @@ public class TestTreeMapStore extends TestBase {
assertTrue("initial: " + initialLength + " len: " + len, len <= initialLength * 3); assertTrue("initial: " + initialLength + " len: " + len, len <= initialLength * 3);
} }
} }
long len = FileUtils.size(fileName); // long len = FileUtils.size(fileName);
// System.out.println("len0: " + len); // System.out.println("len0: " + len);
BtreeMapStore s = BtreeMapStore.open(fileName); BtreeMapStore s = openStore(fileName);
BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class); BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
m.remove(i); m.remove(i);
...@@ -113,13 +144,13 @@ public class TestTreeMapStore extends TestBase { ...@@ -113,13 +144,13 @@ public class TestTreeMapStore extends TestBase {
s.store(); s.store();
s.compact(); s.compact();
s.close(); s.close();
len = FileUtils.size(fileName); // len = FileUtils.size(fileName);
// System.out.println("len1: " + len); // System.out.println("len1: " + len);
s = BtreeMapStore.open(fileName); s = openStore(fileName);
m = s.openMap("data", Integer.class, String.class); m = s.openMap("data", Integer.class, String.class);
s.compact(); s.compact();
s.close(); s.close();
len = FileUtils.size(fileName); // len = FileUtils.size(fileName);
// System.out.println("len2: " + len); // System.out.println("len2: " + len);
} }
...@@ -127,14 +158,15 @@ public class TestTreeMapStore extends TestBase { ...@@ -127,14 +158,15 @@ public class TestTreeMapStore extends TestBase {
String fileName = getBaseDir() + "/testReuseSpace.h3"; String fileName = getBaseDir() + "/testReuseSpace.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
long initialLength = 0; long initialLength = 0;
for (int j = 0; j < 10; j++) { for (int j = 0; j < 20; j++) {
BtreeMapStore s = BtreeMapStore.open(fileName); BtreeMapStore s = openStore(fileName);
BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class); BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
m.put(i, "Hello"); m.put(i, "Hello");
} }
s.store(); s.store();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
assertEquals("Hello", m.get(i));
m.remove(i); m.remove(i);
} }
s.store(); s.store();
...@@ -151,23 +183,25 @@ public class TestTreeMapStore extends TestBase { ...@@ -151,23 +183,25 @@ public class TestTreeMapStore extends TestBase {
private void testRandom() { private void testRandom() {
String fileName = getBaseDir() + "/testRandom.h3"; String fileName = getBaseDir() + "/testRandom.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
BtreeMapStore s = BtreeMapStore.open(fileName); BtreeMapStore s = openStore(fileName);
BtreeMap<Integer, Integer> m = s.openMap("data", Integer.class, Integer.class); BtreeMap<Integer, Integer> m = s.openMap("data", Integer.class, Integer.class);
TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>(); TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
Random r = new Random(1); Random r = new Random(1);
int operationCount = 1000; int operationCount = 10000;
int maxValue = 20; int maxValue = 30;
for (int i = 0; i < operationCount; i++) { for (int i = 0; i < operationCount; i++) {
int k = r.nextInt(maxValue); int k = r.nextInt(maxValue);
int v = r.nextInt(); int v = r.nextInt();
boolean compareAll; boolean compareAll;
switch (r.nextInt(3)) { switch (r.nextInt(3)) {
case 0: case 0:
log(i + ": put " + k + " = " + v);
m.put(k, v); m.put(k, v);
map.put(k, v); map.put(k, v);
compareAll = true; compareAll = true;
break; break;
case 1: case 1:
log(i + ": remove " + k);
m.remove(k); m.remove(k);
map.remove(k); map.remove(k);
compareAll = true; compareAll = true;
...@@ -185,10 +219,12 @@ public class TestTreeMapStore extends TestBase { ...@@ -185,10 +219,12 @@ public class TestTreeMapStore extends TestBase {
} }
if (compareAll) { if (compareAll) {
Iterator<Integer> it = m.keyIterator(null); Iterator<Integer> it = m.keyIterator(null);
Iterator<Integer> it2 = map.keySet().iterator(); Iterator<Integer> itExpected = map.keySet().iterator();
while (it2.hasNext()) { while (itExpected.hasNext()) {
assertTrue(it.hasNext()); assertTrue(it.hasNext());
assertEquals(it2.next(), it.next()); Integer expected = itExpected.next();
Integer got = it.next();
assertEquals(expected, got);
} }
assertFalse(it.hasNext()); assertFalse(it.hasNext());
} }
...@@ -199,7 +235,7 @@ public class TestTreeMapStore extends TestBase { ...@@ -199,7 +235,7 @@ public class TestTreeMapStore extends TestBase {
private void testKeyValueClasses() { private void testKeyValueClasses() {
String fileName = getBaseDir() + "/testKeyValueClasses.h3"; String fileName = getBaseDir() + "/testKeyValueClasses.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
BtreeMapStore s = BtreeMapStore.open(fileName); BtreeMapStore s = openStore(fileName);
BtreeMap<Integer, String> is = s.openMap("intString", Integer.class, String.class); BtreeMap<Integer, String> is = s.openMap("intString", Integer.class, String.class);
is.put(1, "Hello"); is.put(1, "Hello");
BtreeMap<Integer, Integer> ii = s.openMap("intInt", Integer.class, Integer.class); BtreeMap<Integer, Integer> ii = s.openMap("intInt", Integer.class, Integer.class);
...@@ -220,8 +256,9 @@ public class TestTreeMapStore extends TestBase { ...@@ -220,8 +256,9 @@ public class TestTreeMapStore extends TestBase {
} catch (RuntimeException e) { } catch (RuntimeException e) {
// expected // expected
} }
s.store();
s.close(); s.close();
s = BtreeMapStore.open(fileName); s = openStore(fileName);
is = s.openMap("intString", Integer.class, String.class); is = s.openMap("intString", Integer.class, String.class);
assertEquals("Hello", is.get(1)); assertEquals("Hello", is.get(1));
ii = s.openMap("intInt", Integer.class, Integer.class); ii = s.openMap("intInt", Integer.class, Integer.class);
...@@ -236,7 +273,7 @@ public class TestTreeMapStore extends TestBase { ...@@ -236,7 +273,7 @@ public class TestTreeMapStore extends TestBase {
private void testIterate() { private void testIterate() {
String fileName = getBaseDir() + "/testIterate.h3"; String fileName = getBaseDir() + "/testIterate.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
BtreeMapStore s = BtreeMapStore.open(fileName); BtreeMapStore s = openStore(fileName);
BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class); BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
Iterator<Integer> it = m.keyIterator(null); Iterator<Integer> it = m.keyIterator(null);
assertFalse(it.hasNext()); assertFalse(it.hasNext());
...@@ -269,7 +306,7 @@ public class TestTreeMapStore extends TestBase { ...@@ -269,7 +306,7 @@ public class TestTreeMapStore extends TestBase {
private void testSimple() { private void testSimple() {
String fileName = getBaseDir() + "/testSimple.h3"; String fileName = getBaseDir() + "/testSimple.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
BtreeMapStore s = BtreeMapStore.open(fileName); BtreeMapStore s = openStore(fileName);
BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class); BtreeMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
m.put(i, "hello " + i); m.put(i, "hello " + i);
...@@ -281,10 +318,10 @@ public class TestTreeMapStore extends TestBase { ...@@ -281,10 +318,10 @@ public class TestTreeMapStore extends TestBase {
for (int i = 1; i < 3; i++) { for (int i = 1; i < 3; i++) {
assertEquals("hello " + i, m.get(i)); assertEquals("hello " + i, m.get(i));
} }
s.store();
s.close(); s.close();
s = BtreeMapStore.open(fileName); s = openStore(fileName);
m = s.openMap("data", Integer.class, String.class); m = s.openMap("data", Integer.class, String.class);
assertNull(m.get(0)); assertNull(m.get(0));
for (int i = 1; i < 3; i++) { for (int i = 1; i < 3; i++) {
...@@ -293,4 +330,19 @@ public class TestTreeMapStore extends TestBase { ...@@ -293,4 +330,19 @@ public class TestTreeMapStore extends TestBase {
s.close(); s.close();
} }
private static BtreeMapStore openStore(String fileName) {
BtreeMapStore store = BtreeMapStore.open(fileName, new TestTypeFactory());
store.setMaxPageSize(10);
return store;
}
/**
* Log the message.
*
* @param msg the message
*/
private static void log(String msg) {
// System.out.println(msg);
}
} }
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version
* 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html). Initial Developer: H2 Group
*/
package org.h2.test.store;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.DataTypeFactory;
import org.h2.dev.store.btree.StringType;
/**
* A data type factory.
*/
public class TestTypeFactory implements DataTypeFactory {
public DataType fromString(String s) {
if (s.length() == 0) {
return new StringType();
}
switch (s.charAt(0)) {
case 'i':
return new IntegerType();
case 'r':
return RowType.fromString(s, this);
}
throw new RuntimeException("Unknown data type " + s);
}
public DataType getDataType(Class<?> objectClass) {
if (objectClass == Integer.class) {
return new IntegerType();
}
throw new RuntimeException("Unsupported object class " + objectClass.toString());
}
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version 1.0,
and under the Eclipse Public License, Version 1.0
(http://h2database.com/html/license.html).
Initial Developer: H2 Group
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /><title>
Javadoc package documentation
</title></head><body style="font: 9pt/130% Tahoma, Arial, Helvetica, sans-serif; font-weight: normal;"><p>
This package contains tests for the map store.
</p></body></html>
\ No newline at end of file
...@@ -17,13 +17,15 @@ import java.util.Iterator; ...@@ -17,13 +17,15 @@ import java.util.Iterator;
public class BtreeMap<K, V> { public class BtreeMap<K, V> {
private final BtreeMapStore store; private final BtreeMapStore store;
private final long id;
private final String name; private final String name;
private final DataType keyType; private final DataType keyType;
private final DataType valueType; private final DataType valueType;
private Page root; private Page root;
private BtreeMap(BtreeMapStore store, String name, DataType keyType, DataType valueType) { private BtreeMap(BtreeMapStore store, long id, String name, DataType keyType, DataType valueType) {
this.store = store; this.store = store;
this.id = id;
this.name = name; this.name = name;
this.keyType = keyType; this.keyType = keyType;
this.valueType = valueType; this.valueType = valueType;
...@@ -40,8 +42,8 @@ public class BtreeMap<K, V> { ...@@ -40,8 +42,8 @@ public class BtreeMap<K, V> {
* @param valueClass the value class * @param valueClass the value class
* @return the map * @return the map
*/ */
static <K, V> BtreeMap<K, V> open(BtreeMapStore store, String name, DataType keyType, DataType valueType) { static <K, V> BtreeMap<K, V> open(BtreeMapStore store, long id, String name, DataType keyType, DataType valueType) {
return new BtreeMap<K, V>(store, name, keyType, valueType); return new BtreeMap<K, V>(store, id, name, keyType, valueType);
} }
/** /**
...@@ -51,9 +53,7 @@ public class BtreeMap<K, V> { ...@@ -51,9 +53,7 @@ public class BtreeMap<K, V> {
* @param data the value * @param data the value
*/ */
public void put(K key, V data) { public void put(K key, V data) {
if (!isChanged()) { markChanged();
store.markChanged(name, this);
}
root = Page.put(this, root, key, data); root = Page.put(this, root, key, data);
} }
...@@ -77,23 +77,32 @@ public class BtreeMap<K, V> { ...@@ -77,23 +77,32 @@ public class BtreeMap<K, V> {
* @param key the key * @param key the key
* @return the value, or null if not found * @return the value, or null if not found
*/ */
public Page getPage(K key) { Page getPage(K key) {
if (root == null) { if (root == null) {
return null; return null;
} }
return root.findPage(key); return root.findPage(key);
} }
/**
* Remove all entries.
*/
public void clear() {
if (root != null) {
markChanged();
root.removeAllRecursive();
root = null;
}
}
/** /**
* Remove a key-value pair. * Remove a key-value pair.
* *
* @param key the key * @param key the key
*/ */
public void remove(K key) { public void remove(K key) {
if (!isChanged()) {
store.markChanged(name, this);
}
if (root != null) { if (root != null) {
markChanged();
root = Page.remove(root, key); root = Page.remove(root, key);
} }
} }
...@@ -107,6 +116,12 @@ public class BtreeMap<K, V> { ...@@ -107,6 +116,12 @@ public class BtreeMap<K, V> {
return root != null && root.getId() < 0; return root != null && root.getId() < 0;
} }
private void markChanged() {
if (!isChanged()) {
store.markChanged(name, this);
}
}
/** /**
* Compare two keys. * Compare two keys.
* *
...@@ -206,4 +221,12 @@ public class BtreeMap<K, V> { ...@@ -206,4 +221,12 @@ public class BtreeMap<K, V> {
return name; return name;
} }
int getMaxPageSize() {
return store.getMaxPageSize();
}
long getId() {
return id;
}
} }
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
*/ */
package org.h2.dev.store.btree; package org.h2.dev.store.btree;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -17,7 +17,6 @@ import java.util.Comparator; ...@@ -17,7 +17,6 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Properties; import java.util.Properties;
import java.util.TreeMap;
import org.h2.dev.store.FilePathCache; import org.h2.dev.store.FilePathCache;
import org.h2.store.fs.FilePath; import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
...@@ -35,21 +34,21 @@ header ...@@ -35,21 +34,21 @@ header
header: header:
# H3 store # # H3 store #
pageSize=4096 blockSize=4096
r=1
chunk: chunk:
'd' [id] [metaRootPos] data ... 1 byte: 'c'
4 bytes: length
4 bytes: chunk id (an incrementing number)
4 bytes: metaRootPos (relative to the chunk start)
data ...
todo: todo:
- garbage collection - garbage collection
- use page checksums - use page checksums
- compress chunks - compress chunks
- possibly encode the length in pos (1=32, 2=128, 3=512,...)
- encode length in pos (1=32, 2=128, 3=512,...)
- floating header (avoid duplicate header) - floating header (avoid duplicate header)
for each chunk, store chunk (a counter) for each chunk, store chunk (a counter)
...@@ -66,27 +65,35 @@ public class BtreeMapStore { ...@@ -66,27 +65,35 @@ public class BtreeMapStore {
private static final StringType STRING_TYPE = new StringType(); private static final StringType STRING_TYPE = new StringType();
private final String fileName; private final String fileName;
private final DataTypeFactory typeFactory;
private FileChannel file; private FileChannel file;
private int pageSize = 4 * 1024; private int blockSize = 4 * 1024;
private long rootBlockStart; private long rootChunkPos;
private HashMap<Long, Page> cache = SmallLRUCache.newInstance(50000); private HashMap<Long, Page> cache = SmallLRUCache.newInstance(5000);
private ArrayList<Page> temp = New.arrayList(); private HashMap<Long, Page> temp = New.hashMap();
private TreeMap<Integer, Block> blocks = new TreeMap<Integer, Block>(); private HashMap<Integer, Chunk> chunks = New.hashMap();
private BtreeMap<String, String> meta;
private HashMap<String, BtreeMap<?, ?>> maps = New.hashMap(); private HashMap<String, BtreeMap<?, ?>> maps = New.hashMap();
private HashMap<String, BtreeMap<?, ?>> mapsChanged = New.hashMap(); private HashMap<String, BtreeMap<?, ?>> mapsChanged = New.hashMap();
private BtreeMap<String, String> meta;
// TODO use an int instead? (with rollover to 0) // TODO use an int instead? (with rollover to 0)
private long transaction; private long transaction;
private long tempPageId;
private int lastChunkId;
// TODO use bit set, and integer instead of long
private long lastMapId;
private int lastBlockId; private int maxPageSize = 30;
// TODO support quota (per map, per storage) // TODO support reading metadata to support quota (per map, per storage)
// TODO support r-tree // TODO support r-tree
// TODO support triggers and events (possibly on a different layer)
private BtreeMapStore(String fileName) { private BtreeMapStore(String fileName, DataTypeFactory typeFactory) {
this.fileName = fileName; this.fileName = fileName;
this.typeFactory = typeFactory;
} }
/** /**
...@@ -96,7 +103,18 @@ public class BtreeMapStore { ...@@ -96,7 +103,18 @@ public class BtreeMapStore {
* @return the store * @return the store
*/ */
public static BtreeMapStore open(String fileName) { public static BtreeMapStore open(String fileName) {
BtreeMapStore s = new BtreeMapStore(fileName); return open(fileName, null);
}
/**
* Open a tree store.
*
* @param fileName the file name
* @param typeFactory the type factory
* @return the store
*/
public static BtreeMapStore open(String fileName, DataTypeFactory typeFactory) {
BtreeMapStore s = new BtreeMapStore(fileName, typeFactory);
s.open(); s.open();
return s; return s;
} }
...@@ -107,43 +125,74 @@ public class BtreeMapStore { ...@@ -107,43 +125,74 @@ public class BtreeMapStore {
* @param <K> the key type * @param <K> the key type
* @param <V> the value type * @param <V> the value type
* @param name the name of the map * @param name the name of the map
* @param keyClass the key class * @param keyType the key type
* @param valueClass the value class * @param valueType the value type
* @return the map * @return the map
*/ */
public <K, V> BtreeMap<K, V> openMap(String name, Class<K> keyClass, Class<V> valueClass) { public <K, V> BtreeMap<K, V> openMap(String name, DataType keyType, DataType valueType) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
BtreeMap<K, V> m = (BtreeMap<K, V>) maps.get(name); BtreeMap<K, V> m = (BtreeMap<K, V>) maps.get(name);
if (m == null) { if (m == null) {
String root = meta.get("root." + name); String identifier = meta.get("map." + name);
DataType keyType = getDataType(keyClass); long id;
DataType valueType = getDataType(valueClass); String root;
m = BtreeMap.open(this, name, keyType, valueType); if (identifier == null) {
maps.put(name, m); id = ++lastMapId;
if (root == null) { String types = id + "/" + keyType.asString() + "/" + valueType.asString();
String info = m.getKeyType().asString() + "/" + m.getValueType().asString(); meta.put("map." + name, types);
meta.put("map." + name, info); root = null;
} else { } else {
if (!root.equals("0")) { String types = meta.get("map." + name);
String[] idTypeList = StringUtils.arraySplit(types, '/', false);
id = Long.parseLong(idTypeList[0]);
keyType = getDataType(idTypeList[1]);
valueType = getDataType(idTypeList[2]);
root = meta.get("root." + id);
}
m = BtreeMap.open(this, id, name, keyType, valueType);
maps.put(name, m);
if (root != null && !"0".equals(root)) {
m.setRoot(Long.parseLong(root)); m.setRoot(Long.parseLong(root));
} }
} }
}
return m; return m;
} }
/**
* Open a map.
*
* @param <K> the key type
* @param <V> the value type
* @param name the name of the map
* @param keyClass the key class
* @param valueClass the value class
* @return the map
*/
public <K, V> BtreeMap<K, V> openMap(String name, Class<K> keyClass, Class<V> valueClass) {
DataType keyType = getDataType(keyClass);
DataType valueType = getDataType(valueClass);
return openMap(name, keyType, valueType);
}
private DataType getDataType(Class<?> clazz) { private DataType getDataType(Class<?> clazz) {
if (clazz == String.class) { if (clazz == String.class) {
return STRING_TYPE; return STRING_TYPE;
} }
return DataTypeFactory.getDataType(clazz); return getTypeFactory().getDataType(clazz);
} }
private DataType getDataType(String s) { private DataType getDataType(String s) {
if (s.equals("")) { if (s.equals("")) {
return STRING_TYPE; return STRING_TYPE;
} }
return DataTypeFactory.fromString(s); return getTypeFactory().fromString(s);
}
private DataTypeFactory getTypeFactory() {
if (typeFactory == null) {
throw new RuntimeException("No data type factory set");
}
return typeFactory;
} }
/** /**
...@@ -159,7 +208,7 @@ public class BtreeMapStore { ...@@ -159,7 +208,7 @@ public class BtreeMapStore {
} }
private void open() { private void open() {
meta = BtreeMap.open(this, "meta", STRING_TYPE, STRING_TYPE); meta = BtreeMap.open(this, 0, "meta", STRING_TYPE, STRING_TYPE);
FileUtils.createDirectories(FileUtils.getParent(fileName)); FileUtils.createDirectories(FileUtils.getParent(fileName));
try { try {
log("file open"); log("file open");
...@@ -176,21 +225,24 @@ public class BtreeMapStore { ...@@ -176,21 +225,24 @@ public class BtreeMapStore {
} }
private void readMeta() { private void readMeta() {
long rootId = readMetaRootId(rootBlockStart); Chunk header = readChunkHeader(rootChunkPos);
lastBlockId = getBlockId(rootId); lastChunkId = header.id;
Block b = new Block(lastBlockId); chunks.put(header.id, header);
b.start = rootBlockStart; meta.setRoot(getId(header.id, header.metaRootOffset));
blocks.put(b.id, b); Iterator<String> it = meta.keyIterator("chunk.");
meta.setRoot(rootId);
Iterator<String> it = meta.keyIterator("block.");
while (it.hasNext()) { while (it.hasNext()) {
String s = it.next(); String s = it.next();
if (!s.startsWith("block.")) { if (!s.startsWith("chunk.")) {
break; break;
} }
b = Block.fromString(meta.get(s)); Chunk c = Chunk.fromString(meta.get(s));
lastBlockId = Math.max(b.id, lastBlockId); if (c.id == header.id) {
blocks.put(b.id, b); c.start = header.start;
c.length = header.length;
c.metaRootOffset = header.metaRootOffset;
}
lastChunkId = Math.max(c.id, lastChunkId);
chunks.put(c.id, c);
} }
} }
...@@ -198,13 +250,15 @@ public class BtreeMapStore { ...@@ -198,13 +250,15 @@ public class BtreeMapStore {
try { try {
ByteBuffer header = ByteBuffer.wrap(( ByteBuffer header = ByteBuffer.wrap((
"# H2 1.5\n" + "# H2 1.5\n" +
"read-version: 1\n" + "versionRead:1\n" +
"write-version: 1\n" + "versionWrite:1\n" +
"rootBlock: " + rootBlockStart + "\n" + "blockSize:" + blockSize + "\n" +
"transaction: " + transaction + "\n").getBytes()); "rootChunk:" + rootChunkPos + "\n" +
"lastMapId:" + lastMapId + "\n" +
"transaction:" + transaction + "\n").getBytes("UTF-8"));
file.position(0); file.position(0);
file.write(header); file.write(header);
file.position(pageSize); file.position(blockSize);
file.write(header); file.write(header);
} catch (Exception e) { } catch (Exception e) {
throw convert(e); throw convert(e);
...@@ -214,13 +268,14 @@ public class BtreeMapStore { ...@@ -214,13 +268,14 @@ public class BtreeMapStore {
private void readHeader() { private void readHeader() {
try { try {
file.position(0); file.position(0);
byte[] header = new byte[pageSize]; byte[] header = new byte[blockSize];
// TODO read fully; read both headers // TODO read fully; read both headers
file.read(ByteBuffer.wrap(header)); file.read(ByteBuffer.wrap(header));
Properties prop = new Properties(); Properties prop = new Properties();
prop.load(new ByteArrayInputStream(header)); prop.load(new StringReader(new String(header, "UTF-8")));
rootBlockStart = Long.parseLong(prop.get("rootBlock").toString()); rootChunkPos = Long.parseLong(prop.get("rootChunk").toString());
transaction = Long.parseLong(prop.get("transaction").toString()); transaction = Long.parseLong(prop.get("transaction").toString());
lastMapId = Long.parseLong(prop.get("lastMapId").toString());
} catch (Exception e) { } catch (Exception e) {
throw convert(e); throw convert(e);
} }
...@@ -234,7 +289,6 @@ public class BtreeMapStore { ...@@ -234,7 +289,6 @@ public class BtreeMapStore {
* Close the file. Uncommitted changes are ignored. * Close the file. Uncommitted changes are ignored.
*/ */
public void close() { public void close() {
store();
if (file != null) { if (file != null) {
try { try {
log("file close"); log("file close");
...@@ -246,18 +300,18 @@ public class BtreeMapStore { ...@@ -246,18 +300,18 @@ public class BtreeMapStore {
} }
} }
private long getPosition(long pageId) { private long getPosition(long posId) {
Block b = getBlock(pageId); Chunk c = getChunk(posId);
if (b == null) { if (c == null) {
throw new RuntimeException("Block " + getBlockId(pageId) + " not found"); throw new RuntimeException("Chunk " + getChunkId(posId) + " not found");
} }
long pos = b.start; long pos = c.start;
pos += (int) (pageId & Integer.MAX_VALUE); pos += (int) (posId & Integer.MAX_VALUE);
return pos; return pos;
} }
private static long getId(int blockId, int offset) { private static long getId(int chunkId, int offset) {
return ((long) blockId << 32) | offset; return ((long) chunkId << 32) | offset;
} }
/** /**
...@@ -272,95 +326,74 @@ public class BtreeMapStore { ...@@ -272,95 +326,74 @@ public class BtreeMapStore {
} }
long trans = commit(); long trans = commit();
// the length estimate is not correct, int chunkId = ++lastChunkId;
// as we don't know the exact positions and entry counts
int lenEstimate = 1 + 8; Chunk c = new Chunk(chunkId);
for (BtreeMap<?, ?> m : mapsChanged.values()) { c.entryCount = Integer.MAX_VALUE;
meta.put("root." + m.getName(), String.valueOf(Long.MAX_VALUE)); c.liveCount = Integer.MAX_VALUE;
Page p = m.getRoot(); c.start = Long.MAX_VALUE;
if (p != null) { c.length = Long.MAX_VALUE;
lenEstimate += p.lengthIncludingTempChildren(); chunks.put(c.id, c);
} meta.put("chunk." + c.id, c.toString());
} ArrayList<Integer> removedChunks = New.arrayList();
int blockId = ++lastBlockId; for (Chunk x : chunks.values()) {
Block b = new Block(blockId);
b.start = Long.MAX_VALUE;
b.entryCount = Integer.MAX_VALUE;
b.liveCount = Integer.MAX_VALUE;
blocks.put(b.id, b);
for (Block x : blocks.values()) {
if (x.liveCount == 0) { if (x.liveCount == 0) {
meta.remove("block." + x.id); meta.remove("chunk." + x.id);
removedChunks.add(x.id);
} else { } else {
meta.put("block." + x.id, "temp-" + x.toString()); meta.put("chunk." + x.id, x.toString());
}
} }
// modifying the meta can itself affect the metadata
// TODO solve this in a better way
ArrayList<Integer> removedBlocks = New.arrayList();
for (Block x : new ArrayList<Block>(blocks.values())) {
if (x.liveCount == 0) {
meta.remove("block." + x.id);
removedBlocks.add(x.id);
} else {
meta.put("block." + x.id, x.toString());
}
}
lenEstimate += meta.getRoot().lengthIncludingTempChildren();
b.length = lenEstimate;
blocks.remove(b.id);
long storePos = allocateBlock(lenEstimate);
blocks.put(b.id, b);
for (int id : removedBlocks) {
blocks.remove(id);
}
long pageId = getId(blockId, 1 + 8);
for (BtreeMap<?, ?> m : mapsChanged.values()) {
Page r = m.getRoot();
long p = r == null ? 0 : pageId;
meta.put("root." + m.getName(), "" + p);
if (r != null) {
pageId = r.updatePageIds(pageId);
} }
for (int x : removedChunks) {
chunks.remove(x);
} }
int metaRootOffset = (int) (pageId - getId(blockId, 0));
// add a dummy entry so the count is correct
meta.put("block." + b.id, b.toString());
int count = 0; int count = 0;
int maxLength = 1 + 4 + 4 + 4;
for (BtreeMap<?, ?> m : mapsChanged.values()) { for (BtreeMap<?, ?> m : mapsChanged.values()) {
Page p = m.getRoot(); Page p = m.getRoot();
if (p != null) { if (p != null) {
count += p.countTemp(); maxLength += p.getMaxLengthTempRecursive();
count += p.countTempRecursive();
meta.put("root." + m.getId(), String.valueOf(Long.MAX_VALUE));
} else {
meta.put("root." + m.getId(), "0");
} }
} }
count += meta.getRoot().countTemp(); maxLength += meta.getRoot().getMaxLengthTempRecursive();
count += meta.getRoot().countTempRecursive();
b.start = storePos;
b.entryCount = count;
b.liveCount = b.entryCount;
meta.put("block." + b.id, b.toString());
pageId = meta.getRoot().updatePageIds(pageId); ByteBuffer buff = ByteBuffer.allocate(maxLength);
int len = (int) (pageId - getId(blockId, 0)); // need to patch the header later
buff.put((byte) 'c');
ByteBuffer buff = ByteBuffer.allocate(len); buff.putInt(0);
buff.put((byte) 'd'); buff.putInt(0);
buff.putInt(b.id); buff.putInt(0);
buff.putInt(metaRootOffset); long idOffset = getId(chunkId, 0);
for (BtreeMap<?, ?> m : mapsChanged.values()) { for (BtreeMap<?, ?> m : mapsChanged.values()) {
Page p = m.getRoot(); Page p = m.getRoot();
if (p != null) { if (p != null) {
p.storeTemp(buff); long root = p.writeTempRecursive(buff, idOffset);
} meta.put("root." + m.getId(), "" + root);
} }
meta.getRoot().storeTemp(buff);
if (buff.hasRemaining()) {
throw new RuntimeException("remaining: " + buff.remaining());
} }
// fix metadata
c.entryCount = count;
c.liveCount = count;
meta.put("chunk." + c.id, c.toString());
meta.getRoot().writeTempRecursive(buff, idOffset);
buff.flip();
int length = buff.limit();
long storePos = allocateChunk(length);
int rootOffset = (int) (meta.getRoot().getId() - idOffset);
buff.rewind();
buff.put((byte) 'c');
buff.putInt(length);
buff.putInt(chunkId);
buff.putInt(rootOffset);
buff.rewind(); buff.rewind();
try { try {
file.position(storePos); file.position(storePos);
...@@ -368,26 +401,33 @@ public class BtreeMapStore { ...@@ -368,26 +401,33 @@ public class BtreeMapStore {
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
rootBlockStart = storePos; rootChunkPos = storePos;
writeHeader(); writeHeader();
mapsChanged.clear(); mapsChanged.clear();
temp.clear(); temp.clear();
tempPageId = 0;
// update the start position and length
c.start = storePos;
c.length = length;
meta.put("chunk." + c.id, c.toString());
return trans; return trans;
} }
private long allocateBlock(long length) { private long allocateChunk(long length) {
BitSet set = new BitSet(); BitSet set = new BitSet();
set.set(0); set.set(0);
set.set(1); set.set(1);
for (Block b : blocks.values()) { for (Chunk c : chunks.values()) {
if (b.start == Long.MAX_VALUE) { if (c.start == Long.MAX_VALUE) {
continue; continue;
} }
int first = (int) (b.start / pageSize); int first = (int) (c.start / blockSize);
int last = (int) ((b.start + b.length) / pageSize); int last = (int) ((c.start + c.length) / blockSize);
set.set(first, last +1); set.set(first, last +1);
} }
int required = (int) (length / pageSize) + 1; int required = (int) (length / blockSize) + 1;
for (int i = 0; i < set.size(); i++) { for (int i = 0; i < set.size(); i++) {
if (!set.get(i)) { if (!set.get(i)) {
boolean ok = true; boolean ok = true;
...@@ -398,11 +438,11 @@ public class BtreeMapStore { ...@@ -398,11 +438,11 @@ public class BtreeMapStore {
} }
} }
if (ok) { if (ok) {
return i * pageSize; return i * blockSize;
} }
} }
} }
return set.size() * pageSize; return set.size() * blockSize;
} }
/** /**
...@@ -421,8 +461,9 @@ public class BtreeMapStore { ...@@ -421,8 +461,9 @@ public class BtreeMapStore {
* @return the page id * @return the page id
*/ */
long registerTempPage(Page p) { long registerTempPage(Page p) {
temp.add(p); long id = --tempPageId;
return -temp.size(); temp.put(id, p);
return id;
} }
/** /**
...@@ -434,37 +475,42 @@ public class BtreeMapStore { ...@@ -434,37 +475,42 @@ public class BtreeMapStore {
return ++transaction; return ++transaction;
} }
private long readMetaRootId(long blockStart) { private Chunk readChunkHeader(long pos) {
try { try {
file.position(blockStart); file.position(pos);
ByteBuffer buff = ByteBuffer.wrap(new byte[16]); ByteBuffer buff = ByteBuffer.wrap(new byte[16]);
file.read(buff); file.read(buff);
buff.rewind(); buff.rewind();
if (buff.get() != 'd') { if (buff.get() != 'c') {
throw new RuntimeException("File corrupt"); throw new RuntimeException("File corrupt");
} }
int blockId = buff.getInt(); int length = buff.getInt();
int chunkId = buff.getInt();
int offset = buff.getInt(); int offset = buff.getInt();
return getId(blockId, offset); Chunk c = new Chunk(chunkId);
c.start = pos;
c.length = length;
c.metaRootOffset = offset;
return c;
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
/** /**
* Try to reduce the file size. Blocks with a low number of live items will * Try to reduce the file size. Chunks with a low number of live items will
* be re-written. * be re-written.
*/ */
public void compact() { public void compact() {
if (blocks.size() <= 1) { if (chunks.size() <= 1) {
return; return;
} }
long liveCountTotal = 0, entryCountTotal = 0; long liveCountTotal = 0, entryCountTotal = 0;
for (Block b : blocks.values()) { for (Chunk c : chunks.values()) {
entryCountTotal += b.entryCount; entryCountTotal += c.entryCount;
liveCountTotal += b.liveCount; liveCountTotal += c.liveCount;
} }
int averageEntryCount = (int) (entryCountTotal / blocks.size()); int averageEntryCount = (int) (entryCountTotal / chunks.size());
if (entryCountTotal == 0) { if (entryCountTotal == 0) {
return; return;
} }
...@@ -472,52 +518,51 @@ public class BtreeMapStore { ...@@ -472,52 +518,51 @@ public class BtreeMapStore {
if (percentTotal > 80) { if (percentTotal > 80) {
return; return;
} }
ArrayList<Block> old = New.arrayList(); ArrayList<Chunk> old = New.arrayList();
for (Block b : blocks.values()) { for (Chunk c : chunks.values()) {
int age = lastBlockId - b.id + 1; int age = lastChunkId - c.id + 1;
b.collectPriority = b.getFillRate() / age; c.collectPriority = c.getFillRate() / age;
old.add(b); old.add(c);
} }
Collections.sort(old, new Comparator<Block>() { Collections.sort(old, new Comparator<Chunk>() {
public int compare(Block o1, Block o2) { public int compare(Chunk o1, Chunk o2) {
return new Integer(o1.collectPriority).compareTo(o2.collectPriority); return new Integer(o1.collectPriority).compareTo(o2.collectPriority);
} }
}); });
int moveCount = 0; int moveCount = 0;
Block move = null; Chunk move = null;
for (Block b : old) { for (Chunk c : old) {
if (moveCount + b.liveCount > averageEntryCount) { if (moveCount + c.liveCount > averageEntryCount) {
break; break;
} }
log(" block " + b.id + " " + b.getFillRate() + "% full; prio=" + b.collectPriority); log(" chunk " + c.id + " " + c.getFillRate() + "% full; prio=" + c.collectPriority);
moveCount += b.liveCount; moveCount += c.liveCount;
move = b; move = c;
} }
boolean remove = false; boolean remove = false;
for (Iterator<Block> it = old.iterator(); it.hasNext();) { for (Iterator<Chunk> it = old.iterator(); it.hasNext();) {
Block b = it.next(); Chunk c = it.next();
if (move == b) { if (move == c) {
remove = true; remove = true;
} else if (remove) { } else if (remove) {
it.remove(); it.remove();
} }
} }
long oldMetaRootId = readMetaRootId(move.start); Chunk header = readChunkHeader(move.start);
long offset = getPosition(oldMetaRootId); log(" meta:" + move.id + "/" + header.metaRootOffset + " start: " + move.start);
log(" meta:" + move.id + "/" + offset + " start: " + move.start); BtreeMap<String, String> oldMeta = BtreeMap.open(this, 0, "old-meta", STRING_TYPE, STRING_TYPE);
BtreeMap<String, String> oldMeta = BtreeMap.open(this, "old-meta", STRING_TYPE, STRING_TYPE); oldMeta.setRoot(getId(header.id, header.metaRootOffset));
oldMeta.setRoot(oldMetaRootId);
Iterator<String> it = oldMeta.keyIterator(null); Iterator<String> it = oldMeta.keyIterator(null);
ArrayList<Integer> oldBlocks = New.arrayList(); ArrayList<Integer> oldChunks = New.arrayList();
while (it.hasNext()) { while (it.hasNext()) {
String k = it.next(); String k = it.next();
String s = oldMeta.get(k); String s = oldMeta.get(k);
log(" " + k + " " + s.replace('\n', ' ')); log(" " + k + " " + s.replace('\n', ' '));
if (k.startsWith("block.")) { if (k.startsWith("chunk.")) {
Block b = Block.fromString(s); Chunk c = Chunk.fromString(s);
if (!blocks.containsKey(b.id)) { if (!chunks.containsKey(c.id)) {
oldBlocks.add(b.id); oldChunks.add(c.id);
blocks.put(b.id, b); chunks.put(c.id, c);
} }
continue; continue;
} }
...@@ -528,11 +573,12 @@ public class BtreeMapStore { ...@@ -528,11 +573,12 @@ public class BtreeMapStore {
if (!maps.containsKey(k)) { if (!maps.containsKey(k)) {
continue; continue;
} }
String[] types = StringUtils.arraySplit(s, '/', false); String[] idTypesList = StringUtils.arraySplit(s, '/', false);
DataType kt = getDataType(types[0]); long id = Long.parseLong(idTypesList[0]);
DataType vt = getDataType(types[1]); DataType kt = getDataType(idTypesList[1]);
long oldDataRoot = Long.parseLong(oldMeta.get("root." + k)); DataType vt = getDataType(idTypesList[2]);
BtreeMap<?, ?> oldData = BtreeMap.open(this, "old-" + k, kt, vt); long oldDataRoot = Long.parseLong(oldMeta.get("root." + id));
BtreeMap<?, ?> oldData = BtreeMap.open(this, id, "old-" + k, kt, vt);
if (oldDataRoot == 0) { if (oldDataRoot == 0) {
// no rows // no rows
} else { } else {
...@@ -549,9 +595,9 @@ public class BtreeMapStore { ...@@ -549,9 +595,9 @@ public class BtreeMapStore {
// temporarily changed - ok // temporarily changed - ok
// TODO move old data if changed temporarily? // TODO move old data if changed temporarily?
} else { } else {
Block b = getBlock(p.getId()); Chunk c = getChunk(p.getId());
if (old.contains(b)) { if (old.contains(c)) {
log(" move key:" + o + " block:" + b.id); log(" move key:" + o + " chunk:" + c.id);
Object value = data.get(o); Object value = data.get(o);
data.remove(o); data.remove(o);
data.put(o, value); data.put(o, value);
...@@ -560,8 +606,8 @@ public class BtreeMapStore { ...@@ -560,8 +606,8 @@ public class BtreeMapStore {
} }
} }
} }
for (int o : oldBlocks) { for (int o : oldChunks) {
blocks.remove(o); chunks.remove(o);
} }
} }
...@@ -574,7 +620,7 @@ public class BtreeMapStore { ...@@ -574,7 +620,7 @@ public class BtreeMapStore {
*/ */
Page readPage(BtreeMap<?, ?> map, long id) { Page readPage(BtreeMap<?, ?> map, long id) {
if (id < 0) { if (id < 0) {
return temp.get((int) (-id - 1)); return temp.get(id);
} }
Page p = cache.get(id); Page p = cache.get(id);
if (p == null) { if (p == null) {
...@@ -606,19 +652,25 @@ public class BtreeMapStore { ...@@ -606,19 +652,25 @@ public class BtreeMapStore {
*/ */
void removePage(long id) { void removePage(long id) {
if (id > 0) { if (id > 0) {
if (getBlock(id).liveCount == 0) { cache.remove(id);
if (getChunk(id).liveCount == 0) {
throw new RuntimeException("Negative live count: " + id); throw new RuntimeException("Negative live count: " + id);
} }
getBlock(id).liveCount--; getChunk(id).liveCount--;
} else {
temp.remove(id);
if (temp.size() == 0) {
tempPageId = 0;
}
} }
} }
private static int getBlockId(long pageId) { private static int getChunkId(long pos) {
return (int) (pageId >>> 32); return (int) (pos >>> 32);
} }
private Block getBlock(long pageId) { private Chunk getChunk(long pos) {
return blocks.get(getBlockId(pageId)); return chunks.get(getChunkId(pos));
} }
/** /**
...@@ -631,4 +683,12 @@ public class BtreeMapStore { ...@@ -631,4 +683,12 @@ public class BtreeMapStore {
// System.out.println(string); // System.out.println(string);
} }
public void setMaxPageSize(int maxPageSize) {
this.maxPageSize = maxPageSize;
}
int getMaxPageSize() {
return maxPageSize;
}
} }
...@@ -11,12 +11,12 @@ import java.io.IOException; ...@@ -11,12 +11,12 @@ import java.io.IOException;
import java.util.Properties; import java.util.Properties;
/** /**
* A block of data. * A chunk of data, containing one or multiple pages
*/ */
class Block { class Chunk {
/** /**
* The block id. * The chunk id.
*/ */
int id; int id;
...@@ -26,7 +26,7 @@ class Block { ...@@ -26,7 +26,7 @@ class Block {
long start; long start;
/** /**
* The length in bytes. * The length in bytes (may be larger than the actual value).
*/ */
long length; long length;
...@@ -45,7 +45,12 @@ class Block { ...@@ -45,7 +45,12 @@ class Block {
*/ */
int collectPriority; int collectPriority;
Block(int id) { /**
* The offset of the meta root.
*/
int metaRootOffset;
Chunk(int id) {
this.id = id; this.id = id;
} }
...@@ -55,17 +60,18 @@ class Block { ...@@ -55,17 +60,18 @@ class Block {
* @param s the string * @param s the string
* @return the block * @return the block
*/ */
static Block fromString(String s) { static Chunk fromString(String s) {
Block b = new Block(0); Chunk c = new Chunk(0);
Properties prop = new Properties(); Properties prop = new Properties();
try { try {
prop.load(new ByteArrayInputStream(s.getBytes("UTF-8"))); prop.load(new ByteArrayInputStream(s.getBytes("UTF-8")));
b.id = Integer.parseInt(prop.get("id").toString()); c.id = Integer.parseInt(prop.get("id").toString());
b.start = Long.parseLong(prop.get("start").toString()); c.start = Long.parseLong(prop.get("start").toString());
b.length = Long.parseLong(prop.get("length").toString()); c.length = Long.parseLong(prop.get("length").toString());
b.entryCount = Integer.parseInt(prop.get("entryCount").toString()); c.entryCount = Integer.parseInt(prop.get("entryCount").toString());
b.liveCount = Integer.parseInt(prop.get("liveCount").toString()); c.liveCount = Integer.parseInt(prop.get("liveCount").toString());
return b; c.metaRootOffset = Integer.parseInt(prop.get("metaRoot").toString());
return c;
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
...@@ -80,7 +86,7 @@ class Block { ...@@ -80,7 +86,7 @@ class Block {
} }
public boolean equals(Object o) { public boolean equals(Object o) {
return o instanceof Block && ((Block) o).id == id; return o instanceof Chunk && ((Chunk) o).id == id;
} }
public String toString() { public String toString() {
...@@ -89,7 +95,8 @@ class Block { ...@@ -89,7 +95,8 @@ class Block {
"start:" + start + "\n" + "start:" + start + "\n" +
"length:" + length + "\n" + "length:" + length + "\n" +
"entryCount:" + entryCount + "\n" + "entryCount:" + entryCount + "\n" +
"liveCount:" + liveCount + "\n"; "liveCount:" + liveCount + "\n" +
"metaRoot:" + metaRootOffset + "\n";
} }
} }
......
...@@ -11,8 +11,11 @@ import java.util.Iterator; ...@@ -11,8 +11,11 @@ import java.util.Iterator;
/** /**
* A cursor to iterate over elements in ascending order. * A cursor to iterate over elements in ascending order.
*
* @param <K> the key type
*/ */
class Cursor<K> implements Iterator<K> { class Cursor<K> implements Iterator<K> {
private ArrayList<CursorPos> parents = new ArrayList<CursorPos>(); private ArrayList<CursorPos> parents = new ArrayList<CursorPos>();
private K current; private K current;
......
...@@ -23,20 +23,37 @@ public interface DataType { ...@@ -23,20 +23,37 @@ public interface DataType {
int compare(Object a, Object b); int compare(Object a, Object b);
/** /**
* Get the length in bytes. * Get the length in bytes used to store an object.
* *
* @param obj the object * @param obj the object
* @return the length * @return the length
*/ */
int length(Object obj); int length(Object obj);
/**
* Get the maximum length in bytes used to store an object. In many cases,
* this method can be faster than calculating the exact length.
*
* @param obj the object
* @return the maximum length
*/
int getMaxLength(Object obj);
/**
* Estimate the used memory in bytes.
*
* @param obj the object
* @return the used memory
*/
int getMemory(Object obj);
/** /**
* Write the object. * Write the object.
* *
* @param buff the target buffer * @param buff the target buffer
* @param x the value * @param x the value
*/ */
void write(ByteBuffer buff, Object x); void write(ByteBuffer buff, Object obj);
/** /**
* Read an object. * Read an object.
......
...@@ -6,34 +6,18 @@ ...@@ -6,34 +6,18 @@
*/ */
package org.h2.dev.store.btree; package org.h2.dev.store.btree;
import java.io.IOException;
import java.io.StringReader;
/** /**
* A factory for data types. * A factory for data types.
*/ */
public class DataTypeFactory { public interface DataTypeFactory {
/** /**
* Read the data type. * Read the data type.
* *
* @param buff the buffer * @param s the string
* @return the type * @return the type
*/ */
static DataType fromString(String s) { DataType fromString(String s);
StringReader r = new StringReader(s);
char c;
try {
c = (char) r.read();
} catch (IOException e) {
throw new RuntimeException(e);
}
switch (c) {
case 'i':
return new IntegerType();
}
throw new RuntimeException("Unknown data type " + c);
}
/** /**
* Get the data type object for the given class. * Get the data type object for the given class.
...@@ -41,13 +25,6 @@ public class DataTypeFactory { ...@@ -41,13 +25,6 @@ public class DataTypeFactory {
* @param objectClass the class * @param objectClass the class
* @return the data type object * @return the data type object
*/ */
static DataType getDataType(Class<?> objectClass) { DataType getDataType(Class<?> objectClass);
if (objectClass == Integer.class) {
return new IntegerType();
} else if (objectClass == String.class) {
return new StringType();
}
throw new RuntimeException("Unsupported object class " + objectClass.toString());
}
} }
...@@ -13,13 +13,18 @@ import java.nio.ByteBuffer; ...@@ -13,13 +13,18 @@ import java.nio.ByteBuffer;
*/ */
public class DataUtils { public class DataUtils {
/**
* The maximum length of a variable size int.
*/
public static final int MAX_VAR_INT_LEN = 5;
/** /**
* Get the length of the variable size int. * Get the length of the variable size int.
* *
* @param x the value * @param x the value
* @return the length in bytes * @return the length in bytes
*/ */
static int getVarIntLen(int x) { public static int getVarIntLen(int x) {
if ((x & (-1 << 7)) == 0) { if ((x & (-1 << 7)) == 0) {
return 1; return 1;
} else if ((x & (-1 << 14)) == 0) { } else if ((x & (-1 << 14)) == 0) {
...@@ -55,7 +60,7 @@ public class DataUtils { ...@@ -55,7 +60,7 @@ public class DataUtils {
* @param buff the source buffer * @param buff the source buffer
* @return the value * @return the value
*/ */
static int readVarInt(ByteBuffer buff) { public static int readVarInt(ByteBuffer buff) {
int b = buff.get(); int b = buff.get();
if (b >= 0) { if (b >= 0) {
return b; return b;
...@@ -111,7 +116,7 @@ public class DataUtils { ...@@ -111,7 +116,7 @@ public class DataUtils {
* @param buff the target buffer * @param buff the target buffer
* @param x the value * @param x the value
*/ */
static void writeVarInt(ByteBuffer buff, int x) { public static void writeVarInt(ByteBuffer buff, int x) {
while ((x & ~0x7f) != 0) { while ((x & ~0x7f) != 0) {
buff.put((byte) (0x80 | (x & 0x7f))); buff.put((byte) (0x80 | (x & 0x7f)));
x >>>= 7; x >>>= 7;
...@@ -133,4 +138,22 @@ public class DataUtils { ...@@ -133,4 +138,22 @@ public class DataUtils {
buff.put((byte) x); buff.put((byte) x);
} }
static void copyWithGap(Object src, Object dst, int oldSize, int gapIndex) {
if (gapIndex > 0) {
System.arraycopy(src, 0, dst, 0, gapIndex);
}
if (gapIndex < oldSize) {
System.arraycopy(src, gapIndex, dst, gapIndex + 1, oldSize - gapIndex);
}
}
static void copyExcept(Object src, Object dst, int oldSize, int removeIndex) {
if (removeIndex > 0 && oldSize > 0) {
System.arraycopy(src, 0, dst, 0, removeIndex);
}
if (removeIndex < oldSize) {
System.arraycopy(src, removeIndex + 1, dst, removeIndex, oldSize - removeIndex - 1);
}
}
} }
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.dev.store.btree;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.Properties;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils;
/**
* Convert a database file to a human-readable text dump.
*/
public class Dump {
private static int blockSize = 4 * 1024;
public static void main(String... args) {
String fileName = "test.h3";
for (int i = 0; i < args.length; i++) {
if ("-file".equals(args[i])) {
fileName = args[++i];
}
}
dump(fileName);
}
public static void dump(String fileName) {
if (!FileUtils.exists(fileName)) {
System.out.println("File not found: " + fileName);
return;
}
FileChannel file = null;
try {
file = FilePath.get(fileName).open("r");
long fileLength = file.size();
file.position(0);
byte[] header = new byte[blockSize];
file.read(ByteBuffer.wrap(header));
Properties prop = new Properties();
prop.load(new ByteArrayInputStream(header));
prop.load(new StringReader(new String(header, "UTF-8")));
System.out.println("file " + fileName);
System.out.println(" length " + fileLength);
System.out.println(" " + prop);
ByteBuffer block = ByteBuffer.wrap(new byte[16]);
for (long pos = 0; pos < fileLength;) {
file.position(pos);
block.rewind();
FileUtils.readFully(file, block);
block.rewind();
if (block.get() != 'c') {
pos += blockSize;
continue;
}
int length = block.getInt();
int chunkId = block.getInt();
int metaRootOffset = block.getInt();
System.out.println(" chunk " + chunkId + " at " + pos +
" length " + length + " offset " + metaRootOffset);
ByteBuffer chunk = ByteBuffer.allocate(length);
file.position(pos);
FileUtils.readFully(file, chunk);
int p = block.position();
pos = (pos + length + blockSize) / blockSize * blockSize;
length -= p;
while (length > 0) {
chunk.position(p);
int len = chunk.getInt();
long mapId = chunk.getLong();
int type = chunk.get();
int count = DataUtils.readVarInt(chunk);
if (type == 1) {
long[] children = new long[count];
for (int i = 0; i < count; i++) {
children[i] = chunk.getLong();
}
System.out.println(" map " + mapId + " at " + p + " node, " + count + " children: " + Arrays.toString(children));
} else {
System.out.println(" map " + mapId + " at " + p + " leaf, " + count + " rows");
}
p += len;
length -= len;
}
}
} catch (IOException e) {
System.out.println("ERROR: " + e);
} finally {
try {
file.close();
} catch (IOException e) {
// ignore
}
}
System.out.println();
}
}
...@@ -10,19 +10,20 @@ import java.nio.ByteBuffer; ...@@ -10,19 +10,20 @@ import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* A btree page implementation. * A btree page (a node or a leaf).
* <p>
* For nodes, the key at a given index is larger than the largest key of the
* child at the same index.
*/ */
class Page { public class Page {
private static final int MAX_SIZE = 20;
private final BtreeMap<?, ?> map; private final BtreeMap<?, ?> map;
private long id; private long id;
private long storedId;
private long transaction; private long transaction;
private Object[] keys; private Object[] keys;
private Object[] values; private Object[] values;
private long[] children; private long[] children;
private int cachedCompare;
private Page(BtreeMap<?, ?> map) { private Page(BtreeMap<?, ?> map) {
this.map = map; this.map = map;
...@@ -57,7 +58,7 @@ class Page { ...@@ -57,7 +58,7 @@ class Page {
*/ */
static Page read(BtreeMap<?, ?> map, long id, ByteBuffer buff) { static Page read(BtreeMap<?, ?> map, long id, ByteBuffer buff) {
Page p = new Page(map); Page p = new Page(map);
p.id = p.storedId = id; p.id = id;
p.read(buff); p.read(buff);
return p; return p;
} }
...@@ -72,6 +73,7 @@ class Page { ...@@ -72,6 +73,7 @@ class Page {
map.removePage(id); map.removePage(id);
Page p2 = create(map, keys, values, children); Page p2 = create(map, keys, values, children);
p2.transaction = t; p2.transaction = t;
p2.cachedCompare = cachedCompare;
return p2; return p2;
} }
...@@ -153,18 +155,39 @@ class Page { ...@@ -153,18 +155,39 @@ class Page {
private int findKey(Object key) { private int findKey(Object key) {
int low = 0, high = keys.length - 1; int low = 0, high = keys.length - 1;
int x = cachedCompare - 1;
if (x < 0 || x > high) {
x = (low + high) >>> 1;
}
while (low <= high) { while (low <= high) {
int x = (low + high) >>> 1;
int compare = map.compare(key, keys[x]); int compare = map.compare(key, keys[x]);
if (compare > 0) { if (compare > 0) {
low = x + 1; low = x + 1;
} else if (compare < 0) { } else if (compare < 0) {
high = x - 1; high = x - 1;
} else { } else {
cachedCompare = x + 1;
return x; return x;
} }
x = (low + high) >>> 1;
} }
cachedCompare = low;
return -(low + 1); return -(low + 1);
// regular binary search (without caching)
// int low = 0, high = keys.length - 1;
// while (low <= high) {
// int x = (low + high) >>> 1;
// int compare = map.compare(key, keys[x]);
// if (compare > 0) {
// low = x + 1;
// } else if (compare < 0) {
// high = x - 1;
// } else {
// return x;
// }
// }
// return -(low + 1);
} }
/** /**
...@@ -176,8 +199,8 @@ class Page { ...@@ -176,8 +199,8 @@ class Page {
*/ */
static void min(Page p, ArrayList<CursorPos> parents, Object key) { static void min(Page p, ArrayList<CursorPos> parents, Object key) {
while (p != null) { while (p != null) {
int x = key == null ? 0 : p.findKey(key);
if (p.children != null) { if (p.children != null) {
int x = key == null ? -1 : p.findKey(key);
if (x < 0) { if (x < 0) {
x = -x - 1; x = -x - 1;
} else { } else {
...@@ -189,6 +212,7 @@ class Page { ...@@ -189,6 +212,7 @@ class Page {
parents.add(c); parents.add(c);
p = p.map.readPage(p.children[x]); p = p.map.readPage(p.children[x]);
} else { } else {
int x = key == null ? 0 : p.findKey(key);
if (x < 0) { if (x < 0) {
x = -x - 1; x = -x - 1;
} }
...@@ -207,7 +231,7 @@ class Page { ...@@ -207,7 +231,7 @@ class Page {
* @param parents the stack of parent page positions * @param parents the stack of parent page positions
* @return the next key * @return the next key
*/ */
public static Object nextKey(ArrayList<CursorPos> parents) { static Object nextKey(ArrayList<CursorPos> parents) {
if (parents.size() == 0) { if (parents.size() == 0) {
return null; return null;
} }
...@@ -224,7 +248,7 @@ class Page { ...@@ -224,7 +248,7 @@ class Page {
return null; return null;
} }
p = parents.remove(parents.size() - 1); p = parents.remove(parents.size() - 1);
index = p.index++; index = ++p.index;
if (index < p.page.children.length) { if (index < p.page.children.length) {
parents.add(p); parents.add(p);
Page x = p.page; Page x = p.page;
...@@ -302,7 +326,7 @@ class Page { ...@@ -302,7 +326,7 @@ class Page {
parent.children[parentIndex] = p.id; parent.children[parentIndex] = p.id;
} }
if (!p.isLeaf()) { if (!p.isLeaf()) {
if (p.keyCount() >= MAX_SIZE) { if (p.keyCount() >= map.getMaxPageSize()) {
// TODO almost duplicate code // TODO almost duplicate code
int pos = p.keyCount() / 2; int pos = p.keyCount() / 2;
Object k = p.keys[pos]; Object k = p.keys[pos];
...@@ -331,7 +355,7 @@ class Page { ...@@ -331,7 +355,7 @@ class Page {
} }
index = -index - 1; index = -index - 1;
p.insert(index, key, value, 0); p.insert(index, key, value, 0);
if (p.keyCount() >= MAX_SIZE) { if (p.keyCount() >= map.getMaxPageSize()) {
int pos = p.keyCount() / 2; int pos = p.keyCount() / 2;
Object k = p.keys[pos]; Object k = p.keys[pos];
Page split = p.splitLeaf(pos); Page split = p.splitLeaf(pos);
...@@ -347,6 +371,8 @@ class Page { ...@@ -347,6 +371,8 @@ class Page {
} }
if (index < 0) { if (index < 0) {
index = -index - 1; index = -index - 1;
} else {
index++;
} }
parent = p; parent = p;
parentIndex = index; parentIndex = index;
...@@ -356,6 +382,18 @@ class Page { ...@@ -356,6 +382,18 @@ class Page {
return top; return top;
} }
/**
* Remove this page and all child pages.
*/
void removeAllRecursive() {
if (children != null) {
for (long c : children) {
map.readPage(c).removeAllRecursive();
}
}
map.removePage(id);
}
/** /**
* Remove a key-value pair. * Remove a key-value pair.
* *
...@@ -368,6 +406,7 @@ class Page { ...@@ -368,6 +406,7 @@ class Page {
if (p.isLeaf()) { if (p.isLeaf()) {
if (index >= 0) { if (index >= 0) {
if (p.keyCount() == 1) { if (p.keyCount() == 1) {
p.map.removePage(p.id);
return null; return null;
} }
p = p.copyOnWrite(); p = p.copyOnWrite();
...@@ -380,6 +419,8 @@ class Page { ...@@ -380,6 +419,8 @@ class Page {
// node // node
if (index < 0) { if (index < 0) {
index = -index - 1; index = -index - 1;
} else {
index++;
} }
Page c = p.map.readPage(p.children[index]); Page c = p.map.readPage(p.children[index]);
Page c2 = remove(c, key); Page c2 = remove(c, key);
...@@ -390,7 +431,8 @@ class Page { ...@@ -390,7 +431,8 @@ class Page {
p = p.copyOnWrite(); p = p.copyOnWrite();
p.remove(index); p.remove(index);
if (p.keyCount() == 0) { if (p.keyCount() == 0) {
p = c2; p.map.removePage(p.id);
p = p.map.readPage(p.children[0]);
} }
} else { } else {
p = p.copyOnWrite(); p = p.copyOnWrite();
...@@ -401,18 +443,18 @@ class Page { ...@@ -401,18 +443,18 @@ class Page {
private void insert(int index, Object key, Object value, long child) { private void insert(int index, Object key, Object value, long child) {
Object[] newKeys = new Object[keys.length + 1]; Object[] newKeys = new Object[keys.length + 1];
copyWithGap(keys, newKeys, keys.length, index); DataUtils.copyWithGap(keys, newKeys, keys.length, index);
newKeys[index] = key; newKeys[index] = key;
keys = newKeys; keys = newKeys;
if (values != null) { if (values != null) {
Object[] newValues = new Object[values.length + 1]; Object[] newValues = new Object[values.length + 1];
copyWithGap(values, newValues, values.length, index); DataUtils.copyWithGap(values, newValues, values.length, index);
newValues[index] = value; newValues[index] = value;
values = newValues; values = newValues;
} }
if (children != null) { if (children != null) {
long[] newChildren = new long[children.length + 1]; long[] newChildren = new long[children.length + 1];
copyWithGap(children, newChildren, children.length, index + 1); DataUtils.copyWithGap(children, newChildren, children.length, index + 1);
newChildren[index + 1] = child; newChildren[index + 1] = child;
children = newChildren; children = newChildren;
} }
...@@ -420,21 +462,28 @@ class Page { ...@@ -420,21 +462,28 @@ class Page {
private void remove(int index) { private void remove(int index) {
Object[] newKeys = new Object[keys.length - 1]; Object[] newKeys = new Object[keys.length - 1];
copyExcept(keys, newKeys, keys.length, index); int keyIndex = index >= keys.length ? index - 1 : index;
DataUtils.copyExcept(keys, newKeys, keys.length, keyIndex);
keys = newKeys; keys = newKeys;
if (values != null) { if (values != null) {
Object[] newValues = new Object[values.length - 1]; Object[] newValues = new Object[values.length - 1];
copyExcept(values, newValues, values.length, index); DataUtils.copyExcept(values, newValues, values.length, index);
values = newValues; values = newValues;
} }
if (children != null) { if (children != null) {
long[] newChildren = new long[children.length - 1]; long[] newChildren = new long[children.length - 1];
copyExcept(children, newChildren, children.length, index); DataUtils.copyExcept(children, newChildren, children.length, index);
children = newChildren; children = newChildren;
} }
} }
private void read(ByteBuffer buff) { private void read(ByteBuffer buff) {
// len
buff.getInt();
long id = buff.getLong();
if (id != map.getId()) {
throw new RuntimeException("Page map id missmatch, expected " + map.getId() + " got " + id);
}
boolean node = buff.get() == 1; boolean node = buff.get() == 1;
if (node) { if (node) {
int len = DataUtils.readVarInt(buff); int len = DataUtils.readVarInt(buff);
...@@ -442,9 +491,9 @@ class Page { ...@@ -442,9 +491,9 @@ class Page {
keys = new Object[len - 1]; keys = new Object[len - 1];
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
children[i] = buff.getLong(); children[i] = buff.getLong();
if (i < keys.length) {
keys[i] = map.getKeyType().read(buff);
} }
for (int i = 0; i < len - 1; i++) {
keys[i] = map.getKeyType().read(buff);
} }
} else { } else {
int len = DataUtils.readVarInt(buff); int len = DataUtils.readVarInt(buff);
...@@ -462,17 +511,19 @@ class Page { ...@@ -462,17 +511,19 @@ class Page {
* *
* @param buff the target buffer * @param buff the target buffer
*/ */
void write(ByteBuffer buff) { private void write(ByteBuffer buff) {
int pos = buff.position();
buff.putInt(0);
buff.putLong(map.getId());
if (children != null) { if (children != null) {
buff.put((byte) 1); buff.put((byte) 1);
int len = children.length; int len = children.length;
DataUtils.writeVarInt(buff, len); DataUtils.writeVarInt(buff, len);
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
long c = map.readPage(children[i]).storedId; buff.putLong(children[i]);
buff.putLong(c);
if (i < keys.length) {
map.getKeyType().write(buff, keys[i]);
} }
for (int i = 0; i < len - 1; i++) {
map.getKeyType().write(buff, keys[i]);
} }
} else { } else {
buff.put((byte) 0); buff.put((byte) 0);
...@@ -483,66 +534,62 @@ class Page { ...@@ -483,66 +534,62 @@ class Page {
map.getValueType().write(buff, values[i]); map.getValueType().write(buff, values[i]);
} }
} }
int len = buff.position() - pos;
buff.putInt(pos, len);
} }
/** /**
* Get the length in bytes, including temporary children. * Get the maximum length in bytes to store temporary records, recursively.
*
* @return the length
*/
int lengthIncludingTempChildren() {
int byteCount = length();
if (children != null) {
int len = children.length;
for (int i = 0; i < len; i++) {
long c = children[i];
if (c < 0) {
byteCount += map.readPage(c).lengthIncludingTempChildren();
}
}
}
return byteCount;
}
/**
* Update the page ids recursively.
* *
* @param pageId the new page id * @param pageId the new page id
* @return the next page id * @return the next page id
*/ */
long updatePageIds(long pageId) { int getMaxLengthTempRecursive() {
this.storedId = pageId; int maxLength = 4 + 8 + 1;
pageId += length();
if (children != null) { if (children != null) {
int len = children.length; int len = children.length;
maxLength += DataUtils.MAX_VAR_INT_LEN;
maxLength += 8 * len;
for (int i = 0; i < len - 1; i++) {
maxLength += map.getKeyType().getMaxLength(keys[i]);
}
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
long c = children[i]; long c = children[i];
if (c < 0) { if (c < 0) {
pageId = map.readPage(c).updatePageIds(pageId); maxLength += map.readPage(c).getMaxLengthTempRecursive();
} }
} }
} else {
int len = keys.length;
maxLength += DataUtils.MAX_VAR_INT_LEN;
for (int i = 0; i < len; i++) {
maxLength += map.getKeyType().getMaxLength(keys[i]);
maxLength += map.getValueType().getMaxLength(values[i]);
}
} }
return pageId; return maxLength;
} }
/** /**
* Store this page. * Store this page and all children that are changed,
* in reverse order, and update the id and child ids.
* *
* @param buff the target buffer * @param buff the target buffer
* @param idOffset the offset of the id
* @return the page id * @return the page id
*/ */
long storeTemp(ByteBuffer buff) { long writeTempRecursive(ByteBuffer buff, long idOffset) {
write(buff);
if (children != null) { if (children != null) {
int len = children.length; int len = children.length;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
long c = children[i]; long c = children[i];
if (c < 0) { if (c < 0) {
children[i] = map.readPage(c).storeTemp(buff); children[i] = map.readPage(c).writeTempRecursive(buff, idOffset);
} }
} }
} }
this.id = storedId; this.id = idOffset + buff.position();
write(buff);
return id; return id;
} }
...@@ -551,63 +598,18 @@ class Page { ...@@ -551,63 +598,18 @@ class Page {
* *
* @return the number of temporary pages * @return the number of temporary pages
*/ */
int countTemp() { int countTempRecursive() {
int count = 1; int count = 1;
if (children != null) { if (children != null) {
int size = children.length; int size = children.length;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
long c = children[i]; long c = children[i];
if (c < 0) { if (c < 0) {
count += map.readPage(c).countTemp(); count += map.readPage(c).countTempRecursive();
} }
} }
} }
return count; return count;
} }
/**
* Get the length in bytes.
*
* @return the length
*/
int length() {
int byteCount = 1;
if (children != null) {
int len = children.length;
byteCount += DataUtils.getVarIntLen(len);
for (int i = 0; i < len; i++) {
byteCount += 8;
if (i < keys.length) {
byteCount += map.getKeyType().length(keys[i]);
}
}
} else {
int len = keys.length;
byteCount += DataUtils.getVarIntLen(len);
for (int i = 0; i < len; i++) {
byteCount += map.getKeyType().length(keys[i]);
byteCount += map.getValueType().length(values[i]);
}
}
return byteCount;
}
private static void copyWithGap(Object src, Object dst, int oldSize, int gapIndex) {
if (gapIndex > 0) {
System.arraycopy(src, 0, dst, 0, gapIndex);
}
if (gapIndex < oldSize) {
System.arraycopy(src, gapIndex, dst, gapIndex + 1, oldSize - gapIndex);
}
}
private static void copyExcept(Object src, Object dst, int oldSize, int removeIndex) {
if (removeIndex > 0 && oldSize > 0) {
System.arraycopy(src, 0, dst, 0, removeIndex);
}
if (removeIndex < oldSize) {
System.arraycopy(src, removeIndex + 1, dst, removeIndex, oldSize - removeIndex - 1);
}
}
} }
...@@ -11,39 +11,67 @@ import java.nio.ByteBuffer; ...@@ -11,39 +11,67 @@ import java.nio.ByteBuffer;
/** /**
* A string type. * A string type.
*/ */
class StringType implements DataType { public class StringType implements DataType {
public int compare(Object a, Object b) { public int compare(Object a, Object b) {
return a.toString().compareTo(b.toString()); return a.toString().compareTo(b.toString());
} }
public int length(Object obj) { public int length(Object obj) {
try { int plus = 0;
byte[] bytes = obj.toString().getBytes("UTF-8"); String s = obj.toString();
return DataUtils.getVarIntLen(bytes.length) + bytes.length; int len = s.length();
} catch (Exception e) { for (int i = 0; i < len; i++) {
throw new RuntimeException(e); char c = s.charAt(i);
if (c >= 0x800) {
plus += 2;
} else if (c >= 0x80) {
plus++;
} }
} }
return DataUtils.getVarIntLen(len) + len + plus;
}
public int getMaxLength(Object obj) {
return DataUtils.MAX_VAR_INT_LEN + obj.toString().length() * 3;
}
public int getMemory(Object obj) {
return obj.toString().length() * 2 + 48;
}
public String read(ByteBuffer buff) { public String read(ByteBuffer buff) {
int len = DataUtils.readVarInt(buff); int len = DataUtils.readVarInt(buff);
byte[] bytes = new byte[len]; char[] chars = new char[len];
buff.get(bytes); for (int i = 0; i < len; i++) {
try { int x = buff.get() & 0xff;
return new String(bytes, "UTF-8"); if (x < 0x80) {
} catch (Exception e) { chars[i] = (char) x;
throw new RuntimeException(e); } else if (x >= 0xe0) {
} chars[i] = (char) (((x & 0xf) << 12) + ((buff.get() & 0x3f) << 6) + (buff.get() & 0x3f));
} } else {
chars[i] = (char) (((x & 0x1f) << 6) + (buff.get() & 0x3f));
public void write(ByteBuffer buff, Object x) { }
try { }
byte[] bytes = x.toString().getBytes("UTF-8"); return new String(chars);
DataUtils.writeVarInt(buff, bytes.length); }
buff.put(bytes);
} catch (Exception e) { public void write(ByteBuffer buff, Object obj) {
throw new RuntimeException(e); String s = obj.toString();
int len = s.length();
DataUtils.writeVarInt(buff, len);
for (int i = 0; i < len; i++) {
int c = s.charAt(i);
if (c < 0x80) {
buff.put((byte) c);
} else if (c >= 0x800) {
buff.put((byte) (0xe0 | (c >> 12)));
buff.put((byte) (((c >> 6) & 0x3f)));
buff.put((byte) (c & 0x3f));
} else {
buff.put((byte) (0xc0 | (c >> 6)));
buff.put((byte) (c & 0x3f));
}
} }
} }
......
...@@ -132,7 +132,7 @@ public class StoredMap<K, V> { ...@@ -132,7 +132,7 @@ public class StoredMap<K, V> {
/** /**
* A value type. * A value type.
*/ */
static interface ValueType { interface ValueType {
/** /**
* Get the length in bytes. * Get the length in bytes.
...@@ -170,7 +170,7 @@ public class StoredMap<K, V> { ...@@ -170,7 +170,7 @@ public class StoredMap<K, V> {
/** /**
* A key type. * A key type.
*/ */
static interface KeyType extends ValueType { interface KeyType extends ValueType {
/** /**
* Compare two keys. * Compare two keys.
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论