提交 570437e8 authored 作者: Thomas Mueller's avatar Thomas Mueller

Improved performance for read operations.

上级 44027adc
...@@ -12,13 +12,15 @@ import java.util.Iterator; ...@@ -12,13 +12,15 @@ 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 * @param <K> the key type
* @param <V> the value type
*/ */
public class Cursor<K> implements Iterator<K> { public class Cursor<K, V> implements Iterator<K> {
private final MVMap<K, ?> map; private final MVMap<K, ?> map;
private final K from; private final K from;
private CursorPos pos; private CursorPos pos;
private K current; private K current;
private V currentValue, lastValue;
private final Page root; private final Page root;
private boolean initialized; private boolean initialized;
...@@ -42,9 +44,19 @@ public class Cursor<K> implements Iterator<K> { ...@@ -42,9 +44,19 @@ public class Cursor<K> implements Iterator<K> {
public K next() { public K next() {
hasNext(); hasNext();
K c = current; K c = current;
lastValue = currentValue;
fetchNext(); fetchNext();
return c; return c;
} }
/**
* Get the last read value if there was one.
*
* @return the value or null
*/
public V getValue() {
return lastValue;
}
/** /**
* Skip over that many entries. This method is relatively fast (for this map * Skip over that many entries. This method is relatively fast (for this map
...@@ -110,7 +122,9 @@ public class Cursor<K> implements Iterator<K> { ...@@ -110,7 +122,9 @@ public class Cursor<K> implements Iterator<K> {
private void fetchNext() { private void fetchNext() {
while (pos != null) { while (pos != null) {
if (pos.index < pos.page.getKeyCount()) { if (pos.index < pos.page.getKeyCount()) {
current = (K) pos.page.getKey(pos.index++); int index = pos.index++;
current = (K) pos.page.getKey(index);
currentValue = (V) pos.page.getValue(index);
return; return;
} }
pos = pos.parent; pos = pos.parent;
......
...@@ -17,6 +17,8 @@ import java.util.ArrayList; ...@@ -17,6 +17,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.util.New; import org.h2.util.New;
...@@ -852,5 +854,39 @@ public class DataUtils { ...@@ -852,5 +854,39 @@ public class DataUtils {
return errorValue; return errorValue;
} }
} }
/**
* An entry of a map.
*
* @param <K> the key type
* @param <V> the value type
*/
public static class MapEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
throw DataUtils.newUnsupportedOperationException(
"Updating the value is not supported");
}
}
} }
...@@ -745,20 +745,30 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -745,20 +745,30 @@ public class MVMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* Iterate over all keys. * Iterate over a number of keys.
* *
* @param from the first key to return * @param from the first key to return
* @return the iterator * @return the iterator
*/ */
public Cursor<K> keyIterator(K from) { public Iterator<K> keyIterator(K from) {
return new Cursor<K>(this, root, from); return new Cursor<K, V>(this, root, from);
}
/**
* Get a cursor to iterate over a number of keys and values.
*
* @param from the first key to return
* @return the cursor
*/
public Cursor<K, V> cursor(K from) {
return new Cursor<K, V>(this, root, from);
} }
@Override @Override
public Set<Map.Entry<K, V>> entrySet() { public Set<Map.Entry<K, V>> entrySet() {
HashMap<K, V> map = new HashMap<K, V>(); HashMap<K, V> map = new HashMap<K, V>();
for (K k : keySet()) { for (Cursor<K, V> cursor = cursor(null); cursor.hasNext();) {
map.put(k, get(k)); map.put(cursor.next(), cursor.getValue());
} }
return map.entrySet(); return map.entrySet();
} }
...@@ -771,7 +781,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -771,7 +781,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
@Override @Override
public Iterator<K> iterator() { public Iterator<K> iterator() {
return new Cursor<K>(map, root, null); return new Cursor<K, V>(map, root, null);
} }
@Override @Override
......
...@@ -17,6 +17,7 @@ import org.h2.result.SortOrder; ...@@ -17,6 +17,7 @@ import org.h2.result.SortOrder;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.value.ValueLong;
/** /**
* An index that delegates indexing to another index. * An index that delegates indexing to another index.
...@@ -53,10 +54,10 @@ public class MVDelegateIndex extends BaseIndex { ...@@ -53,10 +54,10 @@ public class MVDelegateIndex extends BaseIndex {
@Override @Override
public Cursor find(Session session, SearchRow first, SearchRow last) { public Cursor find(Session session, SearchRow first, SearchRow last) {
long min = mainIndex.getKey(first, Long.MIN_VALUE, Long.MIN_VALUE); ValueLong min = mainIndex.getKey(first, MVPrimaryIndex.MIN, MVPrimaryIndex.MIN);
// ifNull is MIN_VALUE as well, because the column is never NULL // ifNull is MIN_VALUE as well, because the column is never NULL
// so avoid returning all rows (returning one row is OK) // so avoid returning all rows (returning one row is OK)
long max = mainIndex.getKey(last, Long.MAX_VALUE, Long.MIN_VALUE); ValueLong max = mainIndex.getKey(last, MVPrimaryIndex.MAX, MVPrimaryIndex.MIN);
return mainIndex.find(session, min, max); return mainIndex.find(session, min, max);
} }
......
...@@ -9,6 +9,8 @@ package org.h2.mvstore.db; ...@@ -9,6 +9,8 @@ package org.h2.mvstore.db;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
...@@ -18,6 +20,7 @@ import org.h2.index.BaseIndex; ...@@ -18,6 +20,7 @@ import org.h2.index.BaseIndex;
import org.h2.index.Cursor; import org.h2.index.Cursor;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.db.TransactionStore.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.mvstore.db.TransactionStore.TransactionMap; import org.h2.mvstore.db.TransactionStore.TransactionMap;
import org.h2.result.Row; import org.h2.result.Row;
...@@ -36,6 +39,10 @@ import org.h2.value.ValueNull; ...@@ -36,6 +39,10 @@ import org.h2.value.ValueNull;
*/ */
public class MVPrimaryIndex extends BaseIndex { public class MVPrimaryIndex extends BaseIndex {
static final ValueLong MIN = ValueLong.get(Long.MIN_VALUE);
static final ValueLong MAX = ValueLong.get(Long.MAX_VALUE);
static final ValueLong ZERO = ValueLong.get(0);
private final MVTable mvTable; private final MVTable mvTable;
private final String mapName; private final String mapName;
private TransactionMap<Value, Value> dataMap; private TransactionMap<Value, Value> dataMap;
...@@ -152,30 +159,30 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -152,30 +159,30 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public Cursor find(Session session, SearchRow first, SearchRow last) { public Cursor find(Session session, SearchRow first, SearchRow last) {
long min, max; ValueLong min, max;
if (first == null || mainIndexColumn < 0) { if (first == null || mainIndexColumn < 0) {
min = Long.MIN_VALUE; min = MIN;
} else { } else {
Value v = first.getValue(mainIndexColumn); ValueLong v = (ValueLong) first.getValue(mainIndexColumn);
if (v == null) { if (v == null) {
min = 0; min = ZERO;
} else { } else {
min = v.getLong(); min = v;
} }
} }
if (last == null || mainIndexColumn < 0) { if (last == null || mainIndexColumn < 0) {
max = Long.MAX_VALUE; max = MAX;
} else { } else {
Value v = last.getValue(mainIndexColumn); ValueLong v = (ValueLong) last.getValue(mainIndexColumn);
if (v == null) { if (v == null) {
max = Long.MAX_VALUE; max = MAX;
} else { } else {
max = v.getLong(); max = v;
} }
} }
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator( return new MVStoreCursor(map.entryIterator(
ValueLong.get(min)), max); min), max);
} }
@Override @Override
...@@ -196,7 +203,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -196,7 +203,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) { public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) {
try { try {
long cost = 10 * (dataMap.map.sizeAsLong() + Constants.COST_ROW_OFFSET); long cost = 10 * (dataMap.sizeAsLongEstimated() + Constants.COST_ROW_OFFSET);
return cost; return cost;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
...@@ -236,15 +243,15 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -236,15 +243,15 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public Cursor findFirstOrLast(Session session, boolean first) { public Cursor findFirstOrLast(Session session, boolean first) {
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
Value v = first ? map.firstKey() : map.lastKey(); ValueLong v = (ValueLong) (first ? map.firstKey() : map.lastKey());
if (v == null) { if (v == null) {
return new MVStoreCursor(session, Collections.<Value>emptyList().iterator(), 0); return new MVStoreCursor(Collections.<Entry<Value, Value>>emptyList().iterator(), null);
} }
long key = v.getLong(); Value value = map.get(v);
MVStoreCursor cursor = new MVStoreCursor(session, Entry<Value, Value> e = new DataUtils.MapEntry<Value, Value>(v, value);
Arrays.asList((Value) ValueLong.get(key)).iterator(), key); @SuppressWarnings("unchecked")
cursor.next(); List<Entry<Value, Value>> list = Arrays.asList(e);
return cursor; return new MVStoreCursor(list.iterator(), v);
} }
@Override @Override
...@@ -261,7 +268,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -261,7 +268,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
try { try {
return dataMap.map.sizeAsLong(); return dataMap.sizeAsLongEstimated();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -290,7 +297,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -290,7 +297,7 @@ public class MVPrimaryIndex extends BaseIndex {
* @param ifNull the value to use if the column is NULL * @param ifNull the value to use if the column is NULL
* @return the key * @return the key
*/ */
long getKey(SearchRow row, long ifEmpty, long ifNull) { ValueLong getKey(SearchRow row, ValueLong ifEmpty, ValueLong ifNull) {
if (row == null) { if (row == null) {
return ifEmpty; return ifEmpty;
} }
...@@ -300,7 +307,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -300,7 +307,7 @@ public class MVPrimaryIndex extends BaseIndex {
} else if (v == ValueNull.INSTANCE) { } else if (v == ValueNull.INSTANCE) {
return ifNull; return ifNull;
} }
return v.getLong(); return (ValueLong) v.convertTo(Value.LONG);
} }
/** /**
...@@ -311,9 +318,9 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -311,9 +318,9 @@ public class MVPrimaryIndex extends BaseIndex {
* @param last the key of the last row * @param last the key of the last row
* @return the cursor * @return the cursor
*/ */
Cursor find(Session session, long first, long last) { Cursor find(Session session, ValueLong first, ValueLong last) {
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator(ValueLong.get(first)), last); return new MVStoreCursor(map.entryIterator(first), last);
} }
@Override @Override
...@@ -340,14 +347,12 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -340,14 +347,12 @@ public class MVPrimaryIndex extends BaseIndex {
*/ */
class MVStoreCursor implements Cursor { class MVStoreCursor implements Cursor {
private final Session session; private final Iterator<Entry<Value, Value>> it;
private final Iterator<Value> it; private final ValueLong last;
private final long last; private Entry<Value, Value> current;
private ValueLong current;
private Row row; private Row row;
public MVStoreCursor(Session session, Iterator<Value> it, long last) { public MVStoreCursor(Iterator<Entry<Value, Value>> it, ValueLong last) {
this.session = session;
this.it = it; this.it = it;
this.last = last; this.last = last;
} }
...@@ -356,7 +361,9 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -356,7 +361,9 @@ public class MVPrimaryIndex extends BaseIndex {
public Row get() { public Row get() {
if (row == null) { if (row == null) {
if (current != null) { if (current != null) {
row = getRow(session, current.getLong()); ValueArray array = (ValueArray) current.getValue();
row = new Row(array.getList(), 0);
row.setKey(current.getKey().getLong());
} }
} }
return row; return row;
...@@ -369,8 +376,8 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -369,8 +376,8 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public boolean next() { public boolean next() {
current = (ValueLong) it.next(); current = it.next();
if (current != null && current.getLong() > last) { if (current != null && current.getKey().getLong() > last.getLong()) {
current = null; current = null;
} }
row = null; row = null;
......
...@@ -66,7 +66,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -66,7 +66,7 @@ public class MVSecondaryIndex extends BaseIndex {
ValueDataType valueType = new ValueDataType(null, null, null); ValueDataType valueType = new ValueDataType(null, null, null);
dataMap = mvTable.getTransaction(null).openMap( dataMap = mvTable.getTransaction(null).openMap(
mapName, keyType, valueType); mapName, keyType, valueType);
if (keyType != dataMap.map.getKeyType()) { if (keyType != dataMap.getKeyType()) {
throw DbException.throwInternalError("Incompatible key type"); throw DbException.throwInternalError("Incompatible key type");
} }
} }
...@@ -102,6 +102,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -102,6 +102,7 @@ public class MVSecondaryIndex extends BaseIndex {
} }
if (indexType.isUnique()) { if (indexType.isUnique()) {
// check if there is another (uncommitted) entry // check if there is another (uncommitted) entry
// TODO use entry iterator
Iterator<Value> it = map.keyIterator(unique, true); Iterator<Value> it = map.keyIterator(unique, true);
while (it.hasNext()) { while (it.hasNext()) {
ValueArray k = (ValueArray) it.next(); ValueArray k = (ValueArray) it.next();
...@@ -195,7 +196,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -195,7 +196,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) { public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) {
try { try {
return 10 * getCostRangeIndex(masks, dataMap.map.sizeAsLong(), filter, sortOrder); return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongEstimated(), filter, sortOrder);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -244,7 +245,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -244,7 +245,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public boolean needRebuild() { public boolean needRebuild() {
try { try {
return dataMap.map.sizeAsLong() == 0; return dataMap.sizeAsLongEstimated() == 0;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -259,7 +260,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -259,7 +260,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
try { try {
return dataMap.map.sizeAsLong(); return dataMap.sizeAsLongEstimated();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
......
...@@ -282,7 +282,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex { ...@@ -282,7 +282,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex {
@Override @Override
public boolean needRebuild() { public boolean needRebuild() {
try { try {
return dataMap.map.sizeAsLong() == 0; return dataMap.sizeAsLongEstimated() == 0;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -297,7 +297,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex { ...@@ -297,7 +297,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex {
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
try { try {
return dataMap.map.sizeAsLong(); return dataMap.sizeAsLongEstimated();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
......
/*
* Copyright 2004-2013 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.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import org.h2.mvstore.Chunk;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.Page;
import org.h2.mvstore.db.TransactionStore;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.util.Profiler;
import org.h2.value.ValueLong;
import org.h2.value.ValueString;
/**
* Tests performance and helps analyze bottlenecks.
*/
public class TestBenchmark extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
test(true);
test(false);
test(true);
test(false);
test(true);
test(false);
}
private void test(boolean mvStore) throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true);
Connection conn;
Statement stat;
String url = "mvstore";
if (mvStore) {
url += ";MV_STORE=TRUE;LOG=0";
}
// 2033 mvstore
// 2313 (2075?) default
url = getURL(url, true);
conn = getConnection(url);
stat = conn.createStatement();
stat.execute("create table test(id bigint primary key, name varchar)");
conn.setAutoCommit(false);
PreparedStatement prep = conn.prepareStatement("insert into test values(?, ?)");
String data = "Hello World";
int rowCount = 100000;
int readCount = 20* rowCount;
for (int i = 0; i < rowCount; i++) {
prep.setInt(1, i);
prep.setString(2, data);
prep.execute();
if (i % 100 == 0) {
conn.commit();
}
}
long start = System.currentTimeMillis();
// Profiler prof = new Profiler();
// prof.sumClasses=true;
// prof.startCollecting();
;
prep = conn.prepareStatement("select * from test where id = ?");
for (int i = 0; i < readCount; i++) {
prep.setInt(1, i % rowCount);
prep.executeQuery();
}
//System.out.println("Transactionstore.counter " + ValueLong.counter);
//System.out.println("count " + Page.writeCount + " avgLen " + (1.0*Page.writeLength/Page.writeCount) + " avgSize " + (1.0*Page.writeSize/Page.writeCount));
//System.out.println(prof.getTop(5));
//System.out.println("ountUnc:" + counterUnc);
//System.out.println("ount:" + counter);
System.out.println((System.currentTimeMillis() - start) + " " + (mvStore ? "mvstore" : "default"));
conn.close();
// MVStore s = new MVStore.Builder().fileName(getBaseDir() + "/mvstore.mv.db").open();
// int count = 0;
// long length = 0;
// for(String k : s.getMetaMap().keyList()) {
// if (k.startsWith("chunk.")) {
// String x = s.getMetaMap().get(k);
// Chunk c = Chunk.fromString(x);
// if (c.length < Integer.MAX_VALUE) {
// count++;
// length += c.length;
// }
// }
// }
// if (count > 0) {
// System.out.println("chunks: " + count + " average length: " + (length / count));
// }
// s.close();
}
}
...@@ -706,7 +706,7 @@ public class TestMVStore extends TestBase { ...@@ -706,7 +706,7 @@ public class TestMVStore extends TestBase {
map.put(i, 10 * i); map.put(i, 10 * i);
} }
Cursor<Integer> c = map.keyIterator(50); Cursor<Integer, Integer> c = map.cursor(50);
// skip must reset the root of the cursor // skip must reset the root of the cursor
c.skip(10); c.skip(10);
for (int i = 70; i < 100; i += 2) { for (int i = 70; i < 100; i += 2) {
...@@ -732,7 +732,7 @@ public class TestMVStore extends TestBase { ...@@ -732,7 +732,7 @@ public class TestMVStore extends TestBase {
} }
} }
// skip // skip
c = map.keyIterator(0); c = map.cursor(0);
assertTrue(c.hasNext()); assertTrue(c.hasNext());
assertEquals(0, c.next().intValue()); assertEquals(0, c.next().intValue());
c.skip(0); c.skip(0);
...@@ -742,11 +742,11 @@ public class TestMVStore extends TestBase { ...@@ -742,11 +742,11 @@ public class TestMVStore extends TestBase {
c.skip(20); c.skip(20);
assertEquals(48, c.next().intValue()); assertEquals(48, c.next().intValue());
c = map.keyIterator(0); c = map.cursor(0);
c.skip(20); c.skip(20);
assertEquals(40, c.next().intValue()); assertEquals(40, c.next().intValue());
c = map.keyIterator(0); c = map.cursor(0);
assertEquals(0, c.next().intValue()); assertEquals(0, c.next().intValue());
assertEquals(12, map.keyList().indexOf(24)); assertEquals(12, map.keyList().indexOf(24));
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论