blob: 53c7357fd04611a9dbbadd5c1097e7b60cf5216e [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.pinot.spi.utils;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Wrapper around byte[] that provides additional features such as:
* <ul>
* <li>Implements comparable interface, so comparison and sorting can be performed</li>
* <li>Implements equals() and hashCode(), so it can be used as key for HashMap/Set</li>
* <li>Caches the hash code of the byte[]</li>
* </ul>
* NOTE: DO NOT reuse the bytes when hash is needed because hash is only calculated once and cached.
*/
public class ByteArray implements Comparable<ByteArray>, Serializable {
private static final Logger LOGGER = LoggerFactory.getLogger(ByteArray.class);
private static final MethodHandle COMPARE_UNSIGNED;
static {
MethodHandle compareUnsigned = null;
try {
compareUnsigned = MethodHandles.publicLookup().findStatic(Arrays.class, "compareUnsigned",
MethodType.methodType(int.class, byte[].class, int.class, int.class, byte[].class, int.class, int.class));
} catch (Exception ignored) {
LOGGER.warn("Arrays.compareUnsigned unavailable - this may have a performance impact (are you using JDK8?)");
}
COMPARE_UNSIGNED = compareUnsigned;
}
private final byte[] _bytes;
// Hash for empty ByteArray is 1
private int _hash = 1;
public ByteArray(byte[] bytes) {
_bytes = bytes;
}
public byte[] getBytes() {
return _bytes;
}
public int length() {
return _bytes.length;
}
public String toHexString() {
return BytesUtils.toHexString(_bytes);
}
@Override
public String toString() {
return toHexString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ByteArray bytes = (ByteArray) o;
return Arrays.equals(_bytes, bytes._bytes);
}
@Override
public int hashCode() {
int hash = _hash;
if (hash == 1 && _bytes.length > 0) {
int i = 0;
for (; i + 7 < _bytes.length; i += 8) {
hash = -1807454463 * hash
+ 1742810335 * _bytes[i]
+ 887503681 * _bytes[i + 1]
+ 28629151 * _bytes[i + 2]
+ 923521 * _bytes[i + 3]
+ 29791 * _bytes[i + 4]
+ 961 * _bytes[i + 5]
+ 31 * _bytes[i + 6]
+ _bytes[i + 7];
}
for (; i < _bytes.length; i++) {
hash = 31 * hash + _bytes[i];
}
_hash = hash;
}
return hash;
}
@Override
public int compareTo(ByteArray that) {
if (this == that) {
return 0;
}
return compare(_bytes, that._bytes);
}
/**
* Compares two byte[] values. The comparison performed is on unsigned value for each byte.
* Returns:
* <ul>
* <li> 0 if both values are identical. </li>
* <li> -ve integer if first value is smaller than the second. </li>
* <li> +ve integer if first value is larger than the second. </li>
* </ul>
*
* @param left First byte[] to compare.
* @param right Second byte[] to compare.
* @return Result of comparison as stated above.
*/
public static int compare(byte[] left, byte[] right) {
return compare(left, 0, left.length, right, 0, right.length);
}
/**
* Compares two byte[] values. The comparison performed is on unsigned value for each byte.
* Returns:
* <ul>
* <li> 0 if both values are identical. </li>
* <li> -ve integer if first value is smaller than the second. </li>
* <li> +ve integer if first value is larger than the second. </li>
* </ul>
*
* @param left First byte[] to compare.
* @param leftFromIndex inclusive index of first byte to compare in left
* @param leftToIndex exclusive index of last byte to compare in left
* @param right Second byte[] to compare.
* @param rightFromIndex inclusive index of first byte to compare in right
* @param rightToIndex exclusive index of last byte to compare in right
* @return Result of comparison as stated above.
*/
public static int compare(byte[] left, int leftFromIndex, int leftToIndex, byte[] right, int rightFromIndex,
int rightToIndex) {
if (COMPARE_UNSIGNED != null) {
try {
return (int) COMPARE_UNSIGNED.invokeExact(left, leftFromIndex, leftToIndex, right, rightFromIndex,
rightToIndex);
} catch (ArrayIndexOutOfBoundsException outOfBounds) {
throw outOfBounds;
} catch (Throwable ignore) {
}
}
return compareFallback(left, leftFromIndex, leftToIndex, right, rightFromIndex, rightToIndex);
}
private static int compareFallback(byte[] left, int leftFromIndex, int leftToIndex, byte[] right, int rightFromIndex,
int rightToIndex) {
int len1 = leftToIndex - leftFromIndex;
int len2 = rightToIndex - rightFromIndex;
int lim = Math.min(len1, len2);
for (int k = 0; k < lim; k++) {
// Java byte is always signed, but we need to perform unsigned comparison.
int ai = Byte.toUnsignedInt(left[k + leftFromIndex]);
int bi = Byte.toUnsignedInt(right[k + rightFromIndex]);
if (ai != bi) {
return ai - bi;
}
}
return len1 - len2;
}
}