Skip to content

Commit c6468e8

Browse files
committed
Use MultiBuffer when DB size can't fit in SingleBuffer
1 parent c50c423 commit c6468e8

File tree

6 files changed

+72
-47
lines changed

6 files changed

+72
-47
lines changed

src/main/java/com/maxmind/db/BufferHolder.java

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
11
package com.maxmind.db;
22

33
import com.maxmind.db.Reader.FileMode;
4-
import java.io.ByteArrayOutputStream;
54
import java.io.File;
65
import java.io.IOException;
76
import java.io.InputStream;
87
import java.io.RandomAccessFile;
98
import java.nio.channels.FileChannel;
9+
import java.util.ArrayList;
10+
import java.util.List;
1011

1112
final class BufferHolder {
1213
// DO NOT PASS OUTSIDE THIS CLASS. Doing so will remove thread safety.
1314
private final Buffer buffer;
1415

1516
BufferHolder(File database, FileMode mode) throws IOException {
16-
try (
17-
final RandomAccessFile file = new RandomAccessFile(database, "r");
18-
final FileChannel channel = file.getChannel()
19-
) {
17+
try (RandomAccessFile file = new RandomAccessFile(database, "r");
18+
FileChannel channel = file.getChannel()) {
19+
long size = channel.size();
2020
if (mode == FileMode.MEMORY) {
21-
final SingleBuffer buf = SingleBuffer.wrap(new byte[(int) channel.size()]);
21+
Buffer buf;
22+
if (size <= Integer.MAX_VALUE) {
23+
buf = new SingleBuffer(size);
24+
} else {
25+
buf = new MultiBuffer(size);
26+
}
2227
if (buf.readFrom(channel) != buf.capacity()) {
2328
throw new IOException("Unable to read "
24-
+ database.getName()
25-
+ " into memory. Unexpected end of stream.");
29+
+ database.getName()
30+
+ " into memory. Unexpected end of stream.");
2631
}
2732
this.buffer = buf;
2833
} else {
29-
this.buffer = SingleBuffer.mapFromChannel(channel);
34+
if (size <= Integer.MAX_VALUE) {
35+
this.buffer = SingleBuffer.mapFromChannel(channel);
36+
} else {
37+
this.buffer = MultiBuffer.mapFromChannel(channel);
38+
}
3039
}
3140
}
3241
}
@@ -42,13 +51,30 @@ final class BufferHolder {
4251
if (null == stream) {
4352
throw new NullPointerException("Unable to use a NULL InputStream");
4453
}
45-
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
46-
final byte[] bytes = new byte[16 * 1024];
47-
int br;
48-
while (-1 != (br = stream.read(bytes))) {
49-
baos.write(bytes, 0, br);
54+
final int CHUNK_SIZE = 16 * 1024;
55+
List<byte[]> chunks = new ArrayList<>();
56+
long total = 0;
57+
int read;
58+
byte[] tmp = new byte[CHUNK_SIZE];
59+
60+
while (-1 != (read = stream.read(tmp))) {
61+
byte[] copy = new byte[read];
62+
System.arraycopy(tmp, 0, copy, 0, read);
63+
chunks.add(copy);
64+
total += read;
65+
}
66+
67+
if (total <= Integer.MAX_VALUE) {
68+
byte[] data = new byte[(int) total];
69+
int pos = 0;
70+
for (byte[] chunk : chunks) {
71+
System.arraycopy(chunk, 0, data, pos, chunk.length);
72+
pos += chunk.length;
73+
}
74+
this.buffer = SingleBuffer.wrap(data);
75+
} else {
76+
this.buffer = MultiBuffer.wrap(chunks);
5077
}
51-
this.buffer = SingleBuffer.wrap(baos.toByteArray());
5278
}
5379

5480
/*

src/main/java/com/maxmind/db/Metadata.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ public final class Metadata {
2424

2525
private final int nodeByteSize;
2626

27-
private final int nodeCount;
27+
private final long nodeCount;
2828

2929
private final int recordSize;
3030

31-
private final int searchTreeSize;
31+
private final long searchTreeSize;
3232

3333
/**
3434
* Constructs a {@code Metadata} object.
@@ -71,7 +71,7 @@ public Metadata(
7171
this.languages = languages;
7272
this.description = description;
7373
this.ipVersion = ipVersion;
74-
this.nodeCount = (int) nodeCount;
74+
this.nodeCount = nodeCount;
7575
this.recordSize = recordSize;
7676

7777
this.nodeByteSize = this.recordSize / 4;
@@ -140,7 +140,7 @@ int getNodeByteSize() {
140140
/**
141141
* @return the number of nodes in the search tree.
142142
*/
143-
int getNodeCount() {
143+
long getNodeCount() {
144144
return this.nodeCount;
145145
}
146146

@@ -155,7 +155,7 @@ int getRecordSize() {
155155
/**
156156
* @return the searchTreeSize
157157
*/
158-
int getSearchTreeSize() {
158+
long getSearchTreeSize() {
159159
return this.searchTreeSize;
160160
}
161161

src/main/java/com/maxmind/db/Networks.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ static class NetworkNode {
173173
/** The prefix of the node. */
174174
public int prefix;
175175
/** The node number. */
176-
public int pointer;
176+
public long pointer;
177177

178178
/**
179179
* Constructs a network node for internal use.
@@ -182,7 +182,7 @@ static class NetworkNode {
182182
* @param prefix The prefix of the node.
183183
* @param pointer The node number
184184
*/
185-
NetworkNode(byte[] ip, int prefix, int pointer) {
185+
NetworkNode(byte[] ip, int prefix, long pointer) {
186186
this.ip = ip;
187187
this.prefix = prefix;
188188
this.pointer = pointer;

src/main/java/com/maxmind/db/Reader.java

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public final class Reader implements Closeable {
2222
(byte) 0xCD, (byte) 0xEF, 'M', 'a', 'x', 'M', 'i', 'n', 'd', '.',
2323
'c', 'o', 'm'};
2424

25-
private final int ipV4Start;
25+
private final long ipV4Start;
2626
private final Metadata metadata;
2727
private final AtomicReference<BufferHolder> bufferHolderReference;
2828
private final NodeCache cache;
@@ -152,7 +152,7 @@ public <T> T get(InetAddress ipAddress, Class<T> cls) throws IOException {
152152
return getRecord(ipAddress, cls).getData();
153153
}
154154

155-
int getIpv4Start() {
155+
long getIpv4Start() {
156156
return this.ipV4Start;
157157
}
158158

@@ -171,12 +171,12 @@ public <T> DatabaseRecord<T> getRecord(InetAddress ipAddress, Class<T> cls)
171171

172172
byte[] rawAddress = ipAddress.getAddress();
173173

174-
int[] traverseResult = traverseTree(rawAddress, rawAddress.length * 8);
174+
long[] traverseResult = traverseTree(rawAddress, rawAddress.length * 8);
175175

176-
int pl = traverseResult[1];
177-
int record = traverseResult[0];
176+
long record = traverseResult[0];
177+
int pl = (int) traverseResult[1];
178178

179-
int nodeCount = this.metadata.getNodeCount();
179+
long nodeCount = this.metadata.getNodeCount();
180180
Buffer buffer = this.getBufferHolder().get();
181181
T dataRecord = null;
182182
if (record > nodeCount) {
@@ -253,7 +253,7 @@ BufferHolder getBufferHolder() throws ClosedDatabaseException {
253253
return bufferHolder;
254254
}
255255

256-
private int startNode(int bitLength) {
256+
private long startNode(int bitLength) {
257257
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
258258
// is the case, we can skip over the first 96 nodes.
259259
if (this.metadata.getIpVersion() == 6 && bitLength == 32) {
@@ -264,13 +264,13 @@ private int startNode(int bitLength) {
264264
return 0;
265265
}
266266

267-
private int findIpV4StartNode(Buffer buffer)
267+
private long findIpV4StartNode(Buffer buffer)
268268
throws InvalidDatabaseException {
269269
if (this.metadata.getIpVersion() == 4) {
270270
return 0;
271271
}
272272

273-
int node = 0;
273+
long node = 0;
274274
for (int i = 0; i < 96 && node < this.metadata.getNodeCount(); i++) {
275275
node = this.readNode(buffer, node, 0);
276276
}
@@ -319,9 +319,9 @@ public <T> Networks<T> networksWithin(
319319
prefixLength += 96;
320320
}
321321

322-
int[] traverseResult = this.traverseTree(ipBytes, prefixLength);
323-
int node = traverseResult[0];
324-
int prefix = traverseResult[1];
322+
long[] traverseResult = this.traverseTree(ipBytes, prefixLength);
323+
long node = traverseResult[0];
324+
int prefix = (int) traverseResult[1];
325325

326326
return new Networks<>(this, includeAliasedNetworks,
327327
new Networks.NetworkNode[] {new Networks.NetworkNode(ipBytes, prefix, node)},
@@ -335,12 +335,12 @@ public <T> Networks<T> networksWithin(
335335
* @param bitCount The prefix.
336336
* @return int[]
337337
*/
338-
private int[] traverseTree(byte[] ip, int bitCount)
338+
private long[] traverseTree(byte[] ip, int bitCount)
339339
throws ClosedDatabaseException, InvalidDatabaseException {
340340
Buffer buffer = this.getBufferHolder().get();
341341
int bitLength = ip.length * 8;
342-
int record = this.startNode(bitLength);
343-
int nodeCount = this.metadata.getNodeCount();
342+
long record = this.startNode(bitLength);
343+
long nodeCount = this.metadata.getNodeCount();
344344

345345
int i = 0;
346346
for (; i < bitCount && record < nodeCount; i++) {
@@ -352,19 +352,19 @@ int record = this.startNode(bitLength);
352352
record = this.readNode(buffer, record, bit);
353353
}
354354

355-
return new int[]{record, i};
355+
return new long[]{record, i};
356356
}
357357

358-
int readNode(Buffer buffer, int nodeNumber, int index)
358+
int readNode(Buffer buffer, long nodeNumber, int index)
359359
throws InvalidDatabaseException {
360360
// index is the index of the record within the node, which
361361
// can either be 0 or 1.
362-
int baseOffset = nodeNumber * this.metadata.getNodeByteSize();
362+
long baseOffset = nodeNumber * this.metadata.getNodeByteSize();
363363

364364
switch (this.metadata.getRecordSize()) {
365365
case 24:
366366
// For a 24 bit record, each record is 3 bytes.
367-
buffer.position(baseOffset + index * 3);
367+
buffer.position(baseOffset + (long) index * 3);
368368
return Decoder.decodeInteger(buffer, 0, 3);
369369
case 28:
370370
int middle = buffer.get(baseOffset + 3);
@@ -377,10 +377,10 @@ int readNode(Buffer buffer, int nodeNumber, int index)
377377
// We get the most significant byte of the second record.
378378
middle = 0x0F & middle;
379379
}
380-
buffer.position(baseOffset + index * 4);
380+
buffer.position(baseOffset + (long) index * 4);
381381
return Decoder.decodeInteger(buffer, middle, 3);
382382
case 32:
383-
buffer.position(baseOffset + index * 4);
383+
buffer.position(baseOffset + (long) index * 4);
384384
return Decoder.decodeInteger(buffer, 0, 4);
385385
default:
386386
throw new InvalidDatabaseException("Unknown record size: "
@@ -390,10 +390,10 @@ int readNode(Buffer buffer, int nodeNumber, int index)
390390

391391
<T> T resolveDataPointer(
392392
Buffer buffer,
393-
int pointer,
393+
long pointer,
394394
Class<T> cls
395395
) throws IOException {
396-
int resolved = (pointer - this.metadata.getNodeCount())
396+
long resolved = (pointer - this.metadata.getNodeCount())
397397
+ this.metadata.getSearchTreeSize();
398398

399399
if (resolved >= buffer.capacity()) {

src/test/java/com/maxmind/db/DecoderTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import java.io.IOException;
1010
import java.math.BigInteger;
11-
import java.nio.ByteBuffer;
1211
import java.nio.charset.StandardCharsets;
1312
import java.util.ArrayList;
1413
import java.util.HashMap;

src/test/java/com/maxmind/db/TestDecoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
final class TestDecoder extends Decoder {
77

8-
TestDecoder(NodeCache cache, SingleBuffer buffer, long pointerBase) {
8+
TestDecoder(NodeCache cache, Buffer buffer, long pointerBase) {
99
super(cache, buffer, pointerBase);
1010
}
1111

0 commit comments

Comments
 (0)