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

A persistent multi-version map (work in progress) - implement complete java.util.Map interface

上级 7ef5bbc4
......@@ -150,23 +150,26 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
return bounds;
}
public void put(K key, V value) {
putOrAdd(key, value, false);
@SuppressWarnings("unchecked")
public V put(K key, V value) {
return (V) putOrAdd(key, value, false);
}
public void add(K key, V value) {
putOrAdd(key, value, true);
}
public void putOrAdd(K key, V value, boolean alwaysAdd) {
public Object putOrAdd(K key, V value, boolean alwaysAdd) {
checkWrite();
long writeVersion = store.getCurrentVersion();
Page p = root;
Object result;
if (p == null) {
Object[] keys = { key };
Object[] values = { value };
p = Page.create(this, writeVersion, 1,
keys, values, null, null, null, 1, 0);
result = null;
} else if (alwaysAdd || get(key) == null) {
if (p.getKeyCount() > store.getMaxPageSize()) {
// only possible if this is the root, else we would have split earlier
......@@ -183,43 +186,41 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
totalCount, 0);
// now p is a node; continues
}
p = add(p, writeVersion, key, value);
add(p, writeVersion, key, value);
result = null;
} else {
p = set(p, writeVersion, key, value);
result = set(p, writeVersion, key, value);
}
setRoot(p);
return result;
}
protected Page set(Page p, long writeVersion, Object key, Object value) {
protected Object set(Page p, long writeVersion, Object key, Object value) {
if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page c = p.getChildPage(i);
Page c2 = set(c, writeVersion, key, value);
if (c != c2) {
p = p.copyOnWrite(writeVersion);
p.setChild(i, c2);
break;
Page c = p.getChildPage(i).copyOnWrite(writeVersion);
Object result = set(c, writeVersion, key, value);
if (result != null) {
p.setChild(i, c);
return result;
}
}
}
} else {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
p = p.copyOnWrite(writeVersion);
p.setValue(i, value);
break;
return p.setValue(i, value);
}
}
}
return p;
return null;
}
protected Page add(Page p, long writeVersion, Object key, Object value) {
protected void add(Page p, long writeVersion, Object key, Object value) {
if (p.isLeaf()) {
p = p.copyOnWrite(writeVersion);
p.insertLeaf(p.getKeyCount(), key, value);
return p;
return;
}
// p is a node
int index = -1;
......@@ -241,25 +242,23 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
}
}
}
Page c = p.getChildPage(index);
Page c = p.getChildPage(index).copyOnWrite(writeVersion);
if (c.getKeyCount() >= store.getMaxPageSize()) {
// split on the way down
c = c.copyOnWrite(writeVersion);
Page split = split(c, writeVersion);
p = p.copyOnWrite(writeVersion);
p.setKey(index, getBounds(c));
p.setChild(index, c);
p.insertNode(index, getBounds(split), split);
// now we are not sure where to add
return add(p, writeVersion, key, value);
add(p, writeVersion, key, value);
return;
}
Page c2 = add(c, writeVersion, key, value);
p = p.copyOnWrite(writeVersion);
add(c, writeVersion, key, value);
Object bounds = p.getKey(index);
keyType.increaseBounds(bounds, key);
p.setKey(index, bounds);
p.setChild(index, c2);
return p;
p.setChild(index, c);
}
private Page split(Page p, long writeVersion) {
......
......@@ -56,12 +56,12 @@ public class TestMVStore extends TestBase {
s.setMaxPageSize(6);
MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 60; i++) {
m.put(i, "Hello");
m.put(i, "Hi");
}
s.commit();
s.store();
for (int i = 20; i < 40; i++) {
m.put(i, "Hello");
assertEquals("Hi", m.put(i, "Hello"));
}
s.commit();
for (int i = 10; i < 15; i++) {
......@@ -318,7 +318,7 @@ public class TestMVStore extends TestBase {
s.store();
assertEquals("1/1///", m.get("map.data"));
assertTrue(m.containsKey("chunk.1"));
data.put("1", "Hallo");
assertEquals("Hello", data.put("1", "Hallo"));
s.store();
assertEquals("1/1///", m.get("map.data"));
assertTrue(m.get("root.1").length() > 0);
......@@ -341,7 +341,7 @@ public class TestMVStore extends TestBase {
MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
// t = System.currentTimeMillis();
for (int i = 0; i < len; i++) {
m.put(i, "Hello World");
assertNull(m.put(i, "Hello World"));
}
// System.out.println("put: " + (System.currentTimeMillis() - t));
// t = System.currentTimeMillis();
......@@ -404,7 +404,7 @@ public class TestMVStore extends TestBase {
// p.startCollecting();
// long t = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
m.put(i, "hello " + i);
assertNull(m.put(i, "hello " + i));
assertEquals("hello " + i, m.get(i));
}
// System.out.println("put: " + (System.currentTimeMillis() - t));
......@@ -515,6 +515,7 @@ public class TestMVStore extends TestBase {
Random r = new Random(1);
int operationCount = 1000;
int maxValue = 30;
Integer expected, got;
for (int i = 0; i < operationCount; i++) {
int k = r.nextInt(maxValue);
int v = r.nextInt();
......@@ -522,14 +523,19 @@ public class TestMVStore extends TestBase {
switch (r.nextInt(3)) {
case 0:
log(i + ": put " + k + " = " + v);
m.put(k, v);
map.put(k, v);
expected = map.put(k, v);
got = m.put(k, v);
if (expected == null) {
assertNull(got);
} else {
assertEquals(expected, got);
}
compareAll = true;
break;
case 1:
log(i + ": remove " + k);
Integer expected = map.remove(k);
Integer got = m.remove(k);
expected = map.remove(k);
got = m.remove(k);
if (expected == null) {
assertNull(got);
} else {
......@@ -553,8 +559,8 @@ public class TestMVStore extends TestBase {
Iterator<Integer> itExpected = map.keySet().iterator();
while (itExpected.hasNext()) {
assertTrue(it.hasNext());
Integer expected = itExpected.next();
Integer got = it.next();
expected = itExpected.next();
got = it.next();
assertEquals(expected, got);
}
assertFalse(it.hasNext());
......
......@@ -6,8 +6,11 @@
*/
package org.h2.dev.store.btree;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
......@@ -17,7 +20,7 @@ import java.util.TreeMap;
* @param <K> the key class
* @param <V> the value class
*/
public class MVMap<K, V> {
public class MVMap<K, V> extends AbstractMap<K, V> {
protected final MVStore store;
protected Page root;
......@@ -51,16 +54,20 @@ public class MVMap<K, V> {
*
* @param key the key
* @param value the value
* @return the old value if the key existed, or null otherwise
*/
public void put(K key, V value) {
@SuppressWarnings("unchecked")
public V put(K key, V value) {
checkWrite();
long writeVersion = store.getCurrentVersion();
Page p = root;
Object result;
if (p == null) {
Object[] keys = { key };
Object[] values = { value };
p = Page.create(this, writeVersion, 1,
keys, values, null, null, null, 1, 0);
result = null;
} else {
p = p.copyOnWrite(writeVersion);
if (p.getKeyCount() > store.getMaxPageSize()) {
......@@ -76,9 +83,10 @@ public class MVMap<K, V> {
keys, null, children, childrenPages, counts, totalCount, 0);
// now p is a node; insert continues
}
put(p, writeVersion, key, value);
result = put(p, writeVersion, key, value);
}
setRoot(p);
return (V) result;
}
/**
......@@ -90,16 +98,15 @@ public class MVMap<K, V> {
* @param key the key
* @param value the value (may not be null)
*/
protected void put(Page p, long writeVersion, Object key, Object value) {
protected Object put(Page p, long writeVersion, Object key, Object value) {
if (p.isLeaf()) {
int index = p.binarySearch(key);
if (index < 0) {
index = -index - 1;
p.insertLeaf(index, key, value);
} else {
p.setValue(index, value);
return null;
}
return;
return p.setValue(index, value);
}
// p is a node
int index = p.binarySearch(key);
......@@ -108,8 +115,7 @@ public class MVMap<K, V> {
} else {
index++;
}
Page cOld = p.getChildPage(index);
Page c = cOld.copyOnWrite(writeVersion);
Page c = p.getChildPage(index).copyOnWrite(writeVersion);
if (c.getKeyCount() >= store.getMaxPageSize()) {
// split on the way down
int at = c.getKeyCount() / 2;
......@@ -118,14 +124,11 @@ public class MVMap<K, V> {
p.setChild(index, split);
p.insertNode(index, k, c);
// now we are not sure where to add
put(p, writeVersion, key, value);
return;
}
long oldSize = c.getTotalCount();
put(c, writeVersion, key, value);
if (cOld != c || oldSize != c.getTotalCount()) {
p.setChild(index, c);
return put(p, writeVersion, key, value);
}
Object result = put(c, writeVersion, key, value);
p.setChild(index, c);
return result;
}
/**
......@@ -312,9 +315,9 @@ public class MVMap<K, V> {
* Remove a key-value pair, if the key exists.
*
* @param key the key
* @return the old value if the key existed
* @return the old value if the key existed, or null otherwise
*/
public V remove(K key) {
public V remove(Object key) {
checkWrite();
Page p = root;
if (p == null) {
......@@ -468,6 +471,14 @@ public class MVMap<K, V> {
return c;
}
public Set<Map.Entry<K, V>> entrySet() {
HashMap<K, V> map = new HashMap<K, V>();
for (K k : keySet()) {
map.put(k, get(k));
}
return map.entrySet();
}
public Set<K> keySet() {
checkOpen();
final Page root = this.root;
......@@ -582,6 +593,10 @@ public class MVMap<K, V> {
return id;
}
public boolean equals(Object o) {
return this == o;
}
public int size() {
long size = getSize();
return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size;
......@@ -591,10 +606,6 @@ public class MVMap<K, V> {
return root == null ? 0 : root.getTotalCount();
}
public boolean equals(Object o) {
return this == o;
}
long getCreateVersion() {
return createVersion;
}
......
......@@ -38,27 +38,25 @@ header:
blockSize=4096
TODO:
- rename commit to incrementVersion
- implement complete java.util.Map interface
- test with very small chunks, possibly speed up very small transactions
- check what happens on concurrent reads and 1 write; multiple writes
- support all objects (using serialization)
- concurrent iterator (when to call commit; read on first hasNext())
- compact: use total max length instead of page count (liveCount)
- support background writes (store old version)
- avoid using java.util.Properties (it allocates quite a lot of memory)
- support large binaries
- support database version / schema version
- limited support for writing to old versions (branches)
- atomic test-and-set (when supporting concurrent writes)
- support background writes (store old version)
- file header could be a regular chunk, end of file the second
- possibly split chunk data into immutable and mutable
- test with very small chunks, possibly speed up very small transactions
- compact: use total max length instead of page count (liveCount)
- check what happens on concurrent reads and 1 write; multiple writes
- concurrent iterator (when to call commit)
- support large binaries
- support stores that span multiple files (chunks stored in other files)
- triggers
- support database version / schema version
- implement more counted b-tree (skip, get positions)
- merge pages if small
- r-tree: add missing features (NN search for example)
- compression: maybe hash table reset speeds up compression
- avoid using java.util.Properties (it allocates quite a lot of memory)
- support all objects (using serialization)
*/
......
......@@ -346,12 +346,14 @@ public class Page {
keys[index] = key;
}
public void setValue(int index, Object value) {
public Object setValue(int index, Object value) {
Object old = values[index];
if ((sharedFlags & SHARED_VALUES) != 0) {
values = Arrays.copyOf(values, values.length);
sharedFlags &= ~SHARED_VALUES;
}
values[index] = value;
return old;
}
/**
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论