(key, value);
15 | }
16 |
17 | public K key() {
18 | return key;
19 | }
20 |
21 | public V value() {
22 | return value;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/LargeByteBuffer.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.nio.charset.StandardCharsets;
5 |
6 | /**
7 | * Similar to {@link ByteBuffer} but supports {@code long} positions instead of
8 | * {@code int} positions. Does not include those methods in ByteBuffer that
9 | * include the position as a parameter (for simplicity).
10 | *
11 | *
12 | * Also includes the notion of commit which forces flushing of memory buffers to
13 | * disk.
14 | *
15 | */
16 | public interface LargeByteBuffer {
17 |
18 | long position();
19 |
20 | void position(long newPosition);
21 |
22 | byte get();
23 |
24 | void put(byte b);
25 |
26 | void get(byte[] dst);
27 |
28 | void put(byte[] src);
29 |
30 | int getInt();
31 |
32 | void putInt(int value);
33 |
34 | short getShort();
35 |
36 | void putShort(short value);
37 |
38 | long getLong();
39 |
40 | void putLong(long value);
41 |
42 | double getDouble();
43 |
44 | void putDouble(double value);
45 |
46 | double getFloat();
47 |
48 | void putFloat(float value);
49 |
50 | void commit();
51 |
52 | default String getString() {
53 | int length = getVarint();
54 | byte[] bytes = new byte[length];
55 | get(bytes);
56 | return new String(bytes, StandardCharsets.UTF_8);
57 | }
58 |
59 | default void putString(String value) {
60 | byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
61 | putVarint(bytes.length);
62 | put(bytes);
63 | }
64 |
65 | /**
66 | * Stores an integer in a variable number of bytes (up to 5). A varint is an
67 | * alternative storage method for an integer that may take up as little as one
68 | * byte for small values.
69 | *
70 | *
71 | * Algorithm used is from ProtocolBuffers.
72 | *
73 | * @param value integer value to store
74 | */
75 | default void putVarint(int value) {
76 | while (true) {
77 | if ((value & ~0x7F) == 0) {
78 | put((byte) value);
79 | break;
80 | } else {
81 | put((byte) ((value & 0x7F) | 0x80));
82 | value >>>= 7;
83 | }
84 | }
85 | }
86 |
87 | /**
88 | * Returns an integer that was stored in a variable number of bytes (up to 5). A
89 | * varint is an alternative storage method for an integer that may take up as
90 | * little as one byte for small values.
91 | *
92 | *
93 | * Algorithm used is from ProtocolBuffers.
94 | * @return the decoded integer
95 | */
96 | default int getVarint() {
97 | // Adapated from ProtocolBuffers CodedInputStream
98 | int x;
99 | long pos = position();
100 | if ((x = get()) >= 0) {
101 | return x;
102 | } else if ((x ^= (get() << 7)) < 0) {
103 | x ^= (~0 << 7);
104 | } else if ((x ^= (get() << 14)) >= 0) {
105 | x ^= (~0 << 7) ^ (~0 << 14);
106 | } else if ((x ^= (get() << 21)) < 0) {
107 | x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21);
108 | } else {
109 | position(pos);
110 | // get the value the slow way but can handle when integer needed more than 4
111 | // bytes to represent
112 | return (int) getVarlong();
113 | }
114 | return x;
115 | }
116 |
117 | /**
118 | * Stores a long in a variable number of bytes (up to 9). A varlong is an
119 | * alternative storage method for a long that may take up as little as one byte
120 | * for small values.
121 | *
122 | *
123 | * Algorithm used is from ProtocolBuffers.
124 | *
125 | * @param value long value to store
126 | */
127 | default void putVarlong(long value) {
128 | while (true) {
129 | if ((value & ~0x7FL) == 0) {
130 | put((byte) value);
131 | return;
132 | } else {
133 | put((byte) (((int) value & 0x7F) | 0x80));
134 | value >>>= 7;
135 | }
136 | }
137 | }
138 |
139 | /**
140 | * Returns a long that was stored in a variable number of bytes (up to 9). A
141 | * varlong is an alternative storage method for a long that may take up as
142 | * little as one byte for small values.
143 | *
144 | *
145 | * Algorithm used is from ProtocolBuffers.
146 | * @return the decoded long
147 | */
148 | default long getVarlong() {
149 | long result = 0;
150 | for (int shift = 0; shift < 64; shift += 7) {
151 | final byte b = get();
152 | result |= (long) (b & 0x7F) << shift;
153 | if ((b & 0x80) == 0) {
154 | return result;
155 | }
156 | }
157 | throw new IllegalStateException("malformed varlong");
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/Serializer.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree;
2 |
3 | import java.nio.charset.Charset;
4 | import java.nio.charset.StandardCharsets;
5 |
6 | public interface Serializer {
7 |
8 | T read(LargeByteBuffer bb);
9 |
10 | void write(LargeByteBuffer bb, T t);
11 |
12 | /**
13 | * Returns the maximum size in bytes of a serialized item. Returns 0 when there
14 | * is no maximum.
15 | *
16 | * @return the maximum size in bytes of a serialized item. Returns 0 when there
17 | * is no maximum.
18 | */
19 | int maxSize();
20 |
21 | public static Serializer SHORT = new Serializer() {
22 |
23 | @Override
24 | public Short read(LargeByteBuffer bb) {
25 | return bb.getShort();
26 | }
27 |
28 | @Override
29 | public void write(LargeByteBuffer bb, Short t) {
30 | bb.putShort(t);
31 | }
32 |
33 | @Override
34 | public int maxSize() {
35 | return Short.BYTES;
36 | }
37 | };
38 |
39 | public static Serializer INTEGER = new Serializer() {
40 |
41 | @Override
42 | public Integer read(LargeByteBuffer bb) {
43 | return bb.getInt();
44 | }
45 |
46 | @Override
47 | public void write(LargeByteBuffer bb, Integer t) {
48 | bb.putInt(t);
49 | }
50 |
51 | @Override
52 | public int maxSize() {
53 | return Integer.BYTES;
54 | }
55 | };
56 |
57 | public static Serializer LONG = new Serializer() {
58 |
59 | @Override
60 | public Long read(LargeByteBuffer bb) {
61 | return bb.getLong();
62 | }
63 |
64 | @Override
65 | public void write(LargeByteBuffer bb, Long t) {
66 | bb.putLong(t);
67 | }
68 |
69 | @Override
70 | public int maxSize() {
71 | return Long.BYTES;
72 | }
73 | };
74 |
75 | public static Serializer FLOAT = new Serializer() {
76 |
77 | @Override
78 | public Float read(LargeByteBuffer bb) {
79 | return ((Double) bb.getFloat()).floatValue();
80 | }
81 |
82 | @Override
83 | public void write(LargeByteBuffer bb, Float t) {
84 | bb.putFloat(t);
85 | }
86 |
87 | @Override
88 | public int maxSize() {
89 | return Float.BYTES;
90 | }
91 | };
92 |
93 | public static Serializer DOUBLE = new Serializer() {
94 |
95 | @Override
96 | public Double read(LargeByteBuffer bb) {
97 | return bb.getDouble();
98 | }
99 |
100 | @Override
101 | public void write(LargeByteBuffer bb, Double t) {
102 | bb.putDouble(t);
103 | }
104 |
105 | @Override
106 | public int maxSize() {
107 | return Double.BYTES;
108 | }
109 | };
110 |
111 | public static Serializer utf8() {
112 | return utf8(0);
113 | }
114 |
115 | public static Serializer utf8(int maxSize) {
116 | return string(StandardCharsets.UTF_8, maxSize);
117 | }
118 |
119 | public static Serializer string(Charset charset, int maxSize) {
120 | return new Serializer() {
121 |
122 | @Override
123 | public String read(LargeByteBuffer bb) {
124 | return bb.getString();
125 | }
126 |
127 | @Override
128 | public void write(LargeByteBuffer bb, String s) {
129 | bb.putString(s);
130 | }
131 |
132 | @Override
133 | public int maxSize() {
134 | return maxSize;
135 | }
136 | };
137 | }
138 |
139 | public static Serializer bytes(int maxSize) {
140 | return new Serializer() {
141 |
142 | @Override
143 | public byte[] read(LargeByteBuffer bb) {
144 | int size = bb.getInt();
145 | byte[] bytes = new byte[size];
146 | bb.get(bytes);
147 | return bytes;
148 | }
149 |
150 | @Override
151 | public void write(LargeByteBuffer bb, byte[] bytes) {
152 | bb.putInt(bytes.length);
153 | bb.put(bytes);
154 | }
155 |
156 | @Override
157 | public int maxSize() {
158 | return maxSize;
159 | }
160 |
161 | };
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/Factory.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal;
2 |
3 | public interface Factory extends AutoCloseable {
4 |
5 | Leaf createLeaf();
6 |
7 | NonLeaf createNonLeaf();
8 |
9 | void commit();
10 |
11 | /**
12 | * Called when the root node of the BPlusTree is initialized or changes.
13 | *
14 | * @param node new root node
15 | */
16 | void root(Node node);
17 |
18 | Node loadOrCreateRoot();
19 |
20 | Options options();
21 |
22 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/FactoryProvider.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal;
2 |
3 | public interface FactoryProvider {
4 |
5 | Factory createFactory(Options options);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/LargeMappedByteBuffer.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.io.RandomAccessFile;
6 | import java.io.UncheckedIOException;
7 | import java.lang.reflect.Field;
8 | import java.lang.reflect.InvocationTargetException;
9 | import java.lang.reflect.Method;
10 | import java.nio.ByteBuffer;
11 | import java.nio.MappedByteBuffer;
12 | import java.nio.channels.FileChannel;
13 | import java.nio.channels.FileChannel.MapMode;
14 | import java.nio.file.Files;
15 | import java.nio.file.StandardOpenOption;
16 | import java.util.ArrayList;
17 | import java.util.List;
18 | import java.util.TreeMap;
19 |
20 | import com.github.davidmoten.bplustree.Entry;
21 | import com.github.davidmoten.bplustree.LargeByteBuffer;
22 | import com.github.davidmoten.guavamini.annotations.VisibleForTesting;
23 |
24 | public final class LargeMappedByteBuffer implements AutoCloseable, LargeByteBuffer {
25 |
26 | private final int segmentSizeBytes;
27 |
28 | private final TreeMap map = new TreeMap<>();
29 | private final List> list = new ArrayList<>();
30 | private final File directory;
31 | private final String segmentNamePrefix;
32 |
33 | private byte[] temp2Bytes = new byte[2];
34 | private byte[] temp4Bytes = new byte[4];
35 | private byte[] temp8Bytes = new byte[8];
36 |
37 | public LargeMappedByteBuffer(File directory, int segmentSizeBytes, String segmentNamePrefix) {
38 | this.directory = directory;
39 | this.segmentSizeBytes = segmentSizeBytes;
40 | this.segmentNamePrefix = segmentNamePrefix;
41 | }
42 |
43 | private long position;
44 |
45 | private MappedByteBuffer bb(long position) {
46 | // TODO close segments when map gets too many entries
47 |
48 | long num = segmentNumber(position);
49 | Segment segment = getSegment(num);
50 | if (segment == null) {
51 | segment = createSegment(num);
52 | }
53 | segment.bb.position((int) (position % segmentSizeBytes));
54 | return segment.bb;
55 | }
56 |
57 | private Segment getSegment(long num) {
58 | for (int i = 0; i < list.size(); i++) {
59 | Entry entry = list.get(i);
60 | if (entry.key() == num) {
61 | return entry.value();
62 | }
63 | }
64 | return null;
65 | }
66 |
67 | private void putSegment(long num, Segment segment) {
68 | // map.put(num, segment);
69 | list.add(Entry.create(num, segment));
70 | }
71 |
72 | private Segment createSegment(long num) {
73 | File file = new File(directory, segmentNamePrefix + num);
74 | Segment segment = map(file, segmentSizeBytes);
75 | putSegment(num, segment);
76 | return segment;
77 | }
78 |
79 | private static Segment map(File file, int segmentSizeBytes) {
80 | try {
81 | checkFile(file, segmentSizeBytes);
82 | try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
83 | raf.setLength(segmentSizeBytes);
84 | }
85 | FileChannel channel = (FileChannel) Files //
86 | .newByteChannel(//
87 | file.toPath(), //
88 | StandardOpenOption.CREATE, //
89 | StandardOpenOption.READ, //
90 | StandardOpenOption.WRITE);
91 |
92 | // map the whole file
93 | MappedByteBuffer bb = channel.map(MapMode.READ_WRITE, 0, segmentSizeBytes);
94 | return new Segment(channel, bb);
95 | } catch (IOException e) {
96 | throw new UncheckedIOException(e);
97 | }
98 | }
99 |
100 | @VisibleForTesting
101 | static void checkFile(File file, int segmentSizeBytes) {
102 | if (file.exists() && file.length() != segmentSizeBytes) {
103 | throw new IllegalStateException("segment file " + file + " should be of size " + segmentSizeBytes
104 | + " but was of size " + file.length());
105 | }
106 | }
107 |
108 | @Override
109 | public void position(long newPosition) {
110 | this.position = newPosition;
111 | }
112 |
113 | @Override
114 | public byte get() {
115 | return bb(position++).get();
116 | }
117 |
118 | @Override
119 | public void put(byte b) {
120 | bb(position++).put(b);
121 | }
122 |
123 | @Override
124 | public void get(byte[] dst) {
125 | long p = position;
126 | if (segmentNumber(p) == segmentNumber(p + dst.length)) {
127 | bb(p).get(dst);
128 | } else {
129 | int i = 0;
130 | while (true) {
131 | long p2 = Math.min(segmentPosition(segmentNumber(p) + 1), position + dst.length);
132 | int length = (int) (p2 - p);
133 | if (length == 0) {
134 | break;
135 | }
136 | bb(p).get(dst, i, length);
137 | i += length;
138 | p = p2;
139 | }
140 | }
141 | position += dst.length;
142 | }
143 |
144 | @Override
145 | public void put(byte[] src) {
146 | long p = position;
147 | if (segmentNumber(p) == segmentNumber(p + src.length)) {
148 | bb(p).put(src);
149 | } else {
150 | int i = 0;
151 | while (true) {
152 | long p2 = Math.min(segmentPosition(segmentNumber(p) + 1), position + src.length);
153 | int length = (int) (p2 - p);
154 | if (length == 0) {
155 | break;
156 | }
157 | bb(p).put(src, i, length);
158 | i += length;
159 | p = p2;
160 | }
161 | }
162 | position += src.length;
163 | }
164 |
165 | @Override
166 | public int getInt() {
167 | long p = position;
168 | if (segmentNumber(p) == segmentNumber(p + Integer.BYTES)) {
169 | position += Integer.BYTES;
170 | return bb(p).getInt();
171 | } else {
172 | get(temp4Bytes);
173 | return toInt(temp4Bytes);
174 | }
175 | }
176 |
177 | @Override
178 | public void putInt(int value) {
179 | long p = position;
180 | if (segmentNumber(p) == segmentNumber(p + Integer.BYTES)) {
181 | bb(p).putInt(value);
182 | position += Integer.BYTES;
183 | } else {
184 | put(toBytes(value));
185 | }
186 | }
187 |
188 | @Override
189 | public long getLong() {
190 | long p = position;
191 | if (segmentNumber(p) == segmentNumber(p + Long.BYTES)) {
192 | position += Long.BYTES;
193 | return bb(p).getLong();
194 | } else {
195 | get(temp8Bytes);
196 | return toLong(temp8Bytes);
197 | }
198 | }
199 |
200 | @Override
201 | public void putLong(long value) {
202 | long p = position;
203 | if (segmentNumber(p) == segmentNumber(p + Long.BYTES)) {
204 | position += Long.BYTES;
205 | bb(p).putLong(value);
206 | } else {
207 | put(toBytes(value));
208 | }
209 | }
210 |
211 | @Override
212 | public long position() {
213 | return position;
214 | }
215 |
216 | @Override
217 | public short getShort() {
218 | long p = position;
219 | if (segmentNumber(p) == segmentNumber(p + Short.BYTES)) {
220 | position += Short.BYTES;
221 | return bb(p).getShort();
222 | } else {
223 | get(temp2Bytes);
224 | return toShort(temp2Bytes);
225 | }
226 | }
227 |
228 | @Override
229 | public void putShort(short value) {
230 | long p = position;
231 | if (segmentNumber(p) == segmentNumber(p + Short.BYTES)) {
232 | bb(p).putShort(value);
233 | position += Short.BYTES;
234 | } else {
235 | put(toBytes(value));
236 | }
237 | }
238 |
239 | private long segmentNumber(long position) {
240 | return position / segmentSizeBytes;
241 | }
242 |
243 | private long segmentPosition(long segmentNumber) {
244 | return segmentSizeBytes * segmentNumber;
245 | }
246 |
247 | private static byte[] toBytes(short n) {
248 | return ByteBuffer.allocate(Short.BYTES).putShort(n).array();
249 | }
250 |
251 | private static byte[] toBytes(int n) {
252 | return ByteBuffer.allocate(Integer.BYTES).putInt(n).array();
253 | }
254 |
255 | private static byte[] toBytes(long n) {
256 | return ByteBuffer.allocate(Long.BYTES).putLong(n).array();
257 | }
258 |
259 | private static byte[] toBytes(double n) {
260 | return ByteBuffer.allocate(Double.BYTES).putDouble(n).array();
261 | }
262 |
263 | private static byte[] toBytes(float n) {
264 | return ByteBuffer.allocate(Float.BYTES).putFloat(n).array();
265 | }
266 |
267 | private short toShort(byte[] bytes) {
268 | short ret = 0;
269 | for (int i = 0; i < 2; i++) {
270 | ret <<= 8;
271 | ret |= bytes[i] & 0xFF;
272 | }
273 | return ret;
274 | }
275 |
276 | private static int toInt(byte[] bytes) {
277 | int ret = 0;
278 | for (int i = 0; i < 4; i++) {
279 | ret <<= 8;
280 | ret |= bytes[i] & 0xFF;
281 | }
282 | return ret;
283 | }
284 |
285 | private static long toLong(byte[] b) {
286 | long result = 0;
287 | for (int i = 0; i < 8; i++) {
288 | result <<= 8;
289 | result |= (b[i] & 0xFF);
290 | }
291 | return result;
292 | }
293 |
294 | private static double toDouble(byte[] b) {
295 | return ByteBuffer.wrap(b).getDouble();
296 | }
297 |
298 | private static double toFloat(byte[] b) {
299 | return ByteBuffer.wrap(b).getFloat();
300 | }
301 |
302 | @Override
303 | public void commit() {
304 | for (Segment segment : map.values()) {
305 | segment.bb.force();
306 | }
307 | }
308 |
309 | @Override
310 | public void close() throws IOException {
311 | for (Entry entry : list) {
312 | entry.value().close();
313 | }
314 | list.clear();
315 | }
316 |
317 | private static final class Segment {
318 | private final FileChannel channel;
319 | final MappedByteBuffer bb;
320 |
321 | Segment(FileChannel channel, MappedByteBuffer bb) {
322 | this.channel = channel;
323 | this.bb = bb;
324 | }
325 |
326 | public void close() throws IOException {
327 | // Note that System.gc() seems to do the job as well
328 | // as closeDirectBuffer but of course may cause overall
329 | // system pauses which may not be desirable for everyone
330 | closeDirectBuffer(bb);
331 | channel.close();
332 | }
333 |
334 | }
335 |
336 | private static void closeDirectBuffer(ByteBuffer cb) {
337 | if (cb == null || !cb.isDirect())
338 | return;
339 | // we could use this type cast and call functions without reflection code,
340 | // but static import from sun.* package is risky for non-SUN virtual machine.
341 | // try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex)
342 | // { }
343 |
344 | // JavaSpecVer: 1.6, 1.7, 1.8, 9, 10
345 | boolean isOldJDK = System.getProperty("java.specification.version", "99").startsWith("1.");
346 | try {
347 | if (isOldJDK) {
348 | Method cleaner = cb.getClass().getMethod("cleaner");
349 | cleaner.setAccessible(true);
350 | Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean");
351 | clean.setAccessible(true);
352 | clean.invoke(cleaner.invoke(cb));
353 | } else {
354 | Class> unsafeClass;
355 | try {
356 | unsafeClass = Class.forName("sun.misc.Unsafe");
357 | } catch (Exception ex) {
358 | // jdk.internal.misc.Unsafe doesn't yet have an invokeCleaner() method,
359 | // but that method should be added if sun.misc.Unsafe is removed.
360 | unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
361 | }
362 | Method clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
363 | clean.setAccessible(true);
364 | Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
365 | theUnsafeField.setAccessible(true);
366 | Object theUnsafe = theUnsafeField.get(null);
367 | clean.invoke(theUnsafe, cb);
368 | }
369 | } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchFieldException
370 | | SecurityException | NoSuchMethodException | ClassNotFoundException e) {
371 | e.printStackTrace();
372 | }
373 | cb = null;
374 | }
375 |
376 | @Override
377 | public double getDouble() {
378 | get(temp8Bytes);
379 | return toDouble(temp8Bytes);
380 | }
381 |
382 | @Override
383 | public void putDouble(double value) {
384 | long p = position;
385 | if (segmentNumber(p) == segmentNumber(p + Double.BYTES)) {
386 | bb(p).putDouble(value);
387 | position += Double.BYTES;
388 | } else {
389 | put(toBytes(value));
390 | }
391 | }
392 |
393 | @Override
394 | public double getFloat() {
395 | get(temp4Bytes);
396 | return toFloat(temp4Bytes);
397 | }
398 |
399 | @Override
400 | public void putFloat(float value) {
401 | long p = position;
402 | if (segmentNumber(p) == segmentNumber(p + Float.BYTES)) {
403 | bb(p).putFloat(value);
404 | position += Float.BYTES;
405 | } else {
406 | put(toBytes(value));
407 | }
408 | }
409 |
410 | }
411 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/Leaf.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal;
2 |
3 | public interface Leaf extends Node {
4 |
5 | public static final int TYPE = 0;
6 |
7 | @Override
8 | Options options();
9 |
10 | V value(int i);
11 |
12 | void setNumKeys(int numKeys);
13 |
14 | void setValue(int i, V value);
15 |
16 | /**
17 | * Inserts a key and value at the given index in the node and increments the
18 | * number of keys in the node.
19 | *
20 | * @param i which position to make the insertino at in the Leaf keys
21 | * @param key key to insert
22 | * @param value value to insert
23 | */
24 | void insert(int i, K key, V value);
25 |
26 | /**
27 | * Copies length KeyValues from index start to the start of {@code newLeaf},
28 | * sets the number of keys in the new Leaf to be {@code length}, sets the number
29 | * of keys in source Leaf to be {@code start}.
30 | *
31 | * @param start start index of Key Value pairs to copy in current Leaf
32 | * @param length number of Key Value pairs to copy
33 | * @param newLeaf a new empty Leaf
34 | */
35 | void move(int start, int length, Leaf newLeaf);
36 |
37 | void setNext(Leaf sibling);
38 |
39 | Leaf next();
40 |
41 | @Override
42 | default Split insert(K key, V value) {
43 | // Simple linear search
44 | int i = getLocation(key);
45 | int numKeys = numKeys();
46 | if (numKeys == options().maxLeafKeys()) {
47 | // The node is full. We must split it
48 | // the first mid entries will be retained
49 | // and the rest moved to a new right sibling
50 | int mid = (options().maxLeafKeys() + 1) / 2;
51 | int len = numKeys - mid;
52 | Leaf sibling = factory().createLeaf();
53 | move(mid, len, sibling);
54 | if (i < mid) {
55 | // Inserted element goes to left sibling
56 | Util.insertNonfull(this, key, value, i, mid);
57 | } else {
58 | // Inserted element goes to right sibling
59 | // TODO this probably brings about another array copy
60 | // (shift to the right) in sibling so perhaps should be combined with the
61 | // original move
62 | Util.insertNonfull(sibling, key, value, i - mid, len);
63 | }
64 | sibling.setNext(next());
65 | setNext(sibling);
66 | // Notify the parent about the split
67 | return new Split<>(sibling.key(0), // make the right's key >=
68 | // result.key
69 | this, sibling);
70 | } else {
71 | // The node was not full
72 | Util.insertNonfull(this, key, value, i, numKeys);
73 | return null;
74 | }
75 | }
76 |
77 | /**
78 | * Returns the position where 'key' should be inserted in a leaf node that has
79 | * the given keys. The position returned will be the first key K for which
80 | * {@code key} <= K.. If no key satisfies that criterion then returns the
81 | * current number of keys.
82 | *
83 | * @param key key to insert
84 | * @return the position where key should be inserted
85 | */
86 | @Override
87 | default int getLocation(K key) {
88 | return Util.getLocation(this, key, options().comparator(), true);
89 | }
90 |
91 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/Node.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public interface Node {
7 |
8 | // returns null if no split, otherwise returns split info
9 | Split insert(K key, V value);
10 |
11 | K key(int i);
12 |
13 | int numKeys();
14 |
15 | Options options();
16 |
17 | Factory factory();
18 |
19 | default List keys() {
20 | List list = new ArrayList();
21 | for (int i = 0; i < numKeys(); i++) {
22 | list.add(key(i));
23 | }
24 | return list;
25 | }
26 |
27 | /**
28 | * Returns the position where 'key' should be inserted in a leaf node that has
29 | * the given keys.
30 | *
31 | * @param key key to insert
32 | * @return the position where key should be inserted
33 | */
34 | int getLocation(K key);
35 |
36 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/NonLeaf.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal;
2 |
3 | public interface NonLeaf extends Node {
4 |
5 | public static final int TYPE = 1;
6 |
7 | void setNumKeys(int numKeys);
8 |
9 | @Override
10 | int numKeys();
11 |
12 | void setChild(int i, Node node);
13 |
14 | Node child(int i);
15 |
16 | @Override
17 | K key(int i);
18 |
19 | void setKey(int i, K key);
20 |
21 | void move(int mid, NonLeaf other, int length);
22 |
23 | /**
24 | * Inserts the key at the given index and sets the left child of that key to be
25 | * {@code left}. Also increments the number of keys in the node.
26 | *
27 | * @param i index to insert at
28 | * @param key key to insert
29 | * @param left child to set of the new key
30 | */
31 | void insert(int i, K key, Node left);
32 |
33 | @Override
34 | default Split insert(K key, V value) {
35 | if (numKeys() == options().maxNonLeafKeys()) { // Split
36 | int mid = options().maxNonLeafKeys() / 2 + 1;
37 | int len = options().maxNonLeafKeys() - mid;
38 | NonLeaf sibling = factory().createNonLeaf();
39 | move(mid, sibling, len);
40 |
41 | // Set up the return variable
42 | Split result = new Split<>(key(mid - 1), this, sibling);
43 |
44 | // Now insert in the appropriate sibling
45 | if (options().comparator().compare(key, result.key) < 0) {
46 | Util.insertNonfull(this, key, value);
47 | } else {
48 | Util.insertNonfull(sibling, key, value);
49 | }
50 | return result;
51 | } else {// No split
52 | Util.insertNonfull(this, key, value);
53 | return null;
54 | }
55 | }
56 |
57 | /**
58 | * Returns the position where 'key' should be inserted in a non-leaf node that
59 | * has the given keys. The position returned will be the first key K for which
60 | * {@code key} < K.. If no key satisfies that criterion then returns the
61 | * current number of keys.
62 | *
63 | * @param key key to insert
64 | * @return the position where key should be inserted
65 | */
66 | @Override
67 | default int getLocation(K key) {
68 | return Util.getLocation(this, key, options().comparator(), false);
69 | }
70 |
71 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/Options.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal;
2 |
3 | import java.util.Comparator;
4 |
5 | import com.github.davidmoten.guavamini.Preconditions;
6 |
7 | public final class Options {
8 |
9 | /** the maximum number of keys in the leaf node, M must be > 0 */
10 | private final int maxLeafKeys;
11 |
12 | /**
13 | * the maximum number of keys in inner node, the number of pointer is N+1, N
14 | * must be > 2
15 | */
16 | private final int maxNonLeafKeys;
17 | private final Comparator super K> comparator;
18 | private final boolean uniqueKeys;
19 | private final FactoryProvider factoryProvider;
20 |
21 | public Options(int maxLeafKeys, int maxNonLeafKeys, boolean uniqueKeys,
22 | Comparator super K> comparator, FactoryProvider factoryProvider) {
23 | // only one byte used to store num keys so check values
24 | Preconditions.checkArgument(0 < maxLeafKeys && maxLeafKeys <= 255);
25 | Preconditions.checkArgument(0 < maxNonLeafKeys && maxNonLeafKeys <= 255);
26 | this.maxLeafKeys = maxLeafKeys;
27 | this.maxNonLeafKeys = maxNonLeafKeys;
28 | this.comparator = comparator;
29 | this.uniqueKeys = uniqueKeys;
30 | this.factoryProvider = factoryProvider;
31 | }
32 |
33 | public int maxLeafKeys() {
34 | return maxLeafKeys;
35 | }
36 |
37 | public int maxNonLeafKeys() {
38 | return maxNonLeafKeys;
39 | }
40 |
41 | public Comparator super K> comparator() {
42 | return comparator;
43 | }
44 |
45 | public boolean uniqueKeys() {
46 | return uniqueKeys;
47 | }
48 |
49 | public FactoryProvider factoryProvider() {
50 | return factoryProvider;
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/Split.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal;
2 |
3 | public final class Split {
4 | public final K key;
5 | public final Node left;
6 | public final Node right;
7 |
8 | public Split(K key, Node left, Node right) {
9 | this.key = key;
10 | this.left = left;
11 | this.right = right;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/Util.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal;
2 |
3 | import java.util.Comparator;
4 |
5 | final class Util {
6 |
7 | private Util() {
8 | // prevent instantiation
9 | }
10 |
11 | static void insertNonfull(Leaf leaf, K key, V value, int idx, int numKeys) {
12 | // numKeys == leaf.numKeys() but might be costly to call and may have already
13 | // been calculated by the calling methods so we use a pased value
14 | if (idx < numKeys && leaf.options().uniqueKeys() && leaf.key(idx).equals(key)) {
15 | // We are inserting a duplicate value, simply overwrite the old one
16 | leaf.setValue(idx, value);
17 | } else {
18 | // The key we are inserting is unique
19 | leaf.insert(idx, key, value);
20 | }
21 | }
22 |
23 | static void insertNonfull(NonLeaf node, K key, V value) {
24 | // Simple linear search
25 | int index = node.getLocation(key);
26 | Node child = node.child(index);
27 | Split result = child.insert(key, value);
28 |
29 | if (result != null) {
30 | if (index == node.numKeys()) {
31 | // Insertion at the rightmost key
32 | node.setKey(index, result.key);
33 | node.setChild(index, result.left);
34 | node.setChild(index + 1, result.right);
35 | node.setNumKeys(node.numKeys() + 1);
36 | } else {
37 | // Insertion not at the rightmost key
38 | // shift i>idx to the right
39 | node.insert(index, result.key, result.left);
40 | node.setChild(index + 1, result.right);
41 | }
42 | } // else the current node is not affected
43 | }
44 |
45 | static int getLocation(Node node, K key, Comparator super K> comparator, boolean acceptEquals) {
46 | int numKeys = node.numKeys();
47 | if (numKeys == 0) {
48 | return 0;
49 | }
50 | int start = 0;
51 | int finish = numKeys - 1;
52 | while (true) {
53 | int mid = (start + finish) / 2;
54 | int c = comparator.compare(key, node.key(mid));
55 | if (c < 0 || (acceptEquals && c == 0)) {
56 | finish = mid;
57 | if (start == finish) {
58 | return mid;
59 | }
60 | } else {
61 | if (start == finish) {
62 | return mid + 1;
63 | }
64 | start = mid + 1;
65 | }
66 | }
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/file/FactoryFile.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal.file;
2 |
3 | import java.io.File;
4 | import java.util.List;
5 | import java.util.stream.Collectors;
6 | import java.util.stream.IntStream;
7 |
8 | import com.github.davidmoten.bplustree.Serializer;
9 | import com.github.davidmoten.bplustree.internal.Factory;
10 | import com.github.davidmoten.bplustree.internal.LargeMappedByteBuffer;
11 | import com.github.davidmoten.bplustree.internal.Leaf;
12 | import com.github.davidmoten.bplustree.internal.Node;
13 | import com.github.davidmoten.bplustree.internal.NonLeaf;
14 | import com.github.davidmoten.bplustree.internal.Options;
15 |
16 | public final class FactoryFile implements Factory {
17 |
18 | private static final int NODE_TYPE_BYTES = 1;
19 | private static final int NUM_KEYS_BYTES = 1;
20 | private static final int NUM_NODES_BYTES = 4;
21 | private static final int POSITION_BYTES = 8;
22 | private static final long POSITION_NOT_PRESENT = -1;
23 | private final Options options;
24 |
25 | // a pool of LeafFile objects to use
26 | private final List> leavesPool;
27 | private int leavesIndex = 0;
28 |
29 | // position where next node will be written, first 8 bytes are for the position
30 | // of the root node
31 | private long index = POSITION_BYTES;
32 |
33 | private long valuesIndex = 0; // position where next value will be written
34 | private final Serializer keySerializer;
35 | private final Serializer valueSerializer;
36 | private final LargeMappedByteBuffer metadata;
37 | private final LargeMappedByteBuffer bb;
38 | private final LargeMappedByteBuffer values;
39 | private final Runnable onClose;
40 |
41 | public FactoryFile(Options options, File directory, Serializer keySerializer,
42 | Serializer valueSerializer, int segmentSizeBytes, Runnable onClose) {
43 | this.options = options;
44 | this.keySerializer = keySerializer;
45 | this.valueSerializer = valueSerializer;
46 | this.onClose = onClose;
47 | this.bb = new LargeMappedByteBuffer(directory, segmentSizeBytes, "index-");
48 | this.values = new LargeMappedByteBuffer(directory, segmentSizeBytes, "value-");
49 | this.metadata = new LargeMappedByteBuffer(directory, 8192, "metadata-"); // only needs 8 bytes right now
50 | this.leavesPool = createLeafPool(this, 10);
51 | }
52 |
53 | //////////////////////////////////////////////////
54 | // Format of a Leaf
55 | // NODE_TYPE NUM_KEYS (KEY VALUE)* NEXT_LEAF_POSITION
56 | // where
57 | // NODE_TYPE is one byte (0 = Leaf, 1 = NonLeaf)
58 | // NUM_KEYS is one byte unsigned
59 | // KEY is a byte array of fixed size
60 | // VALUE is a byte array of fixed size
61 | // NEXT_LEAF_POSITION is 8 bytes signed long
62 | // Every Leaf has space allocated for maxLeafKeys key value pairs
63 | //////////////////////////////////////////////////
64 |
65 | private static List> createLeafPool(FactoryFile factory, int size) {
66 | return IntStream //
67 | .rangeClosed(1, size) //
68 | .mapToObj(x -> new LeafFile(factory, POSITION_NOT_PRESENT)).collect(Collectors.toList());
69 | }
70 |
71 | @Override
72 | public Leaf createLeaf() {
73 | return getLeaf(leafNextPosition());
74 | }
75 |
76 | private Leaf getLeaf(long position) {
77 | // return new LeafFile(this, position);
78 | LeafFile leaf = leavesPool.get(leavesIndex);
79 | leavesIndex = (leavesIndex + 1) % leavesPool.size();
80 | leaf.position(position);
81 | return leaf;
82 | }
83 |
84 | private int leafBytes() {
85 | return relativeLeafKeyPosition(options.maxLeafKeys()) //
86 | + POSITION_BYTES; // next leaf position
87 | }
88 |
89 | private long leafNextPosition() {
90 | long i = index;
91 | bb.position(index);
92 | bb.put((byte) Leaf.TYPE);
93 | bb.position(index + leafBytes() - POSITION_BYTES);
94 | bb.putLong(POSITION_NOT_PRESENT);
95 | // shift by max size of a leaf node: numKeys, keys, values, next leaf position
96 | // (b+tree pointer to next leaf node)
97 | index += leafBytes();
98 | return i;
99 | }
100 |
101 | private int relativeLeafKeyPosition(int i) {
102 | return NODE_TYPE_BYTES + NUM_KEYS_BYTES + i * (keySerializer.maxSize() + POSITION_BYTES);
103 | }
104 |
105 | public K leafKey(long position, int i) {
106 | long p = position + relativeLeafKeyPosition(i);
107 | bb.position(p);
108 | return keySerializer.read(bb);
109 | }
110 |
111 | public int leafNumKeys(long position) {
112 | bb.position(position + NODE_TYPE_BYTES);
113 | return bb.get() & 0xFF;
114 | }
115 |
116 | public void leafSetNumKeys(long position, int numKeys) {
117 | bb.position(position + NODE_TYPE_BYTES);
118 | bb.put((byte) numKeys);
119 | }
120 |
121 | public V leafValue(long position, int i) {
122 | long p = position + relativeLeafKeyPosition(i) + keySerializer.maxSize();
123 | bb.position(p);
124 | long valuePos = bb.getLong();
125 | values.position(valuePos);
126 | return valueSerializer.read(values);
127 | }
128 |
129 | public void leafSetValue(long position, int i, V value) {
130 | long p = position + relativeLeafKeyPosition(i) + keySerializer.maxSize();
131 | bb.position(p);
132 | bb.putLong(valuesIndex);
133 | values.position(valuesIndex);
134 | valueSerializer.write(values, value);
135 | valuesIndex = values.position();
136 | }
137 |
138 | public void leafInsert(long position, int i, K key, V value) {
139 | int relativeStart = relativeLeafKeyPosition(i);
140 | int relativeFinish = relativeLeafKeyPosition(leafNumKeys(position));
141 |
142 | bb.position(position + relativeStart);
143 | byte[] bytes = new byte[relativeFinish - relativeStart];
144 | bb.get(bytes);
145 |
146 | // copy bytes across one key
147 | bb.position(position + relativeLeafKeyPosition(i + 1));
148 | bb.put(bytes);
149 |
150 | // write inserted key and value
151 | long p = position + relativeStart;
152 | bb.position(p);
153 | keySerializer.write(bb, key);
154 | bb.position(p + keySerializer.maxSize());
155 | bb.putLong(valuesIndex);
156 | values.position(valuesIndex);
157 | valueSerializer.write(values, value);
158 | valuesIndex = values.position();
159 | // increment number of keys in leaf node
160 | leafSetNumKeys(position, leafNumKeys(position) + 1);
161 | }
162 |
163 | public void leafMove(long position, int start, int length, LeafFile other) {
164 | int relativeStart = relativeLeafKeyPosition(start);
165 | int relativeEnd = relativeLeafKeyPosition(start + length);
166 | byte[] bytes = new byte[relativeEnd - relativeStart];
167 | bb.position(position + relativeStart);
168 | bb.get(bytes);
169 | long p = other.position() + relativeLeafKeyPosition(0);
170 | bb.position(p);
171 | bb.put(bytes);
172 | // set the number of keys in source node to be `start`
173 | leafSetNumKeys(position, start);
174 | leafSetNumKeys(other.position(), length);
175 | }
176 |
177 | public void leafSetNext(long position, LeafFile sibling) {
178 | long p = position + relativeLeafKeyPosition(options.maxLeafKeys());
179 | long v;
180 | if (sibling == null) {
181 | v = POSITION_NOT_PRESENT;
182 | } else {
183 | v = sibling.position();
184 | }
185 | bb.position(p);
186 | bb.putLong(v);
187 | }
188 |
189 | public LeafFile leafNext(long position) {
190 | bb.position(position + relativeLeafKeyPosition(options.maxLeafKeys()));
191 | long p = bb.getLong();
192 | if (p == POSITION_NOT_PRESENT) {
193 | return null;
194 | } else {
195 | return new LeafFile(this, p);
196 | }
197 | }
198 |
199 | //////////////////////////////////////////////////
200 | // Format of a NonLeaf
201 | // NODE_TYPE NUM_KEYS (LEFT_CHILD_POSITION KEY)* RIGHT_CHILD_POSITION
202 | // where
203 | // NODE_TYPE is one byte (0 = Leaf, 1 = NonLeaf)
204 | // NUM_KEYS is 1 byte unsigned
205 | // LEFT_CHILD_POSITION is 8 bytes signed long
206 | // KEY is a fixed size byte array
207 | // RIGHT_CHILD_POSITION is 8 bytes signed long
208 | // Every NonLeaf has space allocated for maxNonLeafKeys keys
209 | //////////////////////////////////////////////////
210 |
211 | @Override
212 | public NonLeaf createNonLeaf() {
213 | return new NonLeafFile(this, nextNonLeafPosition());
214 | }
215 |
216 | private int nonLeafBytes() {
217 | // every key has a child node to the left and the final key has a child node to
218 | // the right as well as the left
219 | return NODE_TYPE_BYTES + NUM_NODES_BYTES + options.maxNonLeafKeys() * (POSITION_BYTES + keySerializer.maxSize())
220 | + POSITION_BYTES;
221 | }
222 |
223 | private long nextNonLeafPosition() {
224 | long i = index;
225 | bb.position(index);
226 | bb.put((byte) NonLeaf.TYPE);
227 | index += nonLeafBytes();
228 | return i;
229 | }
230 |
231 | public void nonLeafSetNumKeys(long position, int numKeys) {
232 | bb.position(position + NODE_TYPE_BYTES);
233 | bb.put((byte) numKeys);
234 | }
235 |
236 | public int nonLeafNumKeys(long position) {
237 | bb.position(position + NODE_TYPE_BYTES);
238 | return bb.get() & 0xFF;
239 | }
240 |
241 | public void nonLeafSetChild(long position, int i, NodeFile node) {
242 | long p = position + relativePositionNonLeafEntry(i);
243 | bb.position(p);
244 | bb.putLong(node.position());
245 | }
246 |
247 | private int relativePositionNonLeafEntry(int i) {
248 | return NODE_TYPE_BYTES + NUM_KEYS_BYTES + i * (POSITION_BYTES + keySerializer.maxSize());
249 | }
250 |
251 | public Node nonLeafChild(long position, int i) {
252 | bb.position(position + relativePositionNonLeafEntry(i));
253 | long pos = bb.getLong();
254 | return readNode(pos);
255 | }
256 |
257 | private Node readNode(long pos) {
258 | bb.position(pos);
259 | int type = bb.get();
260 | if (type == Leaf.TYPE) {
261 | return new LeafFile(this, pos);
262 | // return getLeaf(pos);
263 | } else {
264 | return new NonLeafFile(this, pos);
265 | }
266 | }
267 |
268 | public K nonLeafKey(long position, int i) {
269 | bb.position(position + relativePositionNonLeafEntry(i) + POSITION_BYTES);
270 | return keySerializer.read(bb);
271 | }
272 |
273 | public void nonLeafSetKey(long position, int i, K key) {
274 | bb.position(position + relativePositionNonLeafEntry(i) + POSITION_BYTES);
275 | keySerializer.write(bb, key);
276 | }
277 |
278 | public void nonLeafMove(long position, int mid, int length, NonLeafFile other) {
279 | // read array corresponding to latter half of source node and put at beginning
280 | // of other node
281 | int relativeStart = relativePositionNonLeafEntry(mid);
282 | int size = relativePositionNonLeafEntry(mid + length + 1) - relativeStart;
283 | bb.position(position + relativeStart);
284 | byte[] bytes = new byte[size];
285 | bb.get(bytes);
286 | long newPosition = other.position() + relativePositionNonLeafEntry(0);
287 | bb.position(newPosition);
288 | bb.put(bytes);
289 | nonLeafSetNumKeys(position, mid - 1);
290 | nonLeafSetNumKeys(other.position(), length);
291 | }
292 |
293 | public void nonLeafInsert(long position, int i, K key, NodeFile left) {
294 | int numKeys = nonLeafNumKeys(position);
295 | int relativeStart = relativePositionNonLeafEntry(i);
296 | int relativeEnd = relativePositionNonLeafEntry(numKeys) + POSITION_BYTES;
297 | bb.position(position + relativeStart);
298 | byte[] bytes = new byte[relativeEnd - relativeStart];
299 | bb.get(bytes);
300 | bb.position(position + relativePositionNonLeafEntry(i + 1));
301 | bb.put(bytes);
302 | bb.position(position + relativeStart);
303 | bb.putLong(left.position());
304 | keySerializer.write(bb, key);
305 | nonLeafSetNumKeys(position, numKeys + 1);
306 | }
307 |
308 | @Override
309 | public void close() throws Exception {
310 | bb.close();
311 | values.close();
312 | if (onClose != null) {
313 | onClose.run();
314 | }
315 | }
316 |
317 | @Override
318 | public void commit() {
319 | values.commit();
320 | metadata.position(0);
321 | metadata.putLong(valuesIndex);
322 | metadata.commit();
323 | bb.commit();
324 | }
325 |
326 | @Override
327 | public void root(Node node) {
328 | bb.position(0);
329 | bb.putLong(((NodeFile) node).position());
330 | }
331 |
332 | @Override
333 | public Node loadOrCreateRoot() {
334 | bb.position(0);
335 | long rootPosition = bb.getLong();
336 | if (rootPosition == 0) {
337 | bb.position(0);
338 | bb.putLong(POSITION_BYTES);
339 | return createLeaf();
340 | } else {
341 | metadata.position(0);
342 | valuesIndex = metadata.getLong();
343 | return readNode(rootPosition);
344 | }
345 | }
346 |
347 | @Override
348 | public Options options() {
349 | return options;
350 | }
351 |
352 | }
353 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/file/LeafFile.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal.file;
2 |
3 | import com.github.davidmoten.bplustree.internal.Leaf;
4 | import com.github.davidmoten.bplustree.internal.Options;
5 |
6 | public class LeafFile implements Leaf, NodeFile {
7 |
8 | private final FactoryFile factory;
9 | private long position;
10 |
11 | public LeafFile(FactoryFile factory, long position) {
12 | this.factory = factory;
13 | this.position = position;
14 | }
15 |
16 | @Override
17 | public K key(int i) {
18 | return factory.leafKey(position, i);
19 | }
20 |
21 | @Override
22 | public int numKeys() {
23 | return factory.leafNumKeys(position);
24 | }
25 |
26 | @Override
27 | public FactoryFile factory() {
28 | return factory;
29 | }
30 |
31 | @Override
32 | public Options options() {
33 | return factory.options();
34 | }
35 |
36 | @Override
37 | public V value(int index) {
38 | return factory.leafValue(position, index);
39 | }
40 |
41 | @Override
42 | public void setNumKeys(int numKeys) {
43 | factory.leafSetNumKeys(position, numKeys);
44 | }
45 |
46 | @Override
47 | public void setValue(int idx, V value) {
48 | factory.leafSetValue(position, idx, value);
49 | }
50 |
51 | @Override
52 | public void insert(int idx, K key, V value) {
53 | factory.leafInsert(position, idx, key, value);
54 | }
55 |
56 | @Override
57 | public void move(int start, int length, Leaf other) {
58 | factory.leafMove(position, start, length, (LeafFile) other);
59 | }
60 |
61 | @Override
62 | public void setNext(Leaf sibling) {
63 | factory.leafSetNext(position, (LeafFile) sibling);
64 | }
65 |
66 | @Override
67 | public LeafFile next() {
68 | return factory.leafNext(position);
69 | }
70 |
71 | @Override
72 | public long position() {
73 | return position;
74 | }
75 |
76 | @Override
77 | public void position(long position) {
78 | this.position = position;
79 | }
80 |
81 | @Override
82 | public String toString() {
83 | StringBuilder b = new StringBuilder();
84 | b.append("LeafFile [");
85 | b.append("position=");
86 | b.append(position);
87 | b.append(", numKeys=");
88 | b.append(numKeys());
89 | b.append(", keyValues=[");
90 | StringBuilder b2 = new StringBuilder();
91 | int n = numKeys();
92 | for (int i = 0; i < n; i++) {
93 | if (b2.length() > 0) {
94 | b2.append(", ");
95 | }
96 | b2.append(key(i));
97 | b2.append("->");
98 | b2.append(value(i));
99 | }
100 | b.append(b2.toString());
101 | b.append("]");
102 | b.append("]");
103 | return b.toString();
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/file/NodeFile.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal.file;
2 |
3 | public interface NodeFile {
4 | long position();
5 |
6 | void position(long position);
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/file/NonLeafFile.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal.file;
2 |
3 | import com.github.davidmoten.bplustree.internal.Factory;
4 | import com.github.davidmoten.bplustree.internal.Node;
5 | import com.github.davidmoten.bplustree.internal.NonLeaf;
6 | import com.github.davidmoten.bplustree.internal.Options;
7 |
8 | public final class NonLeafFile implements NonLeaf, NodeFile {
9 |
10 | private final FactoryFile factory;
11 | private long position;
12 |
13 | public NonLeafFile(FactoryFile factory, long position) {
14 | this.factory = factory;
15 | this.position = position;
16 | }
17 |
18 | @Override
19 | public Options options() {
20 | return factory.options();
21 | }
22 |
23 | @Override
24 | public Factory factory() {
25 | return factory;
26 | }
27 |
28 | @Override
29 | public void setNumKeys(int numKeys) {
30 | factory.nonLeafSetNumKeys(position, numKeys);
31 | }
32 |
33 | @Override
34 | public int numKeys() {
35 | return factory.nonLeafNumKeys(position);
36 | }
37 |
38 | @Override
39 | public void setChild(int index, Node node) {
40 | factory.nonLeafSetChild(position, index, (NodeFile) node);
41 | }
42 |
43 | @Override
44 | public Node child(int index) {
45 | return factory.nonLeafChild(position, index);
46 | }
47 |
48 | @Override
49 | public K key(int index) {
50 | return factory.nonLeafKey(position, index);
51 | }
52 |
53 | @Override
54 | public void setKey(int index, K key) {
55 | factory.nonLeafSetKey(position, index, key);
56 | }
57 |
58 | @Override
59 | public void move(int mid, NonLeaf other, int length) {
60 | factory.nonLeafMove(position, mid, length, (NonLeafFile) other);
61 |
62 | }
63 |
64 | @Override
65 | public void insert(int idx, K key, Node left) {
66 | factory.nonLeafInsert(position, idx, key, (NodeFile) left);
67 | }
68 |
69 | @Override
70 | public long position() {
71 | return position;
72 | }
73 |
74 | @Override
75 | public void position(long position) {
76 | this.position = position;
77 | }
78 |
79 | @Override
80 | public String toString() {
81 | StringBuilder b = new StringBuilder();
82 | b.append("NonLeafFile [");
83 | b.append("position=");
84 | b.append(position);
85 | b.append(", numKeys=");
86 | b.append(numKeys());
87 | b.append(", keys=[");
88 | StringBuilder b2 = new StringBuilder();
89 | int n = numKeys();
90 | for (int i = 0; i < n; i++) {
91 | if (b2.length() > 0) {
92 | b2.append(", ");
93 | }
94 | b2.append(key(i));
95 | }
96 | b.append(b2.toString());
97 | b.append("]");
98 | b.append("]");
99 | return b.toString();
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/memory/FactoryMemory.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal.memory;
2 |
3 | import com.github.davidmoten.bplustree.internal.Factory;
4 | import com.github.davidmoten.bplustree.internal.Leaf;
5 | import com.github.davidmoten.bplustree.internal.Node;
6 | import com.github.davidmoten.bplustree.internal.NonLeaf;
7 | import com.github.davidmoten.bplustree.internal.Options;
8 |
9 | public final class FactoryMemory implements Factory {
10 |
11 | private final Options options;
12 |
13 | public FactoryMemory(Options options) {
14 | this.options = options;
15 | }
16 |
17 | @Override
18 | public Leaf createLeaf() {
19 | return new LeafMemory(options, this);
20 | }
21 |
22 | @Override
23 | public NonLeaf createNonLeaf() {
24 | return new NonLeafMemory(options, this);
25 | }
26 |
27 | @Override
28 | public void close() throws Exception {
29 | // do nothing
30 | }
31 |
32 | @Override
33 | public void commit() {
34 | // do nothing
35 | }
36 |
37 | @Override
38 | public void root(Node node) {
39 | // do nothing
40 | }
41 |
42 | @Override
43 | public Node loadOrCreateRoot() {
44 | return createLeaf();
45 | }
46 |
47 | @Override
48 | public Options options() {
49 | return options;
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/memory/LeafMemory.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal.memory;
2 |
3 | import java.util.Arrays;
4 |
5 | import com.github.davidmoten.bplustree.internal.Factory;
6 | import com.github.davidmoten.bplustree.internal.Leaf;
7 | import com.github.davidmoten.bplustree.internal.Options;
8 |
9 | public final class LeafMemory implements Leaf {
10 |
11 | private final Options options;
12 | private final Factory factory;
13 | private final K[] keys;
14 | private final V[] values;
15 | private int numKeys;
16 | private Leaf next;
17 |
18 | @SuppressWarnings("unchecked")
19 | public LeafMemory(Options options, Factory factory) {
20 | this.options = options;
21 | keys = (K[]) new Object[options.maxLeafKeys()];
22 | values = (V[]) new Object[options.maxLeafKeys()];
23 | this.factory = factory;
24 | }
25 |
26 | @Override
27 | public V value(int index) {
28 | return values[index];
29 | }
30 |
31 | @Override
32 | public K key(int index) {
33 | return keys[index];
34 | }
35 |
36 | @Override
37 | public int numKeys() {
38 | return numKeys;
39 | }
40 |
41 | @Override
42 | public void move(int start, int length, Leaf other) {
43 | other.setNumKeys(length);
44 | System.arraycopy(keys, start, ((LeafMemory) other).keys, 0, length);
45 | System.arraycopy(values, start, ((LeafMemory) other).values, 0, length);
46 | numKeys = start;
47 | }
48 |
49 | @Override
50 | public void setNumKeys(int numKeys) {
51 | this.numKeys = numKeys;
52 | }
53 |
54 | @Override
55 | public void setValue(int idx, V value) {
56 | values[idx] = value;
57 | }
58 |
59 | @Override
60 | public void insert(int idx, K key, V value) {
61 | System.arraycopy(keys, idx, keys, idx + 1, numKeys - idx);
62 | System.arraycopy(values, idx, values, idx + 1, numKeys - idx);
63 | keys[idx] = key;
64 | values[idx] = value;
65 | numKeys++;
66 | }
67 |
68 | @Override
69 | public void setNext(Leaf next) {
70 | this.next = next;
71 | }
72 |
73 | @Override
74 | public Leaf next() {
75 | return next;
76 | }
77 |
78 | @Override
79 | public Options options() {
80 | return options;
81 | }
82 |
83 | @Override
84 | public Factory factory() {
85 | return factory;
86 | }
87 |
88 | @Override
89 | public String toString() {
90 | StringBuilder builder = new StringBuilder();
91 | builder.append("LeafMemory [");
92 | builder.append("numKeys=");
93 | builder.append(numKeys);
94 | builder.append(", keys=");
95 | builder.append(Arrays.toString(keys));
96 | builder.append(", values=");
97 | builder.append(Arrays.toString(values));
98 | builder.append("]");
99 | return builder.toString();
100 | }
101 |
102 |
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/com/github/davidmoten/bplustree/internal/memory/NonLeafMemory.java:
--------------------------------------------------------------------------------
1 | package com.github.davidmoten.bplustree.internal.memory;
2 |
3 | import java.util.Arrays;
4 |
5 | import com.github.davidmoten.bplustree.internal.Factory;
6 | import com.github.davidmoten.bplustree.internal.Node;
7 | import com.github.davidmoten.bplustree.internal.NonLeaf;
8 | import com.github.davidmoten.bplustree.internal.Options;
9 |
10 | public final class NonLeafMemory implements NonLeaf {
11 |
12 | private final Options options;
13 | private final Factory factory;
14 | private final Node