diff --git a/binding/java/ReadMe.md b/binding/java/ReadMe.md
index ca5ac0b..f4ba058 100644
--- a/binding/java/ReadMe.md
+++ b/binding/java/ReadMe.md
@@ -11,140 +11,57 @@
```
-### 完全基于文件的查询
+### Example
```java
+package org.lionsoul.ip2region.example;
+
import org.lionsoul.ip2region.xdb.Searcher;
-import java.io.*;
+
import java.util.concurrent.TimeUnit;
-public class SearcherTest {
- public static void main(String[] args) {
- // 1、创建 searcher 对象
+/**
+ * @see org.lionsoul.ip2region.xdb.Searcher
+ */
+public class SearcherExample {
+
+ public static void main(String[] args) throws Exception {
String dbPath = "ip2region.xdb file path";
- Searcher searcher = null;
- try {
- searcher = Searcher.newWithFileOnly(dbPath);
- } catch (IOException e) {
- System.out.printf("failed to create searcher with `%s`: %s\n", dbPath, e);
- return;
+
+ // 基于文件查询,单次 search 需要文件 IO,非线程安全,性能最差
+ try (Searcher fileSearcher = Searcher.newWithFileOnly(dbPath)) {
+ searchExample(fileSearcher);
}
- // 2、查询
+ // 基于 VectorIndex 查询,单次 search 需要内存查询 + 文件 IO,非线程安全,性能次之
+ try (Searcher indexSearcher = Searcher.newWithVectorIndex(dbPath)) {
+ searchExample(indexSearcher);
+ }
+
+ // 推荐:基于 Buffer 查询,单次 search 全使用内存查询,线程安全,支持序列化,性能最佳
+ // 可重写 loadFile 方法扩展从其他文件系统(hdfs/s3/oss...)装载 buffer 数据
+ try (Searcher bufferSearcher = Searcher.newWithBuffer(dbPath)) {
+ searchExample(bufferSearcher);
+ }
+
+ // 备注:并发查询时,对于非线程安全实现推荐为每个线程创建一个独立的 searcher 对象单独使用。
+ }
+
+ private static void searchExample(Searcher searcher) {
+ String ip = "1.2.3.4";
try {
- String ip = "1.2.3.4";
long sTime = System.nanoTime();
- String region = searcher.search(ip);
- long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
- System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
+ Searcher.Region region = searcher.searchRegion(ip);
+ long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
+ System.out.printf("{region: %s, searcher: %s, ioCount: %d, took: %d μs}\n",
+ region, searcher.getClass().getSimpleName(), region.getIoCount(), cost);
+ System.out.println(region.toRegionMsg());
} catch (Exception e) {
System.out.printf("failed to search(%s): %s\n", ip, e);
}
-
- // 3、关闭资源
- searcher.close();
-
- // 备注:并发使用,每个线程需要创建一个独立的 searcher 对象单独使用。
}
}
-```
-### 缓存 `VectorIndex` 索引
-
-我们可以提前从 `xdb` 文件中加载出来 `VectorIndex` 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
-```java
-import org.lionsoul.ip2region.xdb.Searcher;
-import java.io.*;
-import java.util.concurrent.TimeUnit;
-
-public class SearcherTest {
- public static void main(String[] args) {
- String dbPath = "ip2region.xdb file path";
-
- // 1、从 dbPath 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
- byte[] vIndex;
- try {
- vIndex = Searcher.loadVectorIndexFromFile(dbPath);
- } catch (Exception e) {
- System.out.printf("failed to load vector index from `%s`: %s\n", dbPath, e);
- return;
- }
-
- // 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
- Searcher searcher;
- try {
- searcher = Searcher.newWithVectorIndex(dbPath, vIndex);
- } catch (Exception e) {
- System.out.printf("failed to create vectorIndex cached searcher with `%s`: %s\n", dbPath, e);
- return;
- }
-
- // 3、查询
- try {
- String ip = "1.2.3.4";
- long sTime = System.nanoTime();
- String region = searcher.search(ip);
- long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
- System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
- } catch (Exception e) {
- System.out.printf("failed to search(%s): %s\n", ip, e);
- }
-
- // 4、关闭资源
- searcher.close();
-
- // 备注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局的制度 vIndex 缓存。
- }
-}
-```
-
-### 缓存整个 `xdb` 数据
-
-我们也可以预先加载整个 ip2region.xdb 的数据到内存,然后基于这个数据创建查询对象来实现完全基于文件的查询,类似之前的 memory search。
-```java
-import org.lionsoul.ip2region.xdb.Searcher;
-import java.io.*;
-import java.util.concurrent.TimeUnit;
-
-public class SearcherTest {
- public static void main(String[] args) {
- String dbPath = "ip2region.xdb file path";
-
- // 1、从 dbPath 加载整个 xdb 到内存。
- byte[] cBuff;
- try {
- cBuff = Searcher.loadContentFromFile(dbPath);
- } catch (Exception e) {
- System.out.printf("failed to load content from `%s`: %s\n", dbPath, e);
- return;
- }
-
- // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
- Searcher searcher;
- try {
- searcher = Searcher.newWithBuffer(cBuff);
- } catch (Exception e) {
- System.out.printf("failed to create content cached searcher: %s\n", e);
- return;
- }
-
- // 3、查询
- try {
- String ip = "1.2.3.4";
- long sTime = System.nanoTime();
- String region = searcher.search(ip);
- long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
- System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
- } catch (Exception e) {
- System.out.printf("failed to search(%s): %s\n", ip, e);
- }
-
- // 4、关闭资源 - 该 searcher 对象可以安全用于并发,等整个服务关闭的时候再关闭 searcher
- // searcher.close();
-
- // 备注:并发使用,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。
- }
-}
```
@@ -174,7 +91,7 @@ options:
例如:使用默认的 data/ip2region.xdb 文件进行查询测试:
```bash
-➜ java git:(v2.0_xdb) ✗ java -jar target/ip2region-2.6.0.jar search --db=../../data/ip2region.xdb
+$ java -jar target/ip2region-2.6.5.jar search --db=../../data/ip2region.xdb
ip2region xdb searcher test program, cachePolicy: vectorIndex
type 'quit' to exit
ip2region>> 1.2.3.4
@@ -182,26 +99,31 @@ ip2region>> 1.2.3.4
ip2region>>
```
-输入 ip 即可进行查询测试,也可以分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同缓存实现的查询效果。
+输入 ip 即可进行查询测试,也可以分别设置 `--cache-policy` 来测试三种不同缓存实现的查询效果。
# bench 测试
可以通过 `java -jar ip2region-{version}.jar bench` 命令来进行 bench 测试,一方面确保 `xdb` 文件没有错误,一方面可以评估查询性能:
```bash
-➜ java git:(v2.0_xdb) ✗ java -jar target/ip2region-2.6.0.jar bench
+java -jar target/ip2region-2.6.5.jar bench
+
java -jar ip2region-{version}.jar bench [command options]
options:
--db string ip2region binary xdb file path
--src string source ip text file path
- --cache-policy string cache policy: file/vectorIndex/content
+ --cache-policy string cache policy: file/vectorIndex/buffer
```
例如:通过默认的 data/ip2region.xdb 和 data/ip.merge.txt 文件进行 bench 测试:
```bash
-➜ java git:(v2.0_xdb) ✗ java -jar target/ip2region-2.6.0.jar bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt
-Bench finished, {cachePolicy: vectorIndex, total: 3417955, took: 8s, cost: 2 μs/op}
+$ java -jar target/ip2region-2.6.5.jar bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt --cache-policy=buffer
+
+-- 笔者笔记本[mac 2.6 GHz 六核Intel Core i7]测试结果案例
+Bench finished, {cachePolicy: buffer, total: 3417955, took: 2s, ioCount: 0, cost: 0 μs/op}
+Bench finished, {cachePolicy: vectorIndex, total: 3417955, took: 28s, ioCount: 43465554, cost: 8 μs/op}
+Bench finished, {cachePolicy: file, total: 3417955, took: 34s, ioCount: 43465554, cost: 9 μs/op}
```
-可以通过分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同缓存实现的效果。
+可以通过分别设置 `--cache-policy` 来测试三种不同缓存实现的效果。
@Note: 注意 bench 使用的 src 文件要是生成对应 xdb 文件相同的源文件。
diff --git a/binding/java/pom.xml b/binding/java/pom.xml
index babaaee..80860b1 100644
--- a/binding/java/pom.xml
+++ b/binding/java/pom.xml
@@ -44,14 +44,38 @@
+
+ org.projectlombok
+ lombok
+ 1.18.24
+ provided
+
junit
junit
- 4.13.1
+ 4.13.2
test
+
+ net.jcip
+ jcip-annotations
+ 1.0
+ provided
+
+
+
+ aliyun
+ https://maven.aliyun.com/repository/public
+
+
+
+ repo1
+ https://repo1.maven.org/maven2/
+
+
+
@@ -103,7 +127,7 @@
- org.lionsoul.ip2region.SearchTest
+ org.lionsoul.ip2region.xdb.SearchTest
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/example/SearcherExample.java b/binding/java/src/main/java/org/lionsoul/ip2region/example/SearcherExample.java
new file mode 100644
index 0000000..a4e4d3d
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/example/SearcherExample.java
@@ -0,0 +1,47 @@
+package org.lionsoul.ip2region.example;
+
+import org.lionsoul.ip2region.xdb.Searcher;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @see org.lionsoul.ip2region.xdb.Searcher
+ */
+public class SearcherExample {
+
+ public static void main(String[] args) throws Exception {
+ String dbPath = "ip2region.xdb file path";
+
+ // 基于文件查询,单次 search 需要文件 IO,非线程安全,性能最差
+ try (Searcher fileSearcher = Searcher.newWithFileOnly(dbPath)) {
+ searchExample(fileSearcher);
+ }
+
+ // 基于 VectorIndex 查询,单次 search 需要内存查询 + 文件 IO,非线程安全,性能次之
+ try (Searcher indexSearcher = Searcher.newWithVectorIndex(dbPath)) {
+ searchExample(indexSearcher);
+ }
+
+ // 推荐:基于 Buffer 查询,单次 search 全使用内存查询,线程安全,支持序列化,性能最佳
+ // 可重写 loadFile 方法扩展从其他文件系统(hdfs/s3/oss...)装载 buffer 数据
+ try (Searcher bufferSearcher = Searcher.newWithBuffer(dbPath)) {
+ searchExample(bufferSearcher);
+ }
+
+ // 备注:并发查询时,对于非线程安全实现推荐为每个线程创建一个独立的 searcher 对象单独使用。
+ }
+
+ private static void searchExample(Searcher searcher) {
+ String ip = "1.2.3.4";
+ try {
+ long sTime = System.nanoTime();
+ Searcher.Region region = searcher.searchRegion(ip);
+ long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
+ System.out.printf("{region: %s, searcher: %s, ioCount: %d, took: %d μs}\n",
+ region, searcher.getClass().getSimpleName(), region.getIoCount(), cost);
+ System.out.println(region.toRegionMsg());
+ } catch (Exception e) {
+ System.out.printf("failed to search(%s): %s\n", ip, e);
+ }
+ }
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/AbstractSearcher.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/AbstractSearcher.java
new file mode 100644
index 0000000..b949dec
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/AbstractSearcher.java
@@ -0,0 +1,106 @@
+package org.lionsoul.ip2region.xdb;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.lionsoul.ip2region.xdb.InternalUtil.*;
+
+/**
+ * @author Li.Wei by 2022/8/6
+ */
+public abstract class AbstractSearcher implements Searcher {
+ // constant defined copied from the xdb maker
+ protected static final int HEADER_INFO_LENGTH = 256;
+ protected static final int VECTOR_INDEX_ROWS = 256;
+ protected static final int VECTOR_INDEX_COLS = 256;
+ protected static final int VECTOR_INDEX_SIZE = 8;
+ protected static final int SEGMENT_INDEX_SIZE = 14;
+
+ @Deprecated
+ public int getIOCount() {
+ return 0;
+ }
+
+ public Region searchRegion(String ip) {
+ return search0(ip2long(ip));
+ }
+
+ public String search(String ip) {
+ return searchRegion(ip).getRegion();
+ }
+
+ private Region search0(long ip) {
+ int ioCount = 0;
+
+ // locate the segment index block based on the vector index
+ int il0 = (int) ((ip >> 24) & 0xFF);
+ int il1 = (int) ((ip >> 16) & 0xFF);
+ int idx = il0 * VECTOR_INDEX_COLS * VECTOR_INDEX_SIZE + il1 * VECTOR_INDEX_SIZE;
+ // System.out.printf("il0: %d, il1: %d, idx: %d\n", il0, il1, idx);
+ PointIndex pointIndex = this.pointIndex(idx);
+ int sPtr = pointIndex.sPtr, ePtr = pointIndex.ePtr;
+ // System.out.printf("sPtr: %d, ePtr: %d\n", sPtr, ePtr);
+
+ // binary search the segment index block to get the region info
+ final byte[] buff = new byte[SEGMENT_INDEX_SIZE];
+ int dataLen = -1, dataPtr = -1;
+ int l = 0, h = (ePtr - sPtr) / SEGMENT_INDEX_SIZE;
+ while (l <= h) {
+ int m = (l + h) >> 1;
+ int p = sPtr + m * SEGMENT_INDEX_SIZE;
+
+ // read the segment index
+ ioCount += read(p, buff);
+
+ long sip = getIntLong(buff, 0);
+ if (ip < sip) {
+ h = m - 1;
+ } else {
+ long eip = getIntLong(buff, 4);
+ if (ip > eip) {
+ l = m + 1;
+ } else {
+ dataLen = getInt2(buff, 8);
+ dataPtr = getInt(buff, 10);
+ break;
+ }
+ }
+ }
+
+ // empty match interception
+ // System.out.printf("dataLen: %d, dataPtr: %d\n", dataLen, dataPtr);
+ if (dataPtr < 0) {
+ return Region.builder().ioCount(ioCount).build();
+ }
+
+ // load and return the region data
+ final byte[] regionBuff = new byte[dataLen];
+ ioCount += read(dataPtr, regionBuff);
+ return Region.builder().region(new String(regionBuff, StandardCharsets.UTF_8)).ioCount(ioCount).build();
+ }
+
+ /**
+ * find index start,end point
+ *
+ * @param idx idx
+ * @return PointIndex
+ */
+ protected abstract PointIndex pointIndex(int idx);
+
+ /**
+ * read 2 buffer
+ *
+ * @param offset offset
+ * @param buffer buffer
+ * @return io count
+ */
+ protected abstract int read(int offset, byte[] buffer);
+
+ protected static class PointIndex {
+ int sPtr, ePtr;
+
+ public PointIndex(int sPtr, int ePtr) {
+ this.sPtr = sPtr;
+ this.ePtr = ePtr;
+ }
+ }
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/BufferSearcher.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/BufferSearcher.java
new file mode 100644
index 0000000..6aaed80
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/BufferSearcher.java
@@ -0,0 +1,73 @@
+package org.lionsoul.ip2region.xdb;
+
+import net.jcip.annotations.ThreadSafe;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.Serializable;
+
+import static org.lionsoul.ip2region.xdb.InternalUtil.getInt;
+
+/**
+ * xdb content buffer, used for in-memory search.
+ */
+@ThreadSafe
+public class BufferSearcher extends AbstractSearcher implements Serializable {
+
+ private final byte[] contentBuff;
+
+ public BufferSearcher(byte[] contentBuff) {
+ this.contentBuff = contentBuff;
+ }
+
+ public BufferSearcher(String dbFile) {
+ try {
+ this.contentBuff = loadFile(dbFile);
+ } catch (IOException e) {
+ throw new SearcherException("load dbFile error: " + e.getMessage() + ", file=" + dbFile, e);
+ }
+ }
+
+ /**
+ * default load local file. override load hdfs/s3/oss...
+ *
+ * @param dbFile dbFile
+ * @return byte array
+ * @throws IOException ex
+ */
+ protected byte[] loadFile(String dbFile) throws IOException {
+ return loadLocalFile(dbFile);
+ }
+
+ @Override
+ protected int read(int offset, byte[] buffer) {
+ System.arraycopy(contentBuff, offset, buffer, 0, buffer.length);
+ return 0;
+ }
+
+ @Override
+ protected PointIndex pointIndex(int idx) {
+ return new PointIndex(
+ getInt(contentBuff, HEADER_INFO_LENGTH + idx),
+ getInt(contentBuff, HEADER_INFO_LENGTH + idx + 4)
+ );
+ }
+
+ @Override
+ public void close() {
+ // nothing
+ }
+
+ private static byte[] loadLocalFile(String dbPath) throws IOException {
+ final byte[] buff;
+ try (RandomAccessFile handle = new RandomAccessFile(dbPath, "r")) {
+ handle.seek(0);
+ buff = new byte[(int) handle.length()];
+ int rLen = handle.read(buff);
+ if (rLen != buff.length) {
+ throw new IOException("incomplete read: read bytes should be " + buff.length);
+ }
+ }
+ return buff;
+ }
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/FileSearcher.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/FileSearcher.java
new file mode 100644
index 0000000..72da748
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/FileSearcher.java
@@ -0,0 +1,52 @@
+package org.lionsoul.ip2region.xdb;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import static org.lionsoul.ip2region.xdb.InternalUtil.getInt;
+
+/**
+ * random access file handle for file based search.
+ */
+public class FileSearcher extends AbstractSearcher {
+ private final RandomAccessFile handle;
+
+ public FileSearcher(String dbFile) {
+ try {
+ this.handle = new RandomAccessFile(dbFile, "r");
+ } catch (FileNotFoundException e) {
+ throw new SearcherException("load dbFile error: " + e.getMessage() + ", file=" + dbFile, e);
+ }
+ }
+
+ @Override
+ protected int read(int offset, byte[] buffer) {
+ int rLen;
+ try {
+ handle.seek(offset);
+ rLen = handle.read(buffer);
+ } catch (SearcherException | IOException e) {
+ throw new SearcherException("read error", e);
+ }
+ if (rLen != buffer.length) {
+ throw new SearcherException("incomplete read: read bytes should be " + buffer.length);
+ }
+ return 2;
+ }
+
+ @Override
+ protected PointIndex pointIndex(int idx) {
+ final byte[] buff = new byte[VECTOR_INDEX_SIZE];
+ read(HEADER_INFO_LENGTH + idx, buff);
+ return new PointIndex(
+ getInt(buff, 0),
+ getInt(buff, 4)
+ );
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.handle.close();
+ }
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Header.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Header.java
index 8ff0680..e651770 100644
--- a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Header.java
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Header.java
@@ -7,9 +7,8 @@
package org.lionsoul.ip2region.xdb;
-import java.awt.image.SampleModel;
-
-public class Header {
+// Internal class
+class Header {
public final int version;
public final int indexPolicy;
public final int createdAt;
@@ -19,21 +18,22 @@ public class Header {
public Header(byte[] buff) {
assert buff.length >= 16;
- version = Searcher.getInt2(buff, 0);
- indexPolicy = Searcher.getInt2(buff, 2);
- createdAt = Searcher.getInt(buff, 4);
- startIndexPtr = Searcher.getInt(buff, 8);
- endIndexPtr = Searcher.getInt(buff, 12);
+ version = InternalUtil.getInt2(buff, 0);
+ indexPolicy = InternalUtil.getInt2(buff, 2);
+ createdAt = InternalUtil.getInt(buff, 4);
+ startIndexPtr = InternalUtil.getInt(buff, 8);
+ endIndexPtr = InternalUtil.getInt(buff, 12);
buffer = buff;
}
- @Override public String toString() {
+ @Override
+ public String toString() {
return "{" +
- "Version: " + version + ',' +
- "IndexPolicy: " + indexPolicy + ',' +
- "CreatedAt: " + createdAt + ',' +
- "StartIndexPtr: " + startIndexPtr + ',' +
- "EndIndexPtr: " + endIndexPtr +
- '}';
+ "Version: " + version + ',' +
+ "IndexPolicy: " + indexPolicy + ',' +
+ "CreatedAt: " + createdAt + ',' +
+ "StartIndexPtr: " + startIndexPtr + ',' +
+ "EndIndexPtr: " + endIndexPtr +
+ '}';
}
}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/InternalUtil.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/InternalUtil.java
new file mode 100644
index 0000000..17e2ecc
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/InternalUtil.java
@@ -0,0 +1,58 @@
+package org.lionsoul.ip2region.xdb;
+
+/**
+ * Internal Util
+ */
+public class InternalUtil {
+ private static final byte[] SHIFT_INDEX = {24, 16, 8, 0};
+
+ /* get an int from a byte array start from the specified offset */
+ protected static long getIntLong(byte[] b, int offset) {
+ return (
+ ((b[offset++] & 0x000000FFL)) |
+ ((b[offset++] << 8) & 0x0000FF00L) |
+ ((b[offset++] << 16) & 0x00FF0000L) |
+ ((b[offset] << 24) & 0xFF000000L)
+ );
+ }
+
+ protected static int getInt(byte[] b, int offset) {
+ return (
+ ((b[offset++] & 0x000000FF)) |
+ ((b[offset++] << 8) & 0x0000FF00) |
+ ((b[offset++] << 16) & 0x00FF0000) |
+ ((b[offset] << 24) & 0xFF000000)
+ );
+ }
+
+ protected static int getInt2(byte[] b, int offset) {
+ return (
+ (b[offset++] & 0x000000FF) |
+ (b[offset] & 0x0000FF00)
+ );
+ }
+
+ /* long int to ip string */
+ public static String long2ip(long ip) {
+ return String.valueOf((ip >> 24) & 0xFF) + '.' +
+ ((ip >> 16) & 0xFF) + '.' + ((ip >> 8) & 0xFF) + '.' + ((ip) & 0xFF);
+ }
+
+ /* check the specified ip address */
+ public static long ip2long(String ip) {
+ String[] ps = ip.split("\\.");
+ if (ps.length != 4) {
+ throw new SearcherException("invalid ip address `" + ip + "`");
+ }
+
+ long ipDst = 0;
+ for (int i = 0; i < ps.length; i++) {
+ int val = Integer.parseInt(ps[i]);
+ if (val > 255) {
+ throw new SearcherException("ip part `" + ps[i] + "` should be less then 256");
+ }
+ ipDst |= ((long) val << SHIFT_INDEX[i]);
+ }
+ return ipDst & 0xFFFFFFFFL;
+ }
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/SearchTest.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/SearchTest.java
similarity index 62%
rename from binding/java/src/main/java/org/lionsoul/ip2region/SearchTest.java
rename to binding/java/src/main/java/org/lionsoul/ip2region/xdb/SearchTest.java
index 5c70266..f501011 100644
--- a/binding/java/src/main/java/org/lionsoul/ip2region/SearchTest.java
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/SearchTest.java
@@ -4,16 +4,23 @@
// @Author Lion
// @Date 2022/06/23
-package org.lionsoul.ip2region;
+package org.lionsoul.ip2region.xdb;
-import org.lionsoul.ip2region.xdb.Searcher;
-
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
+// for test
public class SearchTest {
- public static void printHelp(String[] args) {
+ protected static final String FILE = "file";
+ protected static final String VECTOR_INDEX = "vectorIndex";
+ protected static final String BUFFER = "buffer";
+
+ public static void printHelp() {
System.out.print("ip2region xdb searcher\n");
System.out.print("java -jar ip2region-{version}.jar [command] [command options]\n");
System.out.print("Command: \n");
@@ -21,22 +28,21 @@ public class SearchTest {
System.out.print(" bench search bench test\n");
}
- public static Searcher createSearcher(String dbPath, String cachePolicy) throws IOException {
- if ("file".equals(cachePolicy)) {
- return Searcher.newWithFileOnly(dbPath);
- } else if ("vectorIndex".equals(cachePolicy)) {
- byte[] vIndex = Searcher.loadVectorIndexFromFile(dbPath);
- return Searcher.newWithVectorIndex(dbPath, vIndex);
- } else if ("content".equals(cachePolicy)) {
- byte[] cBuff = Searcher.loadContentFromFile(dbPath);
- return Searcher.newWithBuffer(cBuff);
- } else {
- throw new IOException("invalid cache policy `" + cachePolicy + "`, options: file/vectorIndex/content");
+ public static AbstractSearcher createSearcher(String dbPath, String cachePolicy) throws IOException {
+ switch (cachePolicy) {
+ case FILE:
+ return (AbstractSearcher) Searcher.newWithFileOnly(dbPath);
+ case VECTOR_INDEX:
+ return (AbstractSearcher) Searcher.newWithVectorIndex(dbPath);
+ case BUFFER:
+ return (AbstractSearcher) Searcher.newWithBuffer(dbPath);
+ default:
+ throw new IOException("invalid cache policy `" + cachePolicy + "`, see help");
}
}
- public static void searchTest(String[] args) throws IOException {
- String dbPath = "", cachePolicy = "vectorIndex";
+ public static void searchTest(String[] args) throws Exception {
+ String dbPath = "", cachePolicy = VECTOR_INDEX;
for (final String r : args) {
if (r.length() < 5) {
continue;
@@ -74,16 +80,16 @@ public class SearchTest {
}
Searcher searcher = createSearcher(dbPath, cachePolicy);
- final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
System.out.printf("ip2region xdb searcher test program, cachePolicy: %s\ntype 'quit' to exit\n", cachePolicy);
- while ( true ) {
+ while (true) {
System.out.print("ip2region>> ");
String line = reader.readLine().trim();
- if ( line.length() < 2 ) {
+ if (line.length() < 2) {
continue;
}
- if ( line.equalsIgnoreCase("quit") ) {
+ if (line.equalsIgnoreCase("quit")) {
break;
}
@@ -91,9 +97,9 @@ public class SearchTest {
double sTime = System.nanoTime();
String region = searcher.search(line);
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
- System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
+ System.out.printf("{region: %s, took: %d μs}\n", region, cost);
} catch (Exception e) {
- System.out.printf("{err: %s, ioCount: %d}\n", e, searcher.getIOCount());
+ System.out.printf("{err: %s}\n", e);
}
}
@@ -102,8 +108,8 @@ public class SearchTest {
System.out.println("searcher test program exited, thanks for trying");
}
- public static void benchTest(String[] args) throws IOException {
- String dbPath = "", srcPath = "", cachePolicy = "vectorIndex";
+ public static void benchTest(String[] args) throws Exception {
+ String dbPath = "", srcPath = "", cachePolicy = VECTOR_INDEX;
for (final String r : args) {
if (r.length() < 5) {
continue;
@@ -121,15 +127,19 @@ public class SearchTest {
String key = r.substring(2, sIdx);
String val = r.substring(sIdx + 1);
- if ("db".equals(key)) {
- dbPath = val;
- } else if ("src".equals(key)) {
- srcPath = val;
- } else if ("cache-policy".equals(key)) {
- cachePolicy = val;
- } else {
- System.out.printf("undefined option `%s`\n", r);
- return;
+ switch (key) {
+ case "db":
+ dbPath = val;
+ break;
+ case "src":
+ srcPath = val;
+ break;
+ case "cache-policy":
+ cachePolicy = val;
+ break;
+ default:
+ System.out.printf("undefined option `%s`\n", r);
+ return;
}
}
@@ -138,12 +148,13 @@ public class SearchTest {
System.out.print("options:\n");
System.out.print(" --db string ip2region binary xdb file path\n");
System.out.print(" --src string source ip text file path\n");
- System.out.print(" --cache-policy string cache policy: file/vectorIndex/content\n");
+ System.out.print(" --cache-policy string cache policy: " + FILE + "/" + VECTOR_INDEX + "/" + BUFFER + "\n");
+
return;
}
- Searcher searcher = createSearcher(dbPath, cachePolicy);
- long count = 0, costs = 0, tStart = System.nanoTime();
+ AbstractSearcher searcher = createSearcher(dbPath, cachePolicy);
+ long count = 0, costs = 0, ioCount = 0, tStart = System.nanoTime();
String line;
final BufferedReader reader = new BufferedReader(new FileReader(srcPath));
while ((line = reader.readLine()) != null) {
@@ -156,7 +167,7 @@ public class SearchTest {
long sip;
try {
- sip = Searcher.checkIP(ps[0]);
+ sip = InternalUtil.ip2long(ps[0]);
} catch (Exception e) {
System.out.printf("check start ip `%s`: %s\n", ps[0], e);
return;
@@ -164,7 +175,7 @@ public class SearchTest {
long eip;
try {
- eip = Searcher.checkIP(ps[1]);
+ eip = InternalUtil.ip2long(ps[1]);
} catch (Exception e) {
System.out.printf("check end ip `%s`: %s\n", ps[1], e);
return;
@@ -178,12 +189,12 @@ public class SearchTest {
long mip = (sip + eip) >> 1;
for (final long ip : new long[]{sip, (sip + mip) >> 1, mip, (mip + eip) >> 1, eip}) {
long sTime = System.nanoTime();
- String region = searcher.search(ip);
+ Searcher.Region region = searcher.searchRegion(InternalUtil.long2ip(ip));
costs += System.nanoTime() - sTime;
-
+ ioCount += region.getIoCount();
// check the region info
- if (!ps[2].equals(region)) {
- System.out.printf("failed search(%s) with (%s != %s)\n", Searcher.long2ip(ip), region, ps[2]);
+ if (!ps[2].equals(region.getRegion())) {
+ System.out.printf("failed search(%s) with (%s != %s)\n", InternalUtil.long2ip(ip), region, ps[2]);
return;
}
@@ -194,31 +205,36 @@ public class SearchTest {
reader.close();
searcher.close();
long took = System.nanoTime() - tStart;
- System.out.printf("Bench finished, {cachePolicy: %s, total: %d, took: %ds, cost: %d μs/op}\n",
+ System.out.printf("Bench finished, {cachePolicy: %s, total: %d, took: %ds, ioCount: %d, cost: %d μs/op}\n",
cachePolicy, count, TimeUnit.NANOSECONDS.toSeconds(took),
- count == 0 ? 0 : TimeUnit.NANOSECONDS.toMicros(costs/count));
+ ioCount,
+ count == 0 ? 0 : TimeUnit.NANOSECONDS.toMicros(costs / count));
}
public static void main(String[] args) {
if (args.length < 1) {
- printHelp(args);
+ printHelp();
return;
}
- if ("search".equals(args[0])) {
- try {
- searchTest(args);
- } catch (IOException e) {
- System.out.printf("failed running search test: %s\n", e);
- }
- } else if ("bench".equals(args[0])) {
- try {
- benchTest(args);
- } catch (IOException e) {
- System.out.printf("failed running bench test: %s\n", e);
- }
- } else {
- printHelp(args);
+ switch (args[0]) {
+ case "search":
+ try {
+ searchTest(args);
+ } catch (Exception e) {
+ System.out.printf("failed running search test: %s\n", e);
+ }
+ break;
+ case "bench":
+ try {
+ benchTest(args);
+ } catch (Exception e) {
+ System.out.printf("failed running bench test: %s\n", e);
+ }
+ break;
+ default:
+ printHelp();
+ break;
}
}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java
index fcdec4c..8d7d6f8 100644
--- a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java
@@ -9,264 +9,76 @@ package org.lionsoul.ip2region.xdb;
// @Date 2022/06/23
-import java.io.IOException;
-import java.io.RandomAccessFile;
+import lombok.Builder;
+import lombok.Data;
-public class Searcher {
- // constant defined copied from the xdb maker
- public static final int HeaderInfoLength = 256;
- public static final int VectorIndexRows = 256;
- public static final int VectorIndexCols = 256;
- public static final int VectorIndexSize = 8;
- public static final int SegmentIndexSize = 14;
+/**
+ * @see FileSearcher
+ * @see VectorIndexSearcher
+ * @see BufferSearcher
+ */
+public interface Searcher extends AutoCloseable {
- // random access file handle for file based search
- private final RandomAccessFile handle;
-
- private int ioCount = 0;
-
- // vector index.
- // use the byte[] instead of VectorIndex entry array to keep
- // the minimal memory allocation.
- private final byte[] vectorIndex;
-
- // xdb content buffer, used for in-memory search
- private final byte[] contentBuff;
-
- // --- static method to create searchers
-
- public static Searcher newWithFileOnly(String dbPath) throws IOException {
- return new Searcher(dbPath, null, null);
+ // FileSearcher
+ static Searcher newWithFileOnly(String dbPath) {
+ return new FileSearcher(dbPath);
}
- public static Searcher newWithVectorIndex(String dbPath, byte[] vectorIndex) throws IOException {
- return new Searcher(dbPath, vectorIndex, null);
+ // IndexSearcher
+ static Searcher newWithVectorIndex(String dbPath) {
+ return new VectorIndexSearcher(dbPath);
}
- public static Searcher newWithBuffer(byte[] cBuff) throws IOException {
- return new Searcher(null, null, cBuff);
+ // IndexSearcher
+ static Searcher newWithVectorIndex(String dbPath, byte[] vectorIndex) {
+ return new VectorIndexSearcher(dbPath, vectorIndex);
}
- // --- End of creator
-
- public Searcher(String dbFile, byte[] vectorIndex, byte[] cBuff) throws IOException {
- if (cBuff != null) {
- this.handle = null;
- this.vectorIndex = null;
- this.contentBuff = cBuff;
- } else {
- this.handle = new RandomAccessFile(dbFile, "r");
- this.vectorIndex = vectorIndex;
- this.contentBuff = null;
- }
+ // BufferSearcher
+ static Searcher newWithBuffer(String dbPath) {
+ return new BufferSearcher(dbPath);
}
- public void close() throws IOException {
- if (this.handle != null) {
- this.handle.close();
- }
+ // BufferSearcher
+ static Searcher newWithBuffer(byte[] cBuff) {
+ return new BufferSearcher(cBuff);
}
- public int getIOCount() {
- return ioCount;
- }
+ String search(String ip);
- public String search(String ipStr) throws Exception {
- long ip = checkIP(ipStr);
- return search(ip);
- }
+ Region searchRegion(String ip);
- public String search(long ip) throws IOException {
- // reset the global counter
- this.ioCount = 0;
+ @Data
+ @Builder
+ class Region {
+ private int ioCount;
+ private String region;
- // locate the segment index block based on the vector index
- int sPtr = 0, ePtr = 0;
- int il0 = (int) ((ip >> 24) & 0xFF);
- int il1 = (int) ((ip >> 16) & 0xFF);
- int idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
- // System.out.printf("il0: %d, il1: %d, idx: %d\n", il0, il1, idx);
- if (vectorIndex != null) {
- sPtr = getInt(vectorIndex, idx);
- ePtr = getInt(vectorIndex, idx + 4);
- } else if (contentBuff != null) {
- sPtr = getInt(contentBuff, HeaderInfoLength + idx);
- ePtr = getInt(contentBuff, HeaderInfoLength + idx + 4);
- } else {
- final byte[] buff = new byte[VectorIndexSize];
- read(HeaderInfoLength + idx, buff);
- sPtr = getInt(buff, 0);
- ePtr = getInt(buff, 4);
- }
-
- // System.out.printf("sPtr: %d, ePtr: %d\n", sPtr, ePtr);
-
- // binary search the segment index block to get the region info
- final byte[] buff = new byte[SegmentIndexSize];
- int dataLen = -1, dataPtr = -1;
- int l = 0, h = (ePtr - sPtr) / SegmentIndexSize;
- while (l <= h) {
- int m = (l + h) >> 1;
- int p = sPtr + m * SegmentIndexSize;
-
- // read the segment index
- read(p, buff);
- long sip = getIntLong(buff, 0);
- if (ip < sip) {
- h = m - 1;
- } else {
- long eip = getIntLong(buff, 4);
- if (ip > eip) {
- l = m + 1;
- } else {
- dataLen = getInt2(buff, 8);
- dataPtr = getInt(buff, 10);
- break;
- }
- }
- }
-
- // empty match interception
- // System.out.printf("dataLen: %d, dataPtr: %d\n", dataLen, dataPtr);
- if (dataPtr < 0) {
- return null;
- }
-
- // load and return the region data
- final byte[] regionBuff = new byte[dataLen];
- read(dataPtr, regionBuff);
- return new String(regionBuff, "utf-8");
- }
-
- protected void read(int offset, byte[] buffer) throws IOException {
- // check the in-memory buffer first
- if (contentBuff != null) {
- // @TODO: reduce data copying, directly decode the data ?
- System.arraycopy(contentBuff, offset, buffer, 0, buffer.length);
- return;
- }
-
- // read from the file handle
- assert handle != null;
- handle.seek(offset);
-
- this.ioCount++;
- int rLen = handle.read(buffer);
- if (rLen != buffer.length) {
- throw new IOException("incomplete read: read bytes should be " + buffer.length);
- }
- }
-
- // --- static cache util function
-
- public static Header loadHeader(RandomAccessFile handle) throws IOException {
- handle.seek(0);
- final byte[] buff = new byte[HeaderInfoLength];
- handle.read(buff);
- return new Header(buff);
- }
-
- public static Header loadHeaderFromFile(String dbPath) throws IOException {
- final RandomAccessFile handle = new RandomAccessFile(dbPath, "r");
- final Header header = loadHeader(handle);
- handle.close();
- return header;
- }
-
- public static byte[] loadVectorIndex(RandomAccessFile handle) throws IOException {
- handle.seek(HeaderInfoLength);
- int len = VectorIndexRows * VectorIndexCols * SegmentIndexSize;
- final byte[] buff = new byte[len];
- int rLen = handle.read(buff);
- if (rLen != len) {
- throw new IOException("incomplete read: read bytes should be " + len);
- }
-
- return buff;
- }
-
- public static byte[] loadVectorIndexFromFile(String dbPath) throws IOException {
- final RandomAccessFile handle = new RandomAccessFile(dbPath, "r");
- final byte[] vIndex = loadVectorIndex(handle);
- handle.close();
- return vIndex;
- }
-
- public static byte[] loadContent(RandomAccessFile handle) throws IOException {
- handle.seek(0);
- final byte[] buff = new byte[(int) handle.length()];
- int rLen = handle.read(buff);
- if (rLen != buff.length) {
- throw new IOException("incomplete read: read bytes should be " + buff.length);
- }
-
- return buff;
- }
-
- public static byte[] loadContentFromFile(String dbPath) throws IOException {
- final RandomAccessFile handle = new RandomAccessFile(dbPath, "r");
- final byte[] content = loadContent(handle);
- handle.close();
- return content;
- }
-
- // --- End cache load util function
-
- // --- static util method
-
- /* get an int from a byte array start from the specified offset */
- public static long getIntLong(byte[] b, int offset) {
- return (
- ((b[offset++] & 0x000000FFL)) |
- ((b[offset++] << 8) & 0x0000FF00L) |
- ((b[offset++] << 16) & 0x00FF0000L) |
- ((b[offset ] << 24) & 0xFF000000L)
- );
- }
-
- public static int getInt(byte[] b, int offset) {
- return (
- ((b[offset++] & 0x000000FF)) |
- ((b[offset++] << 8) & 0x0000FF00) |
- ((b[offset++] << 16) & 0x00FF0000) |
- ((b[offset ] << 24) & 0xFF000000)
- );
- }
-
- public static int getInt2(byte[] b, int offset) {
- return (
- (b[offset++] & 0x000000FF) |
- (b[offset ] & 0x0000FF00)
- );
- }
-
- /* long int to ip string */
- public static String long2ip( long ip )
- {
- return String.valueOf((ip >> 24) & 0xFF) + '.' +
- ((ip >> 16) & 0xFF) + '.' + ((ip >> 8) & 0xFF) + '.' + ((ip) & 0xFF);
- }
-
- public static final byte[] shiftIndex = {24, 16, 8, 0};
-
- /* check the specified ip address */
- public static long checkIP(String ip) throws Exception {
- String[] ps = ip.split("\\.");
- if (ps.length != 4) {
- throw new Exception("invalid ip address `" + ip + "`");
- }
-
- long ipDst = 0;
- for (int i = 0; i < ps.length; i++) {
- int val = Integer.parseInt(ps[i]);
- if (val > 255) {
- throw new Exception("ip part `"+ps[i]+"` should be less then 256");
+ public RegionMsg toRegionMsg() {
+ if (region == null) {
+ return null;
}
- ipDst |= ((long) val << shiftIndex[i]);
+ final String[] ss = region.split("\\|");
+ return RegionMsg.builder()
+ .country(ss[0])
+ .region(ss[1])
+ .province(ss[2])
+ .city(ss[3])
+ .isp(ss[4])
+ .build();
}
-
- return ipDst & 0xFFFFFFFFL;
}
+ // Country|Region|Province|City|ISP
+ // 国家|区域|省份|城市|ISP
+ @Data
+ @Builder
+ class RegionMsg {
+ private String country;
+ private String region;
+ private String province;
+ private String city;
+ private String isp;
+ }
}
\ No newline at end of file
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/SearcherException.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/SearcherException.java
new file mode 100644
index 0000000..e93ce5d
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/SearcherException.java
@@ -0,0 +1,25 @@
+package org.lionsoul.ip2region.xdb;
+
+/**
+ * Searcher RuntimeException
+ */
+public class SearcherException extends RuntimeException {
+ public SearcherException() {
+ }
+
+ public SearcherException(String message) {
+ super(message);
+ }
+
+ public SearcherException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SearcherException(Throwable cause) {
+ super(cause);
+ }
+
+ public SearcherException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/UtilTest.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/UtilTest.java
similarity index 84%
rename from binding/java/src/main/java/org/lionsoul/ip2region/UtilTest.java
rename to binding/java/src/main/java/org/lionsoul/ip2region/xdb/UtilTest.java
index aba7779..14fe249 100644
--- a/binding/java/src/main/java/org/lionsoul/ip2region/UtilTest.java
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/UtilTest.java
@@ -4,17 +4,16 @@
// @Author Lion
// @Date 2022/06/23
-package org.lionsoul.ip2region;
-
-import org.lionsoul.ip2region.xdb.Searcher;
+package org.lionsoul.ip2region.xdb;
+// for test
public class UtilTest {
public static void testIP2Long() {
String ip = "1.2.3.4";
long ipAddr = 0;
try {
- ipAddr = Searcher.checkIP(ip);
+ ipAddr = InternalUtil.ip2long(ip);
} catch (Exception e) {
System.out.printf("failed to check ip: %s\n", e);
return;
@@ -25,7 +24,7 @@ public class UtilTest {
return;
}
- String ip2 = Searcher.long2ip(ipAddr);
+ String ip2 = InternalUtil.long2ip(ipAddr);
if (!ip.equals(ip2)) {
System.out.print("failed long2ip\n");
return;
diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/VectorIndexSearcher.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/VectorIndexSearcher.java
new file mode 100644
index 0000000..a920fef
--- /dev/null
+++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/VectorIndexSearcher.java
@@ -0,0 +1,52 @@
+package org.lionsoul.ip2region.xdb;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import static org.lionsoul.ip2region.xdb.InternalUtil.getInt;
+
+/**
+ * vector index.
+ * use the byte[] instead of VectorIndex entry array to keep the minimal memory allocation.
+ */
+public class VectorIndexSearcher extends FileSearcher {
+
+ private final byte[] vectorIndex;
+
+ public VectorIndexSearcher(String dbFile) {
+ this(dbFile, null);
+ }
+
+ public VectorIndexSearcher(String dbFile, byte[] vectorIndex) {
+ super(dbFile);
+ try {
+ this.vectorIndex = vectorIndex != null ? vectorIndex : loadVectorIndexFromFile(dbFile);
+ } catch (SearcherException | IOException e) {
+ throw new SearcherException("load dbFile error: " + e.getMessage() + ", file=" + dbFile, e);
+ }
+ }
+
+ @Override
+ protected PointIndex pointIndex(int idx) {
+ return new PointIndex(
+ getInt(vectorIndex, idx),
+ getInt(vectorIndex, idx + 4)
+ );
+ }
+
+ // --- static util function
+
+ private static byte[] loadVectorIndexFromFile(String dbPath) throws IOException {
+ final byte[] buff;
+ try (RandomAccessFile handle = new RandomAccessFile(dbPath, "r")) {
+ handle.seek(HEADER_INFO_LENGTH);
+ int len = VECTOR_INDEX_ROWS * VECTOR_INDEX_COLS * SEGMENT_INDEX_SIZE;
+ buff = new byte[len];
+ int rLen = handle.read(buff);
+ if (rLen != len) {
+ throw new IOException("incomplete read: read bytes should be " + len);
+ }
+ }
+ return buff;
+ }
+}