Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录/注册
切换导航
H
h2database
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
Administrator
h2database
Commits
337f0a4f
提交
337f0a4f
authored
10 年前
作者:
Thomas Mueller
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
MVStore: support for concurrent reads and writes is now enabled by default.
上级
a17b66d1
隐藏空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
177 行增加
和
453 行删除
+177
-453
ConcurrentArrayList.java
h2/src/main/org/h2/mvstore/ConcurrentArrayList.java
+0
-1
MVMap.java
h2/src/main/org/h2/mvstore/MVMap.java
+50
-116
MVMapConcurrent.java
h2/src/main/org/h2/mvstore/MVMapConcurrent.java
+3
-71
MVStore.java
h2/src/main/org/h2/mvstore/MVStore.java
+2
-3
Page.java
h2/src/main/org/h2/mvstore/Page.java
+83
-209
MVRTreeMap.java
h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java
+39
-53
没有找到文件。
h2/src/main/org/h2/mvstore/ConcurrentArrayList.java
浏览文件 @
337f0a4f
...
...
@@ -8,7 +8,6 @@ package org.h2.mvstore;
import
java.util.Arrays
;
import
java.util.Iterator
;
/**
* A very simple array list that supports concurrent access.
* Internally, it uses immutable objects.
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/mvstore/MVMap.java
浏览文件 @
337f0a4f
...
...
@@ -21,6 +21,15 @@ import org.h2.util.New;
/**
* A stored map.
* <p>
* Read operations can happen concurrently with all other
* operations, without risk of corruption.
* <p>
* Write operations first read the relevant area from disk to memory
* concurrently, and only then modify the data. The in-memory part of write
* operations is synchronized. For scalable concurrent in-memory write
* operations, the map should be split into multiple smaller sub-maps that are
* then synchronized independently.
*
* @param <K> the key class
* @param <V> the value class
...
...
@@ -43,11 +52,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/
protected
volatile
long
writeVersion
;
/**
* This version is set during a write operation.
*/
protected
volatile
long
currentWriteVersion
=
-
1
;
private
int
id
;
private
long
createVersion
;
private
final
DataType
keyType
;
...
...
@@ -99,21 +103,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
this
.
writeVersion
=
store
.
getCurrentVersion
();
}
/**
* Create a copy of a page, if the write version is higher than the current
* version. If a copy is created, the old page is marked as deleted.
*
* @param p the page
* @param writeVersion the write version
* @return a page with the given write version
*/
protected
Page
copyOnWrite
(
Page
p
,
long
writeVersion
)
{
if
(
p
.
getVersion
()
==
writeVersion
)
{
return
p
;
}
return
p
.
copy
(
writeVersion
);
}
/**
* Add or replace a key-value pair.
*
...
...
@@ -126,16 +115,12 @@ public class MVMap<K, V> extends AbstractMap<K, V>
public
synchronized
V
put
(
K
key
,
V
value
)
{
DataUtils
.
checkArgument
(
value
!=
null
,
"The value may not be null"
);
beforeWrite
();
try
{
long
v
=
writeVersion
;
Page
p
=
copyOnWrite
(
root
,
v
);
p
=
splitRootIfNeeded
(
p
,
v
);
Object
result
=
put
(
p
,
v
,
key
,
value
);
newRoot
(
p
);
return
(
V
)
result
;
}
finally
{
afterWrite
();
}
long
v
=
writeVersion
;
Page
p
=
root
.
copy
(
v
);
p
=
splitRootIfNeeded
(
p
,
v
);
Object
result
=
put
(
p
,
v
,
key
,
value
);
newRoot
(
p
);
return
(
V
)
result
;
}
/**
...
...
@@ -155,14 +140,13 @@ public class MVMap<K, V> extends AbstractMap<K, V>
Page
split
=
p
.
split
(
at
);
Object
[]
keys
=
{
k
};
Page
.
PageReference
[]
children
=
{
new
Page
.
PageReference
(
p
,
p
.
getPos
()),
new
Page
.
PageReference
(
split
,
split
.
getPos
()),
new
Page
.
PageReference
(
p
,
p
.
getPos
()
,
p
.
getTotalCount
()
),
new
Page
.
PageReference
(
split
,
split
.
getPos
()
,
split
.
getTotalCount
()
),
};
long
[]
counts
=
{
p
.
getTotalCount
(),
split
.
getTotalCount
()
};
p
=
Page
.
create
(
this
,
writeVersion
,
1
,
keys
,
null
,
2
,
children
,
counts
,
totalCount
,
0
,
0
);
keys
,
null
,
children
,
totalCount
,
0
);
return
p
;
}
...
...
@@ -191,21 +175,19 @@ public class MVMap<K, V> extends AbstractMap<K, V>
}
else
{
index
++;
}
Page
c
=
copyOnWrite
(
p
.
getChildPage
(
index
),
writeVersion
);
Page
c
=
p
.
getChildPage
(
index
).
copy
(
writeVersion
);
if
(
c
.
getMemory
()
>
store
.
getPageSplitSize
()
&&
c
.
getKeyCount
()
>
1
)
{
// split on the way down
int
at
=
c
.
getKeyCount
()
/
2
;
Object
k
=
c
.
getKey
(
at
);
Page
split
=
c
.
split
(
at
);
p
.
setChild
(
index
,
split
);
p
.
setCounts
(
index
,
split
);
p
.
insertNode
(
index
,
k
,
c
);
// now we are not sure where to add
return
put
(
p
,
writeVersion
,
key
,
value
);
}
p
.
setChild
(
index
,
c
);
Object
result
=
put
(
c
,
writeVersion
,
key
,
value
);
p
.
setC
ounts
(
index
,
c
);
p
.
setC
hild
(
index
,
c
);
return
result
;
}
...
...
@@ -510,12 +492,8 @@ public class MVMap<K, V> extends AbstractMap<K, V>
@Override
public
synchronized
void
clear
()
{
beforeWrite
();
try
{
root
.
removeAllRecursive
();
newRoot
(
Page
.
createEmpty
(
this
,
writeVersion
));
}
finally
{
afterWrite
();
}
root
.
removeAllRecursive
();
newRoot
(
Page
.
createEmpty
(
this
,
writeVersion
));
}
/**
...
...
@@ -537,22 +515,24 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* @return the old value if the key existed, or null otherwise
*/
@Override
public
synchronized
V
remove
(
Object
key
)
{
@SuppressWarnings
(
"unchecked"
)
public
V
remove
(
Object
key
)
{
beforeWrite
();
try
{
long
v
=
writeVersion
;
Page
p
=
copyOnWrite
(
root
,
v
);
@SuppressWarnings
(
"unchecked"
)
V
result
=
(
V
)
remove
(
p
,
v
,
key
);
V
result
=
get
(
key
);
if
(
result
==
null
)
{
return
null
;
}
long
v
=
writeVersion
;
synchronized
(
this
)
{
Page
p
=
root
.
copy
(
v
);
result
=
(
V
)
remove
(
p
,
v
,
key
);
if
(!
p
.
isLeaf
()
&&
p
.
getTotalCount
()
==
0
)
{
p
.
removePage
();
p
=
Page
.
createEmpty
(
this
,
p
.
getVersion
());
}
newRoot
(
p
);
return
result
;
}
finally
{
afterWrite
();
}
return
result
;
}
/**
...
...
@@ -664,18 +644,16 @@ public class MVMap<K, V> extends AbstractMap<K, V>
index
++;
}
Page
cOld
=
p
.
getChildPage
(
index
);
Page
c
=
c
opyOnWrite
(
cOld
,
writeVersion
);
Page
c
=
c
Old
.
copy
(
writeVersion
);
result
=
remove
(
c
,
writeVersion
,
key
);
if
(
result
==
null
||
c
.
getTotalCount
()
!=
0
)
{
// no change, or
// there are more nodes
p
.
setChild
(
index
,
c
);
p
.
setCounts
(
index
,
c
);
}
else
{
// this child was deleted
if
(
p
.
getKeyCount
()
==
0
)
{
p
.
setChild
(
index
,
c
);
p
.
setCounts
(
index
,
c
);
c
.
removePage
();
}
else
{
p
.
remove
(
index
);
...
...
@@ -975,25 +953,21 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/
void
rollbackTo
(
long
version
)
{
beforeWrite
();
try
{
if
(
version
<=
createVersion
)
{
// the map is removed later
}
else
if
(
root
.
getVersion
()
>=
version
)
{
while
(
true
)
{
Page
last
=
oldRoots
.
peekLast
();
if
(
last
==
null
)
{
break
;
}
// slow, but rollback is not a common operation
oldRoots
.
removeLast
(
last
);
root
=
last
;
if
(
root
.
getVersion
()
<
version
)
{
break
;
}
if
(
version
<=
createVersion
)
{
// the map is removed later
}
else
if
(
root
.
getVersion
()
>=
version
)
{
while
(
true
)
{
Page
last
=
oldRoots
.
peekLast
();
if
(
last
==
null
)
{
break
;
}
// slow, but rollback is not a common operation
oldRoots
.
removeLast
(
last
);
root
=
last
;
if
(
root
.
getVersion
()
<
version
)
{
break
;
}
}
}
finally
{
afterWrite
();
}
}
...
...
@@ -1056,45 +1030,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
throw
DataUtils
.
newUnsupportedOperationException
(
"This map is read-only"
);
}
checkConcurrentWrite
();
store
.
beforeWrite
();
currentWriteVersion
=
writeVersion
;
}
/**
* Check that no write operation is in progress.
*/
protected
void
checkConcurrentWrite
()
{
if
(
currentWriteVersion
!=
-
1
)
{
// try to detect concurrent modification
// on a best-effort basis
throw
DataUtils
.
newConcurrentModificationException
(
getName
());
}
}
/**
* This method is called after writing to the map (whether or not the write
* operation was successful).
*/
protected
void
afterWrite
()
{
currentWriteVersion
=
-
1
;
}
/**
* If there is a concurrent update to the given version, wait until it is
* finished.
*
* @param version the read version
*/
protected
void
waitUntilWritten
(
long
version
)
{
if
(
readOnly
)
{
throw
DataUtils
.
newIllegalStateException
(
DataUtils
.
ERROR_INTERNAL
,
"Waiting for writes to a read-only map"
);
}
while
(
currentWriteVersion
==
version
)
{
Thread
.
yield
();
}
}
@Override
...
...
@@ -1267,7 +1203,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
void
copyFrom
(
MVMap
<
K
,
V
>
sourceMap
)
{
beforeWrite
();
newRoot
(
copy
(
sourceMap
.
root
,
null
));
afterWrite
();
}
private
Page
copy
(
Page
source
,
CursorPos
parent
)
{
...
...
@@ -1276,11 +1211,10 @@ public class MVMap<K, V> extends AbstractMap<K, V>
Page
child
=
target
;
for
(
CursorPos
p
=
parent
;
p
!=
null
;
p
=
p
.
parent
)
{
p
.
page
.
setChild
(
p
.
index
,
child
);
p
.
page
=
copyOnWrite
(
p
.
page
,
writeVersion
);
p
.
page
=
p
.
page
.
copy
(
writeVersion
);
child
=
p
.
page
;
if
(
p
.
parent
==
null
)
{
newRoot
(
p
.
page
);
afterWrite
();
beforeWrite
();
}
}
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/mvstore/MVMapConcurrent.java
浏览文件 @
337f0a4f
...
...
@@ -9,17 +9,10 @@ import org.h2.mvstore.type.DataType;
import
org.h2.mvstore.type.ObjectDataType
;
/**
* A stored map. Read operations can happen concurrently with all other
* operations, without risk of corruption.
* <p>
* Write operations first read the relevant area from disk to memory
* concurrently, and only then modify the data. The in-memory part of write
* operations is synchronized. For scalable concurrent in-memory write
* operations, the map should be split into multiple smaller sub-maps that are
* then synchronized independently.
* A class used for backward compatibility.
*
* @param <K> the key
class
* @param <V> the value
class
* @param <K> the key
type
* @param <V> the value
type
*/
public
class
MVMapConcurrent
<
K
,
V
>
extends
MVMap
<
K
,
V
>
{
...
...
@@ -27,67 +20,6 @@ public class MVMapConcurrent<K, V> extends MVMap<K, V> {
super
(
keyType
,
valueType
);
}
@Override
protected
Page
copyOnWrite
(
Page
p
,
long
writeVersion
)
{
return
p
.
copy
(
writeVersion
);
}
@Override
protected
void
checkConcurrentWrite
()
{
// ignore (writes are synchronized)
}
@Override
@SuppressWarnings
(
"unchecked"
)
public
V
put
(
K
key
,
V
value
)
{
beforeWrite
();
try
{
// even if the result is the same, we still update the value
// (otherwise compact doesn't work)
get
(
key
);
long
v
=
writeVersion
;
synchronized
(
this
)
{
Page
p
=
copyOnWrite
(
root
,
v
);
p
=
splitRootIfNeeded
(
p
,
v
);
V
result
=
(
V
)
put
(
p
,
v
,
key
,
value
);
newRoot
(
p
);
return
result
;
}
}
finally
{
afterWrite
();
}
}
@Override
protected
void
waitUntilWritten
(
long
version
)
{
// no need to wait
}
@Override
@SuppressWarnings
(
"unchecked"
)
public
V
remove
(
Object
key
)
{
beforeWrite
();
try
{
V
result
=
get
(
key
);
if
(
result
==
null
)
{
return
null
;
}
long
v
=
writeVersion
;
synchronized
(
this
)
{
Page
p
=
copyOnWrite
(
root
,
v
);
result
=
(
V
)
remove
(
p
,
v
,
key
);
if
(!
p
.
isLeaf
()
&&
p
.
getTotalCount
()
==
0
)
{
p
.
removePage
();
p
=
Page
.
createEmpty
(
this
,
p
.
getVersion
());
}
newRoot
(
p
);
}
return
result
;
}
finally
{
afterWrite
();
}
}
/**
* A builder for this class.
*
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/mvstore/MVStore.java
浏览文件 @
337f0a4f
...
...
@@ -190,7 +190,7 @@ public class MVStore {
* The metadata map. Write access to this map needs to be synchronized on
* the store.
*/
private
MVMap
Concurrent
<
String
,
String
>
meta
;
private
MVMap
<
String
,
String
>
meta
;
private
final
ConcurrentHashMap
<
Integer
,
MVMap
<?,
?>>
maps
=
new
ConcurrentHashMap
<
Integer
,
MVMap
<?,
?>>();
...
...
@@ -283,7 +283,7 @@ public class MVStore {
}
o
=
config
.
get
(
"backgroundExceptionHandler"
);
this
.
backgroundExceptionHandler
=
(
UncaughtExceptionHandler
)
o
;
meta
=
new
MVMap
Concurrent
<
String
,
String
>(
StringDataType
.
INSTANCE
,
meta
=
new
MVMap
<
String
,
String
>(
StringDataType
.
INSTANCE
,
StringDataType
.
INSTANCE
);
HashMap
<
String
,
Object
>
c
=
New
.
hashMap
();
c
.
put
(
"id"
,
0
);
...
...
@@ -1010,7 +1010,6 @@ public class MVStore {
continue
;
}
if
(
v
>=
0
&&
v
>=
lastStoredVersion
)
{
m
.
waitUntilWritten
(
storeVersion
);
MVMap
<?,
?>
r
=
m
.
openVersion
(
storeVersion
);
if
(
r
.
getRoot
().
getPos
()
==
0
)
{
changed
.
add
(
r
);
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/mvstore/Page.java
浏览文件 @
337f0a4f
...
...
@@ -29,10 +29,10 @@ import org.h2.mvstore.type.DataType;
*/
public
class
Page
{
private
static
final
int
SHARED_KEYS
=
1
,
SHARED_VALUES
=
2
,
SHARED_CHILDREN
=
4
,
SHARED_COUNTS
=
8
;
p
rivate
static
final
Object
[]
EMPTY_OBJECT_ARRAY
=
new
Object
[
0
];
/**
* An empty object array.
*/
p
ublic
static
final
Object
[]
EMPTY_OBJECT_ARRAY
=
new
Object
[
0
];
private
final
MVMap
<?,
?>
map
;
private
long
version
;
...
...
@@ -43,26 +43,11 @@ public class Page {
*/
private
long
totalCount
;
/**
* The number of keys.
*/
private
int
keyCount
;
/**
* The number of children.
*/
private
int
childCount
;
/**
* The last result of a find operation is cached.
*/
private
int
cachedCompare
;
/**
* Which arrays are shared with another version of this page.
*/
private
int
sharedFlags
;
/**
* The estimated memory used.
*/
...
...
@@ -89,13 +74,6 @@ public class Page {
*/
private
PageReference
[]
children
;
/**
* The descendant count for each child page.
* <p>
* The array might be larger than needed, to avoid frequent re-sizing.
*/
private
long
[]
counts
;
/**
* Whether the page is an in-memory (not stored, or not yet stored) page,
* and it is removed. This is to keep track of pages that concurrently
...
...
@@ -118,9 +96,9 @@ public class Page {
*/
static
Page
createEmpty
(
MVMap
<?,
?>
map
,
long
version
)
{
return
create
(
map
,
version
,
0
,
EMPTY_OBJECT_ARRAY
,
EMPTY_OBJECT_ARRAY
,
0
,
null
,
null
,
0
,
0
,
DataUtils
.
PAGE_MEMORY
);
EMPTY_OBJECT_ARRAY
,
EMPTY_OBJECT_ARRAY
,
null
,
0
,
DataUtils
.
PAGE_MEMORY
);
}
/**
...
...
@@ -128,31 +106,22 @@ public class Page {
*
* @param map the map
* @param version the version
* @param keyCount the number of keys
* @param keys the keys
* @param values the values
* @param childCount the number of children
* @param children the child page positions
* @param counts the children counts
* @param totalCount the total number of keys
* @param sharedFlags which arrays are shared
* @param memory the memory used in bytes
* @return the page
*/
public
static
Page
create
(
MVMap
<?,
?>
map
,
long
version
,
int
keyCount
,
Object
[]
keys
,
Object
[]
values
,
int
childCount
,
PageReference
[]
children
,
long
[]
counts
,
long
totalCount
,
int
sharedFlags
,
int
memory
)
{
public
static
Page
create
(
MVMap
<?,
?>
map
,
long
version
,
Object
[]
keys
,
Object
[]
values
,
PageReference
[]
children
,
long
totalCount
,
int
memory
)
{
Page
p
=
new
Page
(
map
,
version
);
// the position is 0
p
.
keyCount
=
keyCount
;
p
.
keys
=
keys
;
p
.
values
=
values
;
p
.
childCount
=
childCount
;
p
.
children
=
children
;
p
.
counts
=
counts
;
p
.
totalCount
=
totalCount
;
p
.
sharedFlags
=
sharedFlags
;
if
(
memory
==
0
)
{
p
.
recalculateMemory
();
}
else
{
...
...
@@ -176,17 +145,10 @@ public class Page {
public
static
Page
create
(
MVMap
<?,
?>
map
,
long
version
,
Page
source
)
{
Page
p
=
new
Page
(
map
,
version
);
// the position is 0
p
.
keyCount
=
source
.
keyCount
;
p
.
keys
=
source
.
keys
;
if
(
source
.
isLeaf
())
{
p
.
values
=
source
.
values
;
}
else
{
p
.
childCount
=
source
.
childCount
;
p
.
children
=
source
.
children
;
p
.
counts
=
source
.
counts
;
}
p
.
values
=
source
.
values
;
p
.
children
=
source
.
children
;
p
.
totalCount
=
source
.
totalCount
;
p
.
sharedFlags
=
source
.
sharedFlags
;
p
.
memory
=
source
.
memory
;
MVStore
store
=
map
.
store
;
if
(
store
!=
null
)
{
...
...
@@ -296,7 +258,7 @@ public class Page {
* @return the number of keys
*/
public
int
getKeyCount
()
{
return
key
Count
;
return
key
s
.
length
;
}
/**
...
...
@@ -327,14 +289,14 @@ public class Page {
int
chunkId
=
DataUtils
.
getPageChunkId
(
pos
);
buff
.
append
(
"chunk: "
).
append
(
Long
.
toHexString
(
chunkId
)).
append
(
"\n"
);
}
for
(
int
i
=
0
;
i
<=
key
Count
;
i
++)
{
for
(
int
i
=
0
;
i
<=
key
s
.
length
;
i
++)
{
if
(
i
>
0
)
{
buff
.
append
(
" "
);
}
if
(
children
!=
null
)
{
buff
.
append
(
"["
+
Long
.
toHexString
(
children
[
i
].
pos
)
+
"] "
);
}
if
(
i
<
key
Count
)
{
if
(
i
<
key
s
.
length
)
{
buff
.
append
(
keys
[
i
]);
if
(
values
!=
null
)
{
buff
.
append
(
':'
);
...
...
@@ -353,9 +315,8 @@ public class Page {
*/
public
Page
copy
(
long
version
)
{
Page
newPage
=
create
(
map
,
version
,
keyCount
,
keys
,
values
,
childCount
,
children
,
counts
,
totalCount
,
SHARED_KEYS
|
SHARED_VALUES
|
SHARED_CHILDREN
|
SHARED_COUNTS
,
keys
,
values
,
children
,
totalCount
,
getMemory
());
// mark the old as deleted
removePage
();
...
...
@@ -375,7 +336,7 @@ public class Page {
* @return the value or null
*/
public
int
binarySearch
(
Object
key
)
{
int
low
=
0
,
high
=
key
Count
-
1
;
int
low
=
0
,
high
=
key
s
.
length
-
1
;
// the cached index minus one, so that
// for the first time (when cachedCompare is 0),
// the default value is used
...
...
@@ -400,7 +361,7 @@ public class Page {
return
-(
low
+
1
);
// regular binary search (without caching)
// int low = 0, high = key
Count
- 1;
// int low = 0, high = key
s.length
- 1;
// while (low <= high) {
// int x = (low + high) >>> 1;
// int compare = map.compare(key, keys[x]);
...
...
@@ -426,39 +387,36 @@ public class Page {
}
private
Page
splitLeaf
(
int
at
)
{
int
a
=
at
,
b
=
key
Count
-
a
;
int
a
=
at
,
b
=
key
s
.
length
-
a
;
Object
[]
aKeys
=
new
Object
[
a
];
Object
[]
bKeys
=
new
Object
[
b
];
System
.
arraycopy
(
keys
,
0
,
aKeys
,
0
,
a
);
System
.
arraycopy
(
keys
,
a
,
bKeys
,
0
,
b
);
keys
=
aKeys
;
keyCount
=
a
;
Object
[]
aValues
=
new
Object
[
a
];
Object
[]
bValues
=
new
Object
[
b
];
bValues
=
new
Object
[
b
];
System
.
arraycopy
(
values
,
0
,
aValues
,
0
,
a
);
System
.
arraycopy
(
values
,
a
,
bValues
,
0
,
b
);
values
=
aValues
;
sharedFlags
&=
~(
SHARED_KEYS
|
SHARED_VALUES
);
totalCount
=
a
;
Page
newPage
=
create
(
map
,
version
,
b
,
b
Keys
,
bValues
,
0
,
null
,
null
,
bKeys
.
length
,
0
,
0
);
bKeys
,
bValues
,
null
,
bKeys
.
length
,
0
);
recalculateMemory
();
newPage
.
recalculateMemory
();
return
newPage
;
}
private
Page
splitNode
(
int
at
)
{
int
a
=
at
,
b
=
key
Count
-
a
;
int
a
=
at
,
b
=
key
s
.
length
-
a
;
Object
[]
aKeys
=
new
Object
[
a
];
Object
[]
bKeys
=
new
Object
[
b
-
1
];
System
.
arraycopy
(
keys
,
0
,
aKeys
,
0
,
a
);
System
.
arraycopy
(
keys
,
a
+
1
,
bKeys
,
0
,
b
-
1
);
keys
=
aKeys
;
keyCount
=
a
;
PageReference
[]
aChildren
=
new
PageReference
[
a
+
1
];
PageReference
[]
bChildren
=
new
PageReference
[
b
];
...
...
@@ -466,27 +424,19 @@ public class Page {
System
.
arraycopy
(
children
,
a
+
1
,
bChildren
,
0
,
b
);
children
=
aChildren
;
long
[]
aCounts
=
new
long
[
a
+
1
];
long
[]
bCounts
=
new
long
[
b
];
System
.
arraycopy
(
counts
,
0
,
aCounts
,
0
,
a
+
1
);
System
.
arraycopy
(
counts
,
a
+
1
,
bCounts
,
0
,
b
);
counts
=
aCounts
;
childCount
=
a
+
1
;
sharedFlags
&=
~(
SHARED_KEYS
|
SHARED_CHILDREN
|
SHARED_COUNTS
);
long
t
=
0
;
for
(
long
x
:
aCounts
)
{
t
+=
x
;
for
(
PageReference
x
:
aChildren
)
{
t
+=
x
.
count
;
}
totalCount
=
t
;
t
=
0
;
for
(
long
x
:
bCounts
)
{
t
+=
x
;
for
(
PageReference
x
:
bChildren
)
{
t
+=
x
.
count
;
}
Page
newPage
=
create
(
map
,
version
,
b
-
1
,
b
Keys
,
null
,
b
,
bChildren
,
bCounts
,
t
,
0
,
0
);
bKeys
,
null
,
b
Children
,
t
,
0
);
recalculateMemory
();
newPage
.
recalculateMemory
();
return
newPage
;
...
...
@@ -501,10 +451,10 @@ public class Page {
if
(
MVStore
.
ASSERT
)
{
long
check
=
0
;
if
(
isLeaf
())
{
check
=
key
Count
;
check
=
key
s
.
length
;
}
else
{
for
(
long
x
:
counts
)
{
check
+=
x
;
for
(
PageReference
x
:
children
)
{
check
+=
x
.
count
;
}
}
if
(
check
!=
totalCount
)
{
...
...
@@ -523,7 +473,7 @@ public class Page {
* @return the descendant count
*/
long
getCounts
(
int
index
)
{
return
c
ounts
[
index
]
;
return
c
hildren
[
index
].
count
;
}
/**
...
...
@@ -534,40 +484,11 @@ public class Page {
*/
public
void
setChild
(
int
index
,
Page
c
)
{
if
(
c
!=
children
[
index
].
page
||
c
.
getPos
()
!=
children
[
index
].
pos
)
{
if
((
sharedFlags
&
SHARED_CHILDREN
)
!=
0
)
{
children
=
Arrays
.
copyOf
(
children
,
children
.
length
);
sharedFlags
&=
~
SHARED_CHILDREN
;
}
PageReference
ref
=
new
PageReference
(
c
,
c
.
pos
);
long
oldCount
=
children
[
index
].
count
;
children
=
Arrays
.
copyOf
(
children
,
children
.
length
);
PageReference
ref
=
new
PageReference
(
c
,
c
.
pos
,
c
.
totalCount
);
children
[
index
]
=
ref
;
}
}
/**
* Update the (descendant) count for the given child, if there was a change.
*
* @param index the index
* @param c the new child page
*/
public
void
setCounts
(
int
index
,
Page
c
)
{
setCounts
(
index
,
c
.
totalCount
);
}
/**
* Update the (descendant) count for the given child, if there was a change.
*
* @param index the index
* @param total the new value
*/
private
void
setCounts
(
int
index
,
long
total
)
{
if
(
total
!=
counts
[
index
])
{
if
((
sharedFlags
&
SHARED_COUNTS
)
!=
0
)
{
counts
=
Arrays
.
copyOf
(
counts
,
counts
.
length
);
sharedFlags
&=
~
SHARED_COUNTS
;
}
long
oldCount
=
counts
[
index
];
counts
[
index
]
=
total
;
totalCount
+=
counts
[
index
]
-
oldCount
;
totalCount
+=
c
.
totalCount
-
oldCount
;
}
}
...
...
@@ -578,10 +499,7 @@ public class Page {
* @param key the new key
*/
public
void
setKey
(
int
index
,
Object
key
)
{
if
((
sharedFlags
&
SHARED_KEYS
)
!=
0
)
{
keys
=
Arrays
.
copyOf
(
keys
,
keys
.
length
);
sharedFlags
&=
~
SHARED_KEYS
;
}
keys
=
Arrays
.
copyOf
(
keys
,
keys
.
length
);
Object
old
=
keys
[
index
];
DataType
keyType
=
map
.
getKeyType
();
int
mem
=
keyType
.
getMemory
(
key
);
...
...
@@ -601,10 +519,7 @@ public class Page {
*/
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
=
Arrays
.
copyOf
(
values
,
values
.
length
);
DataType
valueType
=
map
.
getValueType
();
addMemory
(
valueType
.
getMemory
(
value
)
-
valueType
.
getMemory
(
old
));
...
...
@@ -644,26 +559,15 @@ public class Page {
* @param value the value
*/
public
void
insertLeaf
(
int
index
,
Object
key
,
Object
value
)
{
if
(((
sharedFlags
&
SHARED_KEYS
)
==
0
)
&&
keys
.
length
>
keyCount
+
1
)
{
if
(
index
<
keyCount
)
{
System
.
arraycopy
(
keys
,
index
,
keys
,
index
+
1
,
keyCount
-
index
);
System
.
arraycopy
(
values
,
index
,
values
,
index
+
1
,
keyCount
-
index
);
}
}
else
{
int
len
=
keyCount
+
6
;
Object
[]
newKeys
=
new
Object
[
len
];
DataUtils
.
copyWithGap
(
keys
,
newKeys
,
keyCount
,
index
);
keys
=
newKeys
;
Object
[]
newValues
=
new
Object
[
len
];
DataUtils
.
copyWithGap
(
values
,
newValues
,
keyCount
,
index
);
values
=
newValues
;
}
int
len
=
keys
.
length
+
1
;
Object
[]
newKeys
=
new
Object
[
len
];
DataUtils
.
copyWithGap
(
keys
,
newKeys
,
len
-
1
,
index
);
keys
=
newKeys
;
Object
[]
newValues
=
new
Object
[
len
];
DataUtils
.
copyWithGap
(
values
,
newValues
,
len
-
1
,
index
);
values
=
newValues
;
keys
[
index
]
=
key
;
values
[
index
]
=
value
;
keyCount
++;
sharedFlags
&=
~(
SHARED_KEYS
|
SHARED_VALUES
);
totalCount
++;
addMemory
(
map
.
getKeyType
().
getMemory
(
key
)
+
map
.
getValueType
().
getMemory
(
value
));
...
...
@@ -678,26 +582,18 @@ public class Page {
*/
public
void
insertNode
(
int
index
,
Object
key
,
Page
childPage
)
{
Object
[]
newKeys
=
new
Object
[
key
Count
+
1
];
DataUtils
.
copyWithGap
(
keys
,
newKeys
,
key
Count
,
index
);
Object
[]
newKeys
=
new
Object
[
key
s
.
length
+
1
];
DataUtils
.
copyWithGap
(
keys
,
newKeys
,
key
s
.
length
,
index
);
newKeys
[
index
]
=
key
;
keys
=
newKeys
;
keyCount
++;
int
childCount
=
children
.
length
;
PageReference
[]
newChildren
=
new
PageReference
[
childCount
+
1
];
DataUtils
.
copyWithGap
(
children
,
newChildren
,
childCount
,
index
);
newChildren
[
index
]
=
new
PageReference
(
childPage
,
childPage
.
getPos
());
newChildren
[
index
]
=
new
PageReference
(
childPage
,
childPage
.
getPos
(),
childPage
.
totalCount
);
children
=
newChildren
;
long
[]
newCounts
=
new
long
[
childCount
+
1
];
DataUtils
.
copyWithGap
(
counts
,
newCounts
,
childCount
,
index
);
newCounts
[
index
]
=
childPage
.
totalCount
;
counts
=
newCounts
;
childCount
++;
sharedFlags
&=
~(
SHARED_KEYS
|
SHARED_CHILDREN
|
SHARED_COUNTS
);
totalCount
+=
childPage
.
totalCount
;
addMemory
(
map
.
getKeyType
().
getMemory
(
key
)
+
DataUtils
.
PAGE_MEMORY_CHILD
);
...
...
@@ -709,58 +605,32 @@ public class Page {
* @param index the index
*/
public
void
remove
(
int
index
)
{
int
keyIndex
=
index
>=
keyCount
?
index
-
1
:
index
;
int
keyLength
=
keys
.
length
;
int
keyIndex
=
index
>=
keyLength
?
index
-
1
:
index
;
Object
old
=
keys
[
keyIndex
];
addMemory
(-
map
.
getKeyType
().
getMemory
(
old
));
if
((
sharedFlags
&
SHARED_KEYS
)
==
0
&&
keys
.
length
>
keyCount
-
4
)
{
if
(
keyIndex
<
keyCount
-
1
)
{
System
.
arraycopy
(
keys
,
keyIndex
+
1
,
keys
,
keyIndex
,
keyCount
-
keyIndex
-
1
);
}
keys
[
keyCount
-
1
]
=
null
;
}
else
{
Object
[]
newKeys
=
new
Object
[
keyCount
-
1
];
DataUtils
.
copyExcept
(
keys
,
newKeys
,
keyCount
,
keyIndex
);
keys
=
newKeys
;
sharedFlags
&=
~
SHARED_KEYS
;
}
Object
[]
newKeys
=
new
Object
[
keyLength
-
1
];
DataUtils
.
copyExcept
(
keys
,
newKeys
,
keyLength
,
keyIndex
);
keys
=
newKeys
;
if
(
values
!=
null
)
{
old
=
values
[
index
];
addMemory
(-
map
.
getValueType
().
getMemory
(
old
));
if
((
sharedFlags
&
SHARED_VALUES
)
==
0
&&
values
.
length
>
keyCount
-
4
)
{
if
(
index
<
keyCount
-
1
)
{
System
.
arraycopy
(
values
,
index
+
1
,
values
,
index
,
keyCount
-
index
-
1
);
}
values
[
keyCount
-
1
]
=
null
;
}
else
{
Object
[]
newValues
=
new
Object
[
keyCount
-
1
];
DataUtils
.
copyExcept
(
values
,
newValues
,
keyCount
,
index
);
values
=
newValues
;
sharedFlags
&=
~
SHARED_VALUES
;
}
Object
[]
newValues
=
new
Object
[
keyLength
-
1
];
DataUtils
.
copyExcept
(
values
,
newValues
,
keyLength
,
index
);
values
=
newValues
;
totalCount
--;
}
keyCount
--;
if
(
children
!=
null
)
{
addMemory
(-
DataUtils
.
PAGE_MEMORY_CHILD
);
long
countOffset
=
c
ounts
[
index
]
;
long
countOffset
=
c
hildren
[
index
].
count
;
int
childCount
=
children
.
length
;
PageReference
[]
newChildren
=
new
PageReference
[
childCount
-
1
];
DataUtils
.
copyExcept
(
children
,
newChildren
,
childCount
,
index
);
children
=
newChildren
;
long
[]
newCounts
=
new
long
[
childCount
-
1
];
DataUtils
.
copyExcept
(
counts
,
newCounts
,
childCount
,
index
);
counts
=
newCounts
;
sharedFlags
&=
~(
SHARED_CHILDREN
|
SHARED_COUNTS
);
totalCount
-=
countOffset
;
childCount
--;
}
}
...
...
@@ -801,21 +671,19 @@ public class Page {
}
int
len
=
DataUtils
.
readVarInt
(
buff
);
keys
=
new
Object
[
len
];
keyCount
=
len
;
int
type
=
buff
.
get
();
boolean
node
=
(
type
&
1
)
==
DataUtils
.
PAGE_TYPE_NODE
;
if
(
node
)
{
childCount
=
len
+
1
;
children
=
new
PageReference
[
len
+
1
];
long
[]
p
=
new
long
[
len
+
1
];
for
(
int
i
=
0
;
i
<=
len
;
i
++)
{
children
[
i
]
=
new
PageReference
(
null
,
buff
.
getLong
()
);
p
[
i
]
=
buff
.
getLong
(
);
}
counts
=
new
long
[
len
+
1
];
long
total
=
0
;
for
(
int
i
=
0
;
i
<=
len
;
i
++)
{
long
s
=
DataUtils
.
readVarLong
(
buff
);
total
+=
s
;
c
ounts
[
i
]
=
s
;
c
hildren
[
i
]
=
new
PageReference
(
null
,
p
[
i
],
s
)
;
}
totalCount
=
total
;
}
...
...
@@ -855,7 +723,7 @@ public class Page {
*/
private
int
write
(
Chunk
chunk
,
WriteBuffer
buff
)
{
int
start
=
buff
.
position
();
int
len
=
key
Count
;
int
len
=
key
s
.
length
;
int
type
=
children
!=
null
?
DataUtils
.
PAGE_TYPE_NODE
:
DataUtils
.
PAGE_TYPE_LEAF
;
buff
.
putInt
(
0
).
...
...
@@ -867,7 +735,7 @@ public class Page {
if
(
type
==
DataUtils
.
PAGE_TYPE_NODE
)
{
writeChildren
(
buff
);
for
(
int
i
=
0
;
i
<=
len
;
i
++)
{
buff
.
putVarLong
(
c
ounts
[
i
]
);
buff
.
putVarLong
(
c
hildren
[
i
].
count
);
}
}
int
compressStart
=
buff
.
position
();
...
...
@@ -931,7 +799,7 @@ public class Page {
}
private
void
writeChildren
(
WriteBuffer
buff
)
{
int
len
=
key
Count
;
int
len
=
key
s
.
length
;
for
(
int
i
=
0
;
i
<=
len
;
i
++)
{
buff
.
putLong
(
children
[
i
].
pos
);
}
...
...
@@ -951,12 +819,12 @@ public class Page {
}
int
patch
=
write
(
chunk
,
buff
);
if
(!
isLeaf
())
{
int
len
=
child
Count
;
int
len
=
child
ren
.
length
;
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
Page
p
=
children
[
i
].
page
;
if
(
p
!=
null
)
{
p
.
writeUnsavedRecursive
(
chunk
,
buff
);
children
[
i
]
=
new
PageReference
(
p
,
p
.
getPos
());
children
[
i
]
=
new
PageReference
(
p
,
p
.
getPos
()
,
p
.
totalCount
);
}
}
int
old
=
buff
.
position
();
...
...
@@ -971,7 +839,7 @@ public class Page {
*/
void
writeEnd
()
{
if
(!
isLeaf
())
{
int
len
=
child
Count
;
int
len
=
child
ren
.
length
;
for
(
int
i
=
0
;
i
<
len
;
i
++)
{
PageReference
ref
=
children
[
i
];
if
(
ref
.
page
!=
null
)
{
...
...
@@ -980,7 +848,7 @@ public class Page {
DataUtils
.
ERROR_INTERNAL
,
"Page not written"
);
}
ref
.
page
.
writeEnd
();
children
[
i
]
=
new
PageReference
(
null
,
ref
.
pos
);
children
[
i
]
=
new
PageReference
(
null
,
ref
.
pos
,
ref
.
count
);
}
}
}
...
...
@@ -991,7 +859,7 @@ public class Page {
}
public
int
getRawChildPageCount
()
{
return
child
Count
;
return
child
ren
.
length
;
}
@Override
...
...
@@ -1032,12 +900,12 @@ public class Page {
private
void
recalculateMemory
()
{
int
mem
=
DataUtils
.
PAGE_MEMORY
;
DataType
keyType
=
map
.
getKeyType
();
for
(
int
i
=
0
;
i
<
key
Count
;
i
++)
{
for
(
int
i
=
0
;
i
<
key
s
.
length
;
i
++)
{
mem
+=
keyType
.
getMemory
(
keys
[
i
]);
}
if
(
this
.
isLeaf
())
{
DataType
valueType
=
map
.
getValueType
();
for
(
int
i
=
0
;
i
<
key
Count
;
i
++)
{
for
(
int
i
=
0
;
i
<
key
s
.
length
;
i
++)
{
mem
+=
valueType
.
getMemory
(
values
[
i
]);
}
}
else
{
...
...
@@ -1076,9 +944,15 @@ public class Page {
*/
final
Page
page
;
public
PageReference
(
Page
page
,
long
pos
)
{
/**
* The descendant count for this child page.
*/
final
long
count
;
public
PageReference
(
Page
page
,
long
pos
,
long
count
)
{
this
.
page
=
page
;
this
.
pos
=
pos
;
this
.
count
=
count
;
}
}
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java
浏览文件 @
337f0a4f
...
...
@@ -121,7 +121,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
}
@Override
protected
Object
remove
(
Page
p
,
long
writeVersion
,
Object
key
)
{
protected
synchronized
Object
remove
(
Page
p
,
long
writeVersion
,
Object
key
)
{
Object
result
=
null
;
if
(
p
.
isLeaf
())
{
for
(
int
i
=
0
;
i
<
p
.
getKeyCount
();
i
++)
{
...
...
@@ -139,11 +139,10 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
// this will mark the old page as deleted
// so we need to update the parent in any case
// (otherwise the old page might be deleted again)
Page
c
=
c
opyOnWrite
(
cOld
,
writeVersion
);
Page
c
=
c
Old
.
copy
(
writeVersion
);
long
oldSize
=
c
.
getTotalCount
();
result
=
remove
(
c
,
writeVersion
,
key
);
p
.
setChild
(
i
,
c
);
p
.
setCounts
(
i
,
c
);
if
(
oldSize
==
c
.
getTotalCount
())
{
continue
;
}
...
...
@@ -190,45 +189,39 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
putOrAdd
(
key
,
value
,
true
);
}
private
Object
putOrAdd
(
SpatialKey
key
,
V
value
,
boolean
alwaysAdd
)
{
private
synchronized
Object
putOrAdd
(
SpatialKey
key
,
V
value
,
boolean
alwaysAdd
)
{
beforeWrite
();
try
{
long
v
=
writeVersion
;
Page
p
=
copyOnWrite
(
root
,
v
);
Object
result
;
if
(
alwaysAdd
||
get
(
key
)
==
null
)
{
if
(
p
.
getMemory
()
>
store
.
getPageSplitSize
()
&&
p
.
getKeyCount
()
>
1
)
{
// only possible if this is the root, else we would have
// split earlier (this requires pageSplitSize is fixed)
long
totalCount
=
p
.
getTotalCount
();
Page
split
=
split
(
p
,
v
);
Object
k1
=
getBounds
(
p
);
Object
k2
=
getBounds
(
split
);
Object
[]
keys
=
{
k1
,
k2
};
Page
.
PageReference
[]
children
=
{
new
Page
.
PageReference
(
p
,
p
.
getPos
()),
new
Page
.
PageReference
(
split
,
split
.
getPos
()),
new
Page
.
PageReference
(
null
,
0
)
};
long
[]
counts
=
{
p
.
getTotalCount
(),
split
.
getTotalCount
(),
0
};
p
=
Page
.
create
(
this
,
v
,
2
,
keys
,
null
,
3
,
children
,
counts
,
totalCount
,
0
,
0
);
// now p is a node; continues
}
add
(
p
,
v
,
key
,
value
);
result
=
null
;
}
else
{
result
=
set
(
p
,
v
,
key
,
value
);
long
v
=
writeVersion
;
Page
p
=
root
.
copy
(
v
);
Object
result
;
if
(
alwaysAdd
||
get
(
key
)
==
null
)
{
if
(
p
.
getMemory
()
>
store
.
getPageSplitSize
()
&&
p
.
getKeyCount
()
>
3
)
{
// only possible if this is the root, else we would have
// split earlier (this requires pageSplitSize is fixed)
long
totalCount
=
p
.
getTotalCount
();
Page
split
=
split
(
p
,
v
);
Object
k1
=
getBounds
(
p
);
Object
k2
=
getBounds
(
split
);
Object
[]
keys
=
{
k1
,
k2
};
Page
.
PageReference
[]
children
=
{
new
Page
.
PageReference
(
p
,
p
.
getPos
(),
p
.
getTotalCount
()),
new
Page
.
PageReference
(
split
,
split
.
getPos
(),
split
.
getTotalCount
()),
new
Page
.
PageReference
(
null
,
0
,
0
)
};
p
=
Page
.
create
(
this
,
v
,
keys
,
null
,
children
,
totalCount
,
0
);
// now p is a node; continues
}
newRoot
(
p
);
re
turn
result
;
}
finally
{
afterWrite
(
);
add
(
p
,
v
,
key
,
value
);
re
sult
=
null
;
}
else
{
result
=
set
(
p
,
v
,
key
,
value
);
}
newRoot
(
p
);
return
result
;
}
/**
...
...
@@ -252,10 +245,9 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
if
(
contains
(
p
,
i
,
key
))
{
Page
c
=
p
.
getChildPage
(
i
);
if
(
get
(
c
,
key
)
!=
null
)
{
c
=
c
opyOnWrite
(
c
,
writeVersion
);
c
=
c
.
copy
(
writeVersion
);
Object
result
=
set
(
c
,
writeVersion
,
key
,
value
);
p
.
setChild
(
i
,
c
);
p
.
setCounts
(
i
,
c
);
return
result
;
}
}
...
...
@@ -290,14 +282,12 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
}
}
}
Page
c
=
copyOnWrite
(
p
.
getChildPage
(
index
),
writeVersion
);
if
(
c
.
getMemory
()
>
store
.
getPageSplitSize
()
&&
c
.
getKeyCount
()
>
1
)
{
Page
c
=
p
.
getChildPage
(
index
).
copy
(
writeVersion
);
if
(
c
.
getMemory
()
>
store
.
getPageSplitSize
()
&&
c
.
getKeyCount
()
>
4
)
{
// split on the way down
Page
split
=
split
(
c
,
writeVersion
);
p
=
copyOnWrite
(
p
,
writeVersion
);
p
.
setKey
(
index
,
getBounds
(
c
));
p
.
setChild
(
index
,
c
);
p
.
setCounts
(
index
,
c
);
p
.
insertNode
(
index
,
getBounds
(
split
),
split
);
// now we are not sure where to add
add
(
p
,
writeVersion
,
key
,
value
);
...
...
@@ -308,7 +298,6 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
keyType
.
increaseBounds
(
bounds
,
key
);
p
.
setKey
(
index
,
bounds
);
p
.
setChild
(
index
,
c
);
p
.
setCounts
(
index
,
c
);
}
private
Page
split
(
Page
p
,
long
writeVersion
)
{
...
...
@@ -412,20 +401,17 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
private
Page
newPage
(
boolean
leaf
,
long
writeVersion
)
{
Object
[]
values
;
Page
.
PageReference
[]
refs
;
long
[]
c
;
if
(
leaf
)
{
values
=
new
Object
[
4
]
;
values
=
Page
.
EMPTY_OBJECT_ARRAY
;
refs
=
null
;
c
=
null
;
}
else
{
values
=
null
;
refs
=
new
Page
.
PageReference
[]
{
new
Page
.
PageReference
(
null
,
0
)};
c
=
new
long
[
1
];
new
Page
.
PageReference
(
null
,
0
,
0
)};
}
return
Page
.
create
(
this
,
writeVersion
,
0
,
new
Object
[
4
]
,
values
,
leaf
?
0
:
1
,
refs
,
c
,
0
,
0
,
0
);
Page
.
EMPTY_OBJECT_ARRAY
,
values
,
refs
,
0
,
0
);
}
private
static
void
move
(
Page
source
,
Page
target
,
int
sourceIndex
)
{
...
...
This diff is collapsed.
Click to expand it.
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论