Skip to content

Commit d13758d

Browse files
authored
[kv] Use JDK9 Arrays#equals() to check bytes prefix for PrefixLookup (alibaba#331)
1 parent f96c392 commit d13758d

File tree

2 files changed

+101
-25
lines changed

2 files changed

+101
-25
lines changed

fluss-common/src/main/java/com/alibaba/fluss/utils/BytesUtils.java

+99
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package com.alibaba.fluss.utils;
1818

1919
import com.alibaba.fluss.annotation.Internal;
20+
import com.alibaba.fluss.utils.crc.Java;
2021

22+
import java.lang.reflect.Method;
2123
import java.nio.ByteBuffer;
24+
import java.util.Arrays;
2225

2326
/** Utils for bytes. */
2427
@Internal
@@ -58,4 +61,100 @@ public static byte[] toArray(ByteBuffer buffer, int offset, int size) {
5861
}
5962
return dest;
6063
}
64+
65+
/**
66+
* Check if the given first byte array ({@code prefix}) is a prefix of the second byte array
67+
* ({@code bytes}).
68+
*
69+
* @param prefix the prefix byte array
70+
* @param bytes the byte array to check if it has the prefix
71+
* @return true if the given bytes has the given prefix, false otherwise.
72+
*/
73+
public static boolean prefixEquals(byte[] prefix, byte[] bytes) {
74+
return BEST_EQUAL_COMPARER.prefixEquals(prefix, bytes);
75+
}
76+
77+
// -------------------------------------------------------------------------------------------
78+
79+
private static final BytesPrefixComparer BEST_EQUAL_COMPARER;
80+
81+
static {
82+
if (Java.IS_JAVA9_COMPATIBLE) {
83+
BEST_EQUAL_COMPARER = new Java9BytesPrefixComparer();
84+
} else {
85+
BEST_EQUAL_COMPARER = new PureJavaBytesPrefixComparer();
86+
}
87+
}
88+
89+
/** Compare two byte arrays for equality. */
90+
private interface BytesPrefixComparer {
91+
92+
/**
93+
* Check if the given first byte array ({@code prefix}) is a prefix of the second byte array
94+
* ({@code bytes}).
95+
*
96+
* @param prefix The prefix byte array
97+
* @param bytes The byte array to check if it has the prefix
98+
* @return true if the given bytes has the given prefix, false otherwise
99+
*/
100+
boolean prefixEquals(byte[] prefix, byte[] bytes);
101+
}
102+
103+
private static final class Java9BytesPrefixComparer implements BytesPrefixComparer {
104+
private static final Method EQUALS_METHOD;
105+
106+
static {
107+
try {
108+
EQUALS_METHOD =
109+
Class.forName(Arrays.class.getName())
110+
.getMethod(
111+
"equals",
112+
byte[].class,
113+
int.class,
114+
int.class,
115+
byte[].class,
116+
int.class,
117+
int.class);
118+
} catch (Exception e) {
119+
throw new RuntimeException("Failed to load Arrays.equals method", e);
120+
}
121+
}
122+
123+
@Override
124+
public boolean prefixEquals(byte[] prefix, byte[] bytes) {
125+
if (prefix.length > bytes.length) {
126+
return false;
127+
}
128+
try {
129+
int fromIndex = 0; // inclusive
130+
int toIndex = prefix.length; // exclusive
131+
return (boolean)
132+
EQUALS_METHOD.invoke(
133+
null, prefix, fromIndex, toIndex, bytes, fromIndex, toIndex);
134+
} catch (Throwable e) {
135+
// should never happen
136+
throw new RuntimeException(e);
137+
}
138+
}
139+
}
140+
141+
/**
142+
* A pure Java implementation of the {@link BytesPrefixComparer} that does not rely on Java 9
143+
* APIs and compares bytes one by one which is slower.
144+
*/
145+
private static final class PureJavaBytesPrefixComparer implements BytesPrefixComparer {
146+
147+
@Override
148+
public boolean prefixEquals(byte[] prefix, byte[] bytes) {
149+
if (prefix.length > bytes.length) {
150+
return false;
151+
}
152+
for (int i = 0; i < prefix.length; i++) {
153+
if (prefix[i] != bytes[i]) {
154+
return false;
155+
}
156+
}
157+
return true;
158+
}
159+
}
61160
}

fluss-server/src/main/java/com/alibaba/fluss/server/kv/rocksdb/RocksDBKv.java

+2-25
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.alibaba.fluss.exception.FlussRuntimeException;
2020
import com.alibaba.fluss.rocksdb.RocksDBOperationUtils;
2121
import com.alibaba.fluss.server.utils.ResourceGuard;
22+
import com.alibaba.fluss.utils.BytesUtils;
2223
import com.alibaba.fluss.utils.IOUtils;
2324

2425
import org.rocksdb.ColumnFamilyHandle;
@@ -106,7 +107,7 @@ public List<byte[]> prefixLookup(byte[] prefixKey) {
106107
RocksIterator iterator = db.newIterator(defaultColumnFamilyHandle, readOptions);
107108
try {
108109
iterator.seek(prefixKey);
109-
while (iterator.isValid() && isPrefixEquals(prefixKey, iterator.key())) {
110+
while (iterator.isValid() && BytesUtils.prefixEquals(prefixKey, iterator.key())) {
110111
pkList.add(iterator.value());
111112
iterator.next();
112113
}
@@ -204,28 +205,4 @@ public void close() throws Exception {
204205
public RocksDB getDb() {
205206
return db;
206207
}
207-
208-
/**
209-
* Check if the given first byte array ({@code prefix}) is a prefix of the second byte array
210-
* ({@code bytes}).
211-
*
212-
* @param prefix The prefix byte array
213-
* @param bytes The byte array to check if it has the prefix
214-
* @return true if the given bytes has the given prefix, false otherwise
215-
*/
216-
public static boolean isPrefixEquals(byte[] prefix, byte[] bytes) {
217-
// TODO, This is very inefficient to compare arrays byte by byte. In the future we can
218-
// use JDK9 Arrays.compare(compare(byte[] a, int aFromIndex, int aToIndex, byte[] b, int
219-
// bFromIndex, int bToIndex)) to instead. See issue:
220-
// https://github.com/alibaba/fluss/issues/271
221-
if (prefix.length > bytes.length) {
222-
return false;
223-
}
224-
for (int i = 0; i < prefix.length; i++) {
225-
if (prefix[i] != bytes[i]) {
226-
return false;
227-
}
228-
}
229-
return true;
230-
}
231208
}

0 commit comments

Comments
 (0)