提交 4c34bc07 authored 作者: Thomas Mueller's avatar Thomas Mueller

A persistent multi-version map: move to the main source tree

上级 b222ae75
/*
* 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.util.Iterator;
/**
* A cursor to iterate over all keys in new pages.
*
* @param <K> the key type
* @param <V> the value type
*/
public class ChangeCursor<K, V> implements Iterator<K> {
private final MVMap<K, V> map;
private final Page root1, root2;
/**
* The state of this cursor.
* 0: not initialized
* 1: reading from root1
* 2: reading from root2
* 3: closed
*/
private int state;
private CursorPos pos1, pos2;
private K current;
ChangeCursor(MVMap<K, V> map, Page root1, Page root2) {
this.map = map;
this.root1 = root1;
this.root2 = root2;
}
public K next() {
K c = current;
fetchNext();
return c;
}
public boolean hasNext() {
if (state == 0) {
pos1 = new CursorPos(root1, 0, null);
pos1 = min(pos1);
state = 1;
fetchNext();
}
return current != null;
}
public void remove() {
throw new UnsupportedOperationException();
}
private void fetchNext() {
while (fetchNextKey()) {
if (pos1 == null || pos2 == null) {
break;
}
@SuppressWarnings("unchecked")
V v1 = (V) map.binarySearch(root1, current);
@SuppressWarnings("unchecked")
V v2 = (V) map.binarySearch(root2, current);
if (!v1.equals(v2)) {
break;
}
}
}
private boolean fetchNextKey() {
while (true) {
if (state == 3) {
return false;
}
if (state == 1) {
// read from root1
pos1 = fetchNext(pos1);
if (pos1 == null) {
// reached the end of pos1
state = 2;
pos2 = null;
continue;
}
pos2 = find(root2, current);
if (pos2 == null) {
// not found in root2
return true;
}
if (!pos1.page.equals(pos2.page)) {
// the page is different,
// so the entry has possibly changed
return true;
}
while (true) {
pos1 = pos1.parent;
if (pos1 == null) {
// reached end of pos1
state = 2;
pos2 = null;
break;
}
pos2 = pos2.parent;
if (pos2 == null || !pos1.page.equals(pos2.page)) {
if (pos1.index + 1 < map.getChildPageCount(pos1.page)) {
pos1 = new CursorPos(pos1.page.getChildPage(++pos1.index), 0, pos1);
pos1 = min(pos1);
break;
}
}
}
}
if (state == 2) {
if (pos2 == null) {
// init reading from root2
pos2 = new CursorPos(root2, 0, null);
pos2 = min(pos2);
}
// read from root2
pos2 = fetchNext(pos2);
if (pos2 == null) {
// reached the end of pos2
state = 3;
current = null;
continue;
}
pos1 = find(root1, current);
if (pos1 != null) {
// found a corresponding record
// so it was not deleted
// but now we may need to skip pages
if (!pos1.page.equals(pos2.page)) {
// the page is different
pos1 = null;
continue;
}
while (true) {
pos2 = pos2.parent;
if (pos2 == null) {
// reached end of pos1
state = 3;
current = null;
pos1 = null;
break;
}
pos1 = pos1.parent;
if (pos1 == null || !pos2.page.equals(pos1.page)) {
if (pos2.index + 1 < map.getChildPageCount(pos2.page)) {
pos2 = new CursorPos(pos2.page.getChildPage(++pos2.index), 0, pos2);
pos2 = min(pos2);
break;
}
}
}
pos1 = null;
continue;
}
// found no corresponding record
// so it was deleted
return true;
}
}
}
private CursorPos find(Page p, K key) {
// TODO combine with RangeCursor.min
// possibly move to MVMap
CursorPos pos = null;
while (true) {
if (p.isLeaf()) {
int x = key == null ? 0 : p.binarySearch(key);
if (x < 0) {
return null;
}
return new CursorPos(p, x, pos);
}
int x = key == null ? -1 : p.binarySearch(key);
if (x < 0) {
x = -x - 1;
} else {
x++;
}
pos = new CursorPos(p, x, pos);
p = p.getChildPage(x);
}
}
@SuppressWarnings("unchecked")
private CursorPos fetchNext(CursorPos p) {
while (p != null) {
if (p.index < p.page.getKeyCount()) {
current = (K) p.page.getKey(p.index++);
return p;
}
p = p.parent;
if (p == null) {
break;
}
if (p.index + 1 < map.getChildPageCount(p.page)) {
p = new CursorPos(p.page.getChildPage(++p.index), 0, p);
p = min(p);
}
}
current = null;
return p;
}
private static CursorPos min(CursorPos p) {
while (true) {
if (p.page.isLeaf()) {
return p;
}
Page c = p.page.getChildPage(0);
p = new CursorPos(c, 0, p);
}
}
}
/*
* 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.nio.ByteBuffer;
import java.util.HashMap;
/**
* A chunk of data, containing one or multiple pages.
* <p>
* Chunks are page aligned (each page is usually 4096 bytes).
* There are at most 67 million (2^26) chunks,
* each chunk is at most 2 GB large.
* File format:
* 1 byte: 'c'
* 4 bytes: length
* 4 bytes: chunk id (an incrementing number)
* 4 bytes: pageCount
* 8 bytes: metaRootPos
* 8 bytes: maxLengthLive
* [ Page ] *
*/
public class Chunk {
/**
* The chunk id.
*/
final int id;
/**
* The start position within the file.
*/
long start;
/**
* The length in bytes.
*/
int length;
/**
* The number of pages.
*/
int pageCount;
/**
* The sum of the max length of all pages.
*/
long maxLength;
/**
* The sum of the max length of all pages that are in use.
*/
long maxLengthLive;
/**
* The garbage collection priority.
*/
int collectPriority;
/**
* The position of the meta root.
*/
long metaRootPos;
/**
* The version stored in this chunk.
*/
long version;
Chunk(int id) {
this.id = id;
}
/**
* Read the header from the byte buffer.
*
* @param buff the source buffer
* @param start the start of the chunk in the file
* @return the chunk
*/
static Chunk fromHeader(ByteBuffer buff, long start) {
if (buff.get() != 'c') {
throw new RuntimeException("File corrupt");
}
int length = buff.getInt();
int chunkId = buff.getInt();
int pageCount = buff.getInt();
long metaRootPos = buff.getLong();
long maxLength = buff.getLong();
long maxLengthLive = buff.getLong();
Chunk c = new Chunk(chunkId);
c.length = length;
c.pageCount = pageCount;
c.start = start;
c.metaRootPos = metaRootPos;
c.maxLength = maxLength;
c.maxLengthLive = maxLengthLive;
return c;
}
/**
* Write the header.
*
* @param buff the target buffer
*/
void writeHeader(ByteBuffer buff) {
buff.put((byte) 'c');
buff.putInt(length);
buff.putInt(id);
buff.putInt(pageCount);
buff.putLong(metaRootPos);
buff.putLong(maxLength);
buff.putLong(maxLengthLive);
}
/**
* Build a block from the given string.
*
* @param s the string
* @return the block
*/
public static Chunk fromString(String s) {
HashMap<String, String> map = DataUtils.parseMap(s);
int id = Integer.parseInt(map.get("id"));
Chunk c = new Chunk(id);
c.start = Long.parseLong(map.get("start"));
c.length = Integer.parseInt(map.get("length"));
c.pageCount = Integer.parseInt(map.get("pageCount"));
c.maxLength = Long.parseLong(map.get("maxLength"));
c.maxLengthLive = Long.parseLong(map.get("maxLengthLive"));
c.metaRootPos = Long.parseLong(map.get("metaRoot"));
c.version = Long.parseLong(map.get("version"));
return c;
}
public int getFillRate() {
return (int) (maxLength == 0 ? 0 : 100 * maxLengthLive / maxLength);
}
public int hashCode() {
return id;
}
public boolean equals(Object o) {
return o instanceof Chunk && ((Chunk) o).id == id;
}
/**
* Get the chunk data as a string.
*
* @return the string
*/
public String asString() {
return
"id:" + id + "," +
"start:" + start + "," +
"length:" + length + "," +
"pageCount:" + pageCount + "," +
"maxLength:" + maxLength + "," +
"maxLengthLive:" + maxLengthLive + "," +
"metaRoot:" + metaRootPos + "," +
"version:" + version;
}
}
/*
* 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.util.Iterator;
/**
* A cursor to iterate over elements in ascending order.
*
* @param <K> the key type
*/
public class Cursor<K> implements Iterator<K> {
protected final MVMap<K, ?> map;
protected final K from;
protected CursorPos pos;
protected K current;
private final Page root;
private boolean initialized;
protected Cursor(MVMap<K, ?> map, Page root, K from) {
this.map = map;
this.root = root;
this.from = from;
}
public K next() {
hasNext();
K c = current;
fetchNext();
return c;
}
public boolean hasNext() {
if (!initialized) {
min(root, from);
initialized = true;
fetchNext();
}
return current != null;
}
/**
* Skip over that many entries. This method is relatively fast (for this map
* implementation) even if many entries need to be skipped.
*
* @param n the number of entries to skip
*/
public void skip(long n) {
if (!hasNext()) {
return;
}
if (n < 10) {
while (n-- > 0) {
fetchNext();
}
return;
}
long index = map.getKeyIndex(current);
K k = map.getKey(index + n);
min(root, k);
fetchNext();
}
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Fetch the next entry that is equal or larger than the given key, starting
* from the given page.
*
* @param p the page to start
* @param from the key to search
*/
protected void min(Page p, K from) {
while (true) {
if (p.isLeaf()) {
int x = from == null ? 0 : p.binarySearch(from);
if (x < 0) {
x = -x - 1;
}
pos = new CursorPos(p, x, pos);
break;
}
int x = from == null ? -1 : p.binarySearch(from);
if (x < 0) {
x = -x - 1;
} else {
x++;
}
pos = new CursorPos(p, x + 1, pos);
p = p.getChildPage(x);
}
}
/**
* Fetch the next entry if there is one.
*/
@SuppressWarnings("unchecked")
protected void fetchNext() {
while (pos != null) {
if (pos.index < pos.page.getKeyCount()) {
current = (K) pos.page.getKey(pos.index++);
return;
}
pos = pos.parent;
if (pos == null) {
break;
}
if (pos.index < map.getChildPageCount(pos.page)) {
min(pos.page.getChildPage(pos.index++), null);
}
}
current = null;
}
}
/*
* 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;
/**
* A position in a cursor
*/
public class CursorPos {
/**
* The current page.
*/
public final Page page;
/**
* The current index.
*/
public int index;
/**
* The position in the parent page, if any.
*/
public final CursorPos parent;
public CursorPos(Page page, int index, CursorPos parent) {
this.page = page;
this.index = index;
this.parent = parent;
}
}
/*
* 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.util.HashMap;
import org.h2.dev.store.type.DataTypeFactory;
import org.h2.util.New;
/**
* A builder for an MVStore.
*/
public class MVStoreBuilder {
private final HashMap<String, Object> config = New.hashMap();
/**
* Use the following file name. If the file does not exist, it is
* automatically created.
*
* @param fileName the file name
* @return this
*/
public static MVStoreBuilder fileBased(String fileName) {
return new MVStoreBuilder().fileName(fileName);
}
/**
* Open the store in-memory. In this case, no data may be saved.
*
* @return the store
*/
public static MVStoreBuilder inMemory() {
return new MVStoreBuilder();
}
private MVStoreBuilder set(String key, Object value) {
if (config.containsKey(key)) {
throw new IllegalArgumentException("Parameter " + config.get(key) + " is already set");
}
config.put(key, value);
return this;
}
private MVStoreBuilder fileName(String fileName) {
return set("fileName", fileName);
}
/**
* Open the file in read-only mode. In this case, a shared lock will be
* acquired to ensure the file is not concurrently opened in write mode.
* <p>
* If this option is not used, the file is locked exclusively.
* <p>
* Please note a store may only be opened once in every JVM (no matter
* whether it is opened in read-only or read-write mode), because each file
* may be locked only once in a process.
*
* @return this
*/
public MVStoreBuilder readOnly() {
return set("openMode", "r");
}
/**
* Set the read cache size in MB. The default is 16 MB.
*
* @param mb the cache size
* @return this
*/
public MVStoreBuilder cacheSizeMB(int mb) {
return set("cacheSize", Integer.toString(mb));
}
/**
* Use the given data type factory.
*
* @param factory the data type factory
* @return this
*/
public MVStoreBuilder with(DataTypeFactory factory) {
return set("dataTypeFactory", factory);
}
/**
* Open the store.
*
* @return the opened store
*/
public MVStore open() {
MVStore s = new MVStore(config);
s.open();
return s;
}
public String toString() {
return DataUtils.appendMap(new StringBuilder(), config).toString();
}
/**
* Read the configuration from a string.
*
* @param s the string representation
* @return the builder
*/
public static MVStoreBuilder fromString(String s) {
HashMap<String, String> config = DataUtils.parseMap(s);
MVStoreBuilder builder = new MVStoreBuilder();
builder.config.putAll(config);
return builder;
}
}
/*
* 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.PrintWriter;
import java.io.StringReader;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Properties;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils;
/**
* Utility methods used in combination with the MVStore.
*/
public class MVStoreTool {
/**
* Runs this tool.
* Options are case sensitive. Supported options are:
* <table>
* <tr><td>[-dump &lt;dir&gt;]</td>
* <td>Dump the contends of the file</td></tr>
* </table>
*
* @param args the command line arguments
*/
public static void main(String... args) throws IOException {
for (int i = 0; i < args.length; i++) {
if ("-dump".equals(args[i])) {
String fileName = args[++i];
dump(fileName, new PrintWriter(System.out));
}
}
}
/**
* Read the contents of the file and display them in a human-readable
* format.
*
* @param fileName the name of the file
* @param writer the print writer
*/
public static void dump(String fileName, PrintWriter writer) throws IOException {
if (!FileUtils.exists(fileName)) {
writer.println("File not found: " + fileName);
return;
}
FileChannel file = null;
int blockSize = MVStore.BLOCK_SIZE;
try {
file = FilePath.get(fileName).open("r");
long fileLength = file.size();
byte[] header = new byte[blockSize];
file.read(ByteBuffer.wrap(header), 0);
Properties prop = new Properties();
prop.load(new ByteArrayInputStream(header));
prop.load(new StringReader(new String(header, "UTF-8")));
writer.println("file " + fileName);
writer.println(" length " + fileLength);
writer.println(" " + prop);
ByteBuffer block = ByteBuffer.allocate(40);
for (long pos = 0; pos < fileLength;) {
block.rewind();
DataUtils.readFully(file, pos, block);
block.rewind();
if (block.get() != 'c') {
pos += blockSize;
continue;
}
int chunkLength = block.getInt();
int chunkId = block.getInt();
int pageCount = block.getInt();
long metaRootPos = block.getLong();
long maxLength = block.getLong();
long maxLengthLive = block.getLong();
writer.println(" chunk " + chunkId +
" at " + pos +
" length " + chunkLength +
" pageCount " + pageCount +
" root " + metaRootPos +
" maxLength " + maxLength +
" maxLengthLive " + maxLengthLive);
ByteBuffer chunk = ByteBuffer.allocate(chunkLength);
DataUtils.readFully(file, pos, chunk);
int p = block.position();
pos = (pos + chunkLength + blockSize) / blockSize * blockSize;
chunkLength -= p;
while (chunkLength > 0) {
chunk.position(p);
int pageLength = chunk.getInt();
// check value (ignored)
chunk.getShort();
long mapId = DataUtils.readVarInt(chunk);
int len = DataUtils.readVarInt(chunk);
int type = chunk.get();
boolean compressed = (type & 2) != 0;
boolean node = (type & 1) != 0;
writer.println(" map " + mapId + " at " + p + " " +
(node ? "node" : "leaf") + " " +
(compressed ? "compressed " : "") +
"len: " + pageLength + " entries: " + len);
p += pageLength;
chunkLength -= pageLength;
}
}
} catch (IOException e) {
writer.println("ERROR: " + e);
throw e;
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
// ignore
}
}
}
writer.println();
writer.flush();
}
}
<!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>
A persistent storage for tree maps.
</p></body></html>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论