blob: d270d63d635c890dcc5d67351bd77e5828bc8da5 [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.hadoop.hbase.util;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.apache.hadoop.hbase.io.ByteBufferWriter;
import org.apache.hadoop.hbase.io.util.StreamUtils;
import org.apache.hadoop.hbase.nio.ByteBuff;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.WritableUtils;
import org.apache.yetus.audience.InterfaceAudience;
import sun.nio.ch.DirectBuffer;
/**
* Utility functions for working with byte buffers, such as reading/writing
* variable-length long numbers.
*/
@SuppressWarnings("restriction")
@InterfaceAudience.Private
public final class ByteBufferUtils {
// "Compressed integer" serialization helper constants.
public final static int VALUE_MASK = 0x7f;
public final static int NEXT_BIT_SHIFT = 7;
public final static int NEXT_BIT_MASK = 1 << 7;
final static boolean UNSAFE_AVAIL = UnsafeAvailChecker.isAvailable();
public final static boolean UNSAFE_UNALIGNED = UnsafeAvailChecker.unaligned();
private ByteBufferUtils() {
}
static abstract class Comparer {
abstract int compareTo(byte [] buf1, int o1, int l1, ByteBuffer buf2, int o2, int l2);
abstract int compareTo(ByteBuffer buf1, int o1, int l1, ByteBuffer buf2, int o2, int l2);
}
static abstract class Converter {
abstract short toShort(ByteBuffer buffer, int offset);
abstract int toInt(ByteBuffer buffer);
abstract int toInt(ByteBuffer buffer, int offset);
abstract long toLong(ByteBuffer buffer, int offset);
abstract void putInt(ByteBuffer buffer, int val);
abstract int putInt(ByteBuffer buffer, int index, int val);
abstract void putShort(ByteBuffer buffer, short val);
abstract int putShort(ByteBuffer buffer, int index, short val);
abstract void putLong(ByteBuffer buffer, long val);
abstract int putLong(ByteBuffer buffer, int index, long val);
}
static class ComparerHolder {
static final String UNSAFE_COMPARER_NAME = ComparerHolder.class.getName() + "$UnsafeComparer";
static final Comparer BEST_COMPARER = getBestComparer();
static Comparer getBestComparer() {
try {
Class<?> theClass = Class.forName(UNSAFE_COMPARER_NAME);
@SuppressWarnings("unchecked")
Comparer comparer = (Comparer) theClass.getConstructor().newInstance();
return comparer;
} catch (Throwable t) { // ensure we really catch *everything*
return PureJavaComparer.INSTANCE;
}
}
static final class PureJavaComparer extends Comparer {
static final PureJavaComparer INSTANCE = new PureJavaComparer();
private PureJavaComparer() {}
@Override
public int compareTo(byte [] buf1, int o1, int l1, ByteBuffer buf2, int o2, int l2) {
int end1 = o1 + l1;
int end2 = o2 + l2;
for (int i = o1, j = o2; i < end1 && j < end2; i++, j++) {
int a = buf1[i] & 0xFF;
int b = buf2.get(j) & 0xFF;
if (a != b) {
return a - b;
}
}
return l1 - l2;
}
@Override
public int compareTo(ByteBuffer buf1, int o1, int l1, ByteBuffer buf2, int o2, int l2) {
int end1 = o1 + l1;
int end2 = o2 + l2;
for (int i = o1, j = o2; i < end1 && j < end2; i++, j++) {
int a = buf1.get(i) & 0xFF;
int b = buf2.get(j) & 0xFF;
if (a != b) {
return a - b;
}
}
return l1 - l2;
}
}
static final class UnsafeComparer extends Comparer {
public UnsafeComparer() {}
static {
if(!UNSAFE_UNALIGNED) {
throw new Error();
}
}
@Override
public int compareTo(byte[] buf1, int o1, int l1, ByteBuffer buf2, int o2, int l2) {
long offset2Adj;
Object refObj2 = null;
if (buf2.isDirect()) {
offset2Adj = o2 + ((DirectBuffer)buf2).address();
} else {
offset2Adj = o2 + buf2.arrayOffset() + UnsafeAccess.BYTE_ARRAY_BASE_OFFSET;
refObj2 = buf2.array();
}
return compareToUnsafe(buf1, o1 + UnsafeAccess.BYTE_ARRAY_BASE_OFFSET, l1,
refObj2, offset2Adj, l2);
}
@Override
public int compareTo(ByteBuffer buf1, int o1, int l1, ByteBuffer buf2, int o2, int l2) {
long offset1Adj, offset2Adj;
Object refObj1 = null, refObj2 = null;
if (buf1.isDirect()) {
offset1Adj = o1 + ((DirectBuffer) buf1).address();
} else {
offset1Adj = o1 + buf1.arrayOffset() + UnsafeAccess.BYTE_ARRAY_BASE_OFFSET;
refObj1 = buf1.array();
}
if (buf2.isDirect()) {
offset2Adj = o2 + ((DirectBuffer) buf2).address();
} else {
offset2Adj = o2 + buf2.arrayOffset() + UnsafeAccess.BYTE_ARRAY_BASE_OFFSET;
refObj2 = buf2.array();
}
return compareToUnsafe(refObj1, offset1Adj, l1, refObj2, offset2Adj, l2);
}
}
}
static class ConverterHolder {
static final String UNSAFE_CONVERTER_NAME =
ConverterHolder.class.getName() + "$UnsafeConverter";
static final Converter BEST_CONVERTER = getBestConverter();
static Converter getBestConverter() {
try {
Class<?> theClass = Class.forName(UNSAFE_CONVERTER_NAME);
// yes, UnsafeComparer does implement Comparer<byte[]>
@SuppressWarnings("unchecked")
Converter converter = (Converter) theClass.getConstructor().newInstance();
return converter;
} catch (Throwable t) { // ensure we really catch *everything*
return PureJavaConverter.INSTANCE;
}
}
static final class PureJavaConverter extends Converter {
static final PureJavaConverter INSTANCE = new PureJavaConverter();
private PureJavaConverter() {}
@Override
short toShort(ByteBuffer buffer, int offset) {
return buffer.getShort(offset);
}
@Override
int toInt(ByteBuffer buffer) {
return buffer.getInt();
}
@Override
int toInt(ByteBuffer buffer, int offset) {
return buffer.getInt(offset);
}
@Override
long toLong(ByteBuffer buffer, int offset) {
return buffer.getLong(offset);
}
@Override
void putInt(ByteBuffer buffer, int val) {
buffer.putInt(val);
}
@Override
int putInt(ByteBuffer buffer, int index, int val) {
buffer.putInt(index, val);
return index + Bytes.SIZEOF_INT;
}
@Override
void putShort(ByteBuffer buffer, short val) {
buffer.putShort(val);
}
@Override
int putShort(ByteBuffer buffer, int index, short val) {
buffer.putShort(index, val);
return index + Bytes.SIZEOF_SHORT;
}
@Override
void putLong(ByteBuffer buffer, long val) {
buffer.putLong(val);
}
@Override
int putLong(ByteBuffer buffer, int index, long val) {
buffer.putLong(index, val);
return index + Bytes.SIZEOF_LONG;
}
}
static final class UnsafeConverter extends Converter {
public UnsafeConverter() {}
static {
if(!UNSAFE_UNALIGNED) {
throw new Error();
}
}
@Override
short toShort(ByteBuffer buffer, int offset) {
return UnsafeAccess.toShort(buffer, offset);
}
@Override
int toInt(ByteBuffer buffer) {
int i = UnsafeAccess.toInt(buffer, buffer.position());
buffer.position(buffer.position() + Bytes.SIZEOF_INT);
return i;
}
@Override
int toInt(ByteBuffer buffer, int offset) {
return UnsafeAccess.toInt(buffer, offset);
}
@Override
long toLong(ByteBuffer buffer, int offset) {
return UnsafeAccess.toLong(buffer, offset);
}
@Override
void putInt(ByteBuffer buffer, int val) {
int newPos = UnsafeAccess.putInt(buffer, buffer.position(), val);
buffer.position(newPos);
}
@Override
int putInt(ByteBuffer buffer, int index, int val) {
return UnsafeAccess.putInt(buffer, index, val);
}
@Override
void putShort(ByteBuffer buffer, short val) {
int newPos = UnsafeAccess.putShort(buffer, buffer.position(), val);
buffer.position(newPos);
}
@Override
int putShort(ByteBuffer buffer, int index, short val) {
return UnsafeAccess.putShort(buffer, index, val);
}
@Override
void putLong(ByteBuffer buffer, long val) {
int newPos = UnsafeAccess.putLong(buffer, buffer.position(), val);
buffer.position(newPos);
}
@Override
int putLong(ByteBuffer buffer, int index, long val) {
return UnsafeAccess.putLong(buffer, index, val);
}
}
}
/**
* Similar to {@link WritableUtils#writeVLong(java.io.DataOutput, long)},
* but writes to a {@link ByteBuffer}.
*/
public static void writeVLong(ByteBuffer out, long i) {
if (i >= -112 && i <= 127) {
out.put((byte) i);
return;
}
int len = -112;
if (i < 0) {
i ^= -1L; // take one's complement
len = -120;
}
long tmp = i;
while (tmp != 0) {
tmp = tmp >> 8;
len--;
}
out.put((byte) len);
len = (len < -120) ? -(len + 120) : -(len + 112);
for (int idx = len; idx != 0; idx--) {
int shiftbits = (idx - 1) * 8;
long mask = 0xFFL << shiftbits;
out.put((byte) ((i & mask) >> shiftbits));
}
}
private interface ByteVisitor {
byte get();
}
private static long readVLong(ByteVisitor visitor) {
byte firstByte = visitor.get();
int len = WritableUtils.decodeVIntSize(firstByte);
if (len == 1) {
return firstByte;
}
long i = 0;
for (int idx = 0; idx < len - 1; idx++) {
byte b = visitor.get();
i = i << 8;
i = i | (b & 0xFF);
}
return (WritableUtils.isNegativeVInt(firstByte) ? (i ^ -1L) : i);
}
/**
* Similar to {@link WritableUtils#readVLong(DataInput)} but reads from a {@link ByteBuffer}.
*/
public static long readVLong(ByteBuffer in) {
return readVLong(in::get);
}
/**
* Similar to {@link WritableUtils#readVLong(java.io.DataInput)} but reads from a
* {@link ByteBuff}.
*/
public static long readVLong(ByteBuff in) {
return readVLong(in::get);
}
/**
* Put in buffer integer using 7 bit encoding. For each written byte:
* 7 bits are used to store value
* 1 bit is used to indicate whether there is next bit.
* @param value Int to be compressed.
* @param out Where to put compressed data
* @return Number of bytes written.
* @throws IOException on stream error
*/
public static int putCompressedInt(OutputStream out, final int value)
throws IOException {
int i = 0;
int tmpvalue = value;
do {
byte b = (byte) (tmpvalue & VALUE_MASK);
tmpvalue >>>= NEXT_BIT_SHIFT;
if (tmpvalue != 0) {
b |= (byte) NEXT_BIT_MASK;
}
out.write(b);
i++;
} while (tmpvalue != 0);
return i;
}
/**
* Put in output stream 32 bit integer (Big Endian byte order).
* @param out Where to put integer.
* @param value Value of integer.
* @throws IOException On stream error.
*/
public static void putInt(OutputStream out, final int value)
throws IOException {
// We have writeInt in ByteBufferOutputStream so that it can directly write
// int to underlying
// ByteBuffer in one step.
if (out instanceof ByteBufferWriter) {
((ByteBufferWriter) out).writeInt(value);
} else {
StreamUtils.writeInt(out, value);
}
}
public static byte toByte(ByteBuffer buffer, int offset) {
if (UNSAFE_AVAIL) {
return UnsafeAccess.toByte(buffer, offset);
} else {
return buffer.get(offset);
}
}
/**
* Copy the data to the output stream and update position in buffer.
* @param out the stream to write bytes to
* @param in the buffer to read bytes from
* @param length the number of bytes to copy
*/
public static void moveBufferToStream(OutputStream out, ByteBuffer in,
int length) throws IOException {
copyBufferToStream(out, in, in.position(), length);
skip(in, length);
}
/**
* Copy data from a buffer to an output stream. Does not update the position
* in the buffer.
* @param out the stream to write bytes to
* @param in the buffer to read bytes from
* @param offset the offset in the buffer (from the buffer's array offset)
* to start copying bytes from
* @param length the number of bytes to copy
*/
public static void copyBufferToStream(OutputStream out, ByteBuffer in,
int offset, int length) throws IOException {
if (out instanceof ByteBufferWriter) {
((ByteBufferWriter) out).write(in, offset, length);
} else if (in.hasArray()) {
out.write(in.array(), in.arrayOffset() + offset, length);
} else {
for (int i = 0; i < length; ++i) {
out.write(toByte(in, offset + i));
}
}
}
/**
* Copy data from a buffer to an output stream. Does not update the position
* in the buffer.
* @param out the output stream to write bytes to
* @param in the buffer to read bytes from
* @param offset the offset in the buffer (from the buffer's array offset)
* to start copying bytes from
* @param length the number of bytes to copy
*/
public static void copyBufferToStream(DataOutput out, ByteBuffer in, int offset, int length)
throws IOException {
if (out instanceof ByteBufferWriter) {
((ByteBufferWriter) out).write(in, offset, length);
} else if (in.hasArray()) {
out.write(in.array(), in.arrayOffset() + offset, length);
} else {
for (int i = 0; i < length; ++i) {
out.write(toByte(in, offset + i));
}
}
}
public static int putLong(OutputStream out, final long value,
final int fitInBytes) throws IOException {
long tmpValue = value;
for (int i = 0; i < fitInBytes; ++i) {
out.write((byte) (tmpValue & 0xff));
tmpValue >>>= 8;
}
return fitInBytes;
}
public static int putByte(ByteBuffer buffer, int offset, byte b) {
if (UNSAFE_AVAIL) {
return UnsafeAccess.putByte(buffer, offset, b);
} else {
buffer.put(offset, b);
return offset + 1;
}
}
/**
* Check how many bytes are required to store value.
* @param value Value which size will be tested.
* @return How many bytes are required to store value.
*/
public static int longFitsIn(final long value) {
if (value < 0) {
return 8;
}
if (value < (1L << (4 * 8))) {
// no more than 4 bytes
if (value < (1L << (2 * 8))) {
if (value < (1L << (1 * 8))) {
return 1;
}
return 2;
}
if (value < (1L << (3 * 8))) {
return 3;
}
return 4;
}
// more than 4 bytes
if (value < (1L << (6 * 8))) {
if (value < (1L << (5 * 8))) {
return 5;
}
return 6;
}
if (value < (1L << (7 * 8))) {
return 7;
}
return 8;
}
/**
* Check how many bytes is required to store value.
* @param value Value which size will be tested.
* @return How many bytes are required to store value.
*/
public static int intFitsIn(final int value) {
if (value < 0) {
return 4;
}
if (value < (1 << (2 * 8))) {
if (value < (1 << (1 * 8))) {
return 1;
}
return 2;
}
if (value <= (1 << (3 * 8))) {
return 3;
}
return 4;
}
/**
* Read integer from stream coded in 7 bits and increment position.
* @return the integer that has been read
* @throws IOException
*/
public static int readCompressedInt(InputStream input)
throws IOException {
int result = 0;
int i = 0;
byte b;
do {
b = (byte) input.read();
result += (b & VALUE_MASK) << (NEXT_BIT_SHIFT * i);
i++;
if (i > Bytes.SIZEOF_INT + 1) {
throw new IllegalStateException(
"Corrupted compressed int (too long: " + (i + 1) + " bytes)");
}
} while (0 != (b & NEXT_BIT_MASK));
return result;
}
/**
* Read integer from buffer coded in 7 bits and increment position.
* @return Read integer.
*/
public static int readCompressedInt(ByteBuffer buffer) {
byte b = buffer.get();
if ((b & NEXT_BIT_MASK) != 0) {
return (b & VALUE_MASK) + (readCompressedInt(buffer) << NEXT_BIT_SHIFT);
}
return b & VALUE_MASK;
}
/**
* Read long which was written to fitInBytes bytes and increment position.
* @param fitInBytes In how many bytes given long is stored.
* @return The value of parsed long.
* @throws IOException
*/
public static long readLong(InputStream in, final int fitInBytes)
throws IOException {
long tmpLong = 0;
for (int i = 0; i < fitInBytes; ++i) {
tmpLong |= (in.read() & 0xffL) << (8 * i);
}
return tmpLong;
}
/**
* Read long which was written to fitInBytes bytes and increment position.
* @param fitInBytes In how many bytes given long is stored.
* @return The value of parsed long.
*/
public static long readLong(ByteBuffer in, final int fitInBytes) {
long tmpLength = 0;
for (int i = 0; i < fitInBytes; ++i) {
tmpLength |= (in.get() & 0xffL) << (8L * i);
}
return tmpLength;
}
/**
* Copy the given number of bytes from the given stream and put it at the
* current position of the given buffer, updating the position in the buffer.
* @param out the buffer to write data to
* @param in the stream to read data from
* @param length the number of bytes to read/write
*/
public static void copyFromStreamToBuffer(ByteBuffer out,
DataInputStream in, int length) throws IOException {
if (out.hasArray()) {
in.readFully(out.array(), out.position() + out.arrayOffset(),
length);
skip(out, length);
} else {
for (int i = 0; i < length; ++i) {
out.put(in.readByte());
}
}
}
/**
* Copy from the InputStream to a new heap ByteBuffer until the InputStream is exhausted.
*/
public static ByteBuffer drainInputStreamToBuffer(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
IOUtils.copyBytes(is, baos, 4096, true);
ByteBuffer buffer = ByteBuffer.wrap(baos.toByteArray());
buffer.rewind();
return buffer;
}
/**
* Copy one buffer's whole data to another. Write starts at the current position of 'out' buffer.
* Note : This will advance the position marker of {@code out} and also change the position maker
* for {@code in}.
* @param in source buffer
* @param out destination buffer
*/
public static void copyFromBufferToBuffer(ByteBuffer in, ByteBuffer out) {
if (in.hasArray() && out.hasArray()) {
int length = in.remaining();
System.arraycopy(in.array(), in.arrayOffset(), out.array(), out.arrayOffset(), length);
out.position(out.position() + length);
in.position(in.limit());
} else if (UNSAFE_AVAIL) {
int length = in.remaining();
UnsafeAccess.copy(in, in.position(), out, out.position(), length);
out.position(out.position() + length);
in.position(in.limit());
} else {
out.put(in);
}
}
/**
* Copy from one buffer to another from given offset. This will be absolute positional copying and
* won't affect the position of any of the buffers.
* @param in
* @param out
* @param sourceOffset
* @param destinationOffset
* @param length
*/
public static void copyFromBufferToBuffer(ByteBuffer in, ByteBuffer out, int sourceOffset,
int destinationOffset, int length) {
if (in.hasArray() && out.hasArray()) {
System.arraycopy(in.array(), sourceOffset + in.arrayOffset(), out.array(), out.arrayOffset()
+ destinationOffset, length);
} else if (UNSAFE_AVAIL) {
UnsafeAccess.copy(in, sourceOffset, out, destinationOffset, length);
} else {
ByteBuffer outDup = out.duplicate();
outDup.position(destinationOffset);
ByteBuffer inDup = in.duplicate();
inDup.position(sourceOffset).limit(sourceOffset + length);
outDup.put(inDup);
}
// We used to return a result but disabled; return destinationOffset + length;
}
/**
* Copy from one buffer to another from given offset.
* <p>
* Note : This will advance the position marker of {@code out} but not change the position maker
* for {@code in}
* @param in source buffer
* @param out destination buffer
* @param sourceOffset offset in the source buffer
* @param length how many bytes to copy
*/
public static void copyFromBufferToBuffer(ByteBuffer in, ByteBuffer out, int sourceOffset,
int length) {
if (in.hasArray() && out.hasArray()) {
System.arraycopy(in.array(), sourceOffset + in.arrayOffset(), out.array(), out.position()
+ out.arrayOffset(), length);
skip(out, length);
} else if (UNSAFE_AVAIL) {
UnsafeAccess.copy(in, sourceOffset, out, out.position(), length);
skip(out, length);
} else {
ByteBuffer inDup = in.duplicate();
inDup.position(sourceOffset).limit(sourceOffset + length);
out.put(inDup);
}
}
/**
* Find length of common prefix in two arrays.
* @param left Array to be compared.
* @param leftOffset Offset in left array.
* @param leftLength Length of left array.
* @param right Array to be compared.
* @param rightOffset Offset in right array.
* @param rightLength Length of right array.
*/
public static int findCommonPrefix(
byte[] left, int leftOffset, int leftLength,
byte[] right, int rightOffset, int rightLength) {
int length = Math.min(leftLength, rightLength);
int result = 0;
while (result < length &&
left[leftOffset + result] == right[rightOffset + result]) {
result++;
}
return result;
}
/**
* Find length of common prefix in two arrays.
* @param left ByteBuffer to be compared.
* @param leftOffset Offset in left ByteBuffer.
* @param leftLength Length of left ByteBuffer.
* @param right ByteBuffer to be compared.
* @param rightOffset Offset in right ByteBuffer.
* @param rightLength Length of right ByteBuffer.
*/
public static int findCommonPrefix(ByteBuffer left, int leftOffset, int leftLength,
ByteBuffer right, int rightOffset, int rightLength) {
int length = Math.min(leftLength, rightLength);
int result = 0;
while (result < length && ByteBufferUtils.toByte(left, leftOffset + result) == ByteBufferUtils
.toByte(right, rightOffset + result)) {
result++;
}
return result;
}
/**
* Check whether two parts in the same buffer are equal.
* @param buffer In which buffer there are parts
* @param offsetLeft Beginning of first part.
* @param lengthLeft Length of the first part.
* @param offsetRight Beginning of the second part.
* @param lengthRight Length of the second part.
* @return True if equal
*/
public static boolean arePartsEqual(ByteBuffer buffer,
int offsetLeft, int lengthLeft,
int offsetRight, int lengthRight) {
if (lengthLeft != lengthRight) {
return false;
}
if (buffer.hasArray()) {
return 0 == Bytes.compareTo(
buffer.array(), buffer.arrayOffset() + offsetLeft, lengthLeft,
buffer.array(), buffer.arrayOffset() + offsetRight, lengthRight);
}
for (int i = 0; i < lengthRight; ++i) {
if (buffer.get(offsetLeft + i) != buffer.get(offsetRight + i)) {
return false;
}
}
return true;
}
/**
* Increment position in buffer.
* @param buffer In this buffer.
* @param length By that many bytes.
*/
public static void skip(ByteBuffer buffer, int length) {
buffer.position(buffer.position() + length);
}
public static void extendLimit(ByteBuffer buffer, int numBytes) {
buffer.limit(buffer.limit() + numBytes);
}
/**
* Copy the bytes from position to limit into a new byte[] of the exact length and sets the
* position and limit back to their original values (though not thread safe).
* @param buffer copy from here
* @param startPosition put buffer.get(startPosition) into byte[0]
* @return a new byte[] containing the bytes in the specified range
*/
public static byte[] toBytes(ByteBuffer buffer, int startPosition) {
int originalPosition = buffer.position();
byte[] output = new byte[buffer.limit() - startPosition];
buffer.position(startPosition);
buffer.get(output);
buffer.position(originalPosition);
return output;
}
/**
* Copy the given number of bytes from specified offset into a new byte[]
* @param buffer
* @param offset
* @param length
* @return a new byte[] containing the bytes in the specified range
*/
public static byte[] toBytes(ByteBuffer buffer, int offset, int length) {
byte[] output = new byte[length];
for (int i = 0; i < length; i++) {
output[i] = buffer.get(offset + i);
}
return output;
}
public static boolean equals(ByteBuffer buf1, int o1, int l1, ByteBuffer buf2, int o2, int l2) {
if ((l1 == 0) || (l2 == 0)) {
// both 0 length, return true, or else false
return l1 == l2;
}
// Since we're often comparing adjacent sorted data,
// it's usual to have equal arrays except for the very last byte
// so check that first
if (toByte(buf1, o1 + l1 - 1) != toByte(buf2, o2 + l2 - 1)) return false;
return compareTo(buf1, o1, l1, buf2, o2, l2) == 0;
}
/**
* @param buf
* ByteBuffer to hash
* @param offset
* offset to start from
* @param length
* length to hash
*/
public static int hashCode(ByteBuffer buf, int offset, int length) {
int hash = 1;
for (int i = offset; i < offset + length; i++) {
hash = (31 * hash) + (int) toByte(buf, i);
}
return hash;
}
public static int compareTo(ByteBuffer buf1, int o1, int l1, ByteBuffer buf2, int o2, int l2) {
return ComparerHolder.BEST_COMPARER.compareTo(buf1, o1, l1, buf2, o2, l2);
}
public static boolean equals(ByteBuffer buf1, int o1, int l1, byte[] buf2, int o2, int l2) {
if ((l1 == 0) || (l2 == 0)) {
// both 0 length, return true, or else false
return l1 == l2;
}
// Since we're often comparing adjacent sorted data,
// it's usual to have equal arrays except for the very last byte
// so check that first
if (toByte(buf1, o1 + l1 - 1) != buf2[o2 + l2 - 1]) return false;
return compareTo(buf1, o1, l1, buf2, o2, l2) == 0;
}
// The below two methods show up in lots of places. Versions of them in commons util and in
// Cassandra. In guava too? They are copied from ByteBufferUtils. They are here as static
// privates. Seems to make code smaller and make Hotspot happier (comes of compares and study
// of compiled code via jitwatch).
public static int compareTo(byte [] buf1, int o1, int l1, ByteBuffer buf2, int o2, int l2) {
return ComparerHolder.BEST_COMPARER.compareTo(buf1, o1, l1, buf2, o2, l2);
}
public static int compareTo(ByteBuffer buf1, int o1, int l1, byte[] buf2, int o2, int l2) {
return compareTo(buf2, o2, l2, buf1, o1, l1)*-1;
}
static int compareToUnsafe(Object obj1, long o1, int l1, Object obj2, long o2, int l2) {
final int stride = 8;
final int minLength = Math.min(l1, l2);
int strideLimit = minLength & ~(stride - 1);
int i;
/*
* Compare 8 bytes at a time. Benchmarking shows comparing 8 bytes at a time is no slower than
* comparing 4 bytes at a time even on 32-bit. On the other hand, it is substantially faster on
* 64-bit.
*/
for (i = 0; i < strideLimit; i += stride) {
long lw = UnsafeAccess.theUnsafe.getLong(obj1, o1 + (long) i);
long rw = UnsafeAccess.theUnsafe.getLong(obj2, o2 + (long) i);
if (lw != rw) {
if (!UnsafeAccess.LITTLE_ENDIAN) {
return ((lw + Long.MIN_VALUE) < (rw + Long.MIN_VALUE)) ? -1 : 1;
}
/*
* We want to compare only the first index where left[index] != right[index]. This
* corresponds to the least significant nonzero byte in lw ^ rw, since lw and rw are
* little-endian. Long.numberOfTrailingZeros(diff) tells us the least significant
* nonzero bit, and zeroing out the first three bits of L.nTZ gives us the shift to get
* that least significant nonzero byte. This comparison logic is based on UnsignedBytes
* from guava v21
*/
int n = Long.numberOfTrailingZeros(lw ^ rw) & ~0x7;
return ((int) ((lw >>> n) & 0xFF)) - ((int) ((rw >>> n) & 0xFF));
}
}
// The epilogue to cover the last (minLength % stride) elements.
for (; i < minLength; i++) {
int il = (UnsafeAccess.theUnsafe.getByte(obj1, o1 + i) & 0xFF);
int ir = (UnsafeAccess.theUnsafe.getByte(obj2, o2 + i) & 0xFF);
if (il != ir) {
return il - ir;
}
}
return l1 - l2;
}
/**
* Reads a short value at the given buffer's offset.
* @param buffer
* @param offset
* @return short value at offset
*/
public static short toShort(ByteBuffer buffer, int offset) {
return ConverterHolder.BEST_CONVERTER.toShort(buffer, offset);
}
/**
* Reads an int value at the given buffer's current position. Also advances the buffer's position
*/
public static int toInt(ByteBuffer buffer) {
return ConverterHolder.BEST_CONVERTER.toInt(buffer);
}
/**
* Reads an int value at the given buffer's offset.
* @param buffer
* @param offset
* @return int value at offset
*/
public static int toInt(ByteBuffer buffer, int offset) {
return ConverterHolder.BEST_CONVERTER.toInt(buffer, offset);
}
/**
* Converts a ByteBuffer to an int value
*
* @param buf The ByteBuffer
* @param offset Offset to int value
* @param length Number of bytes used to store the int value.
* @return the int value
* @throws IllegalArgumentException
* if there's not enough bytes left in the buffer after the given offset
*/
public static int readAsInt(ByteBuffer buf, int offset, final int length) {
if (offset + length > buf.limit()) {
throw new IllegalArgumentException("offset (" + offset + ") + length (" + length
+ ") exceed the" + " limit of the buffer: " + buf.limit());
}
int n = 0;
for(int i = offset; i < (offset + length); i++) {
n <<= 8;
n ^= toByte(buf, i) & 0xFF;
}
return n;
}
/**
* Reads a long value at the given buffer's offset.
* @param buffer
* @param offset
* @return long value at offset
*/
public static long toLong(ByteBuffer buffer, int offset) {
return ConverterHolder.BEST_CONVERTER.toLong(buffer, offset);
}
/**
* Put an int value out to the given ByteBuffer's current position in big-endian format.
* This also advances the position in buffer by int size.
* @param buffer the ByteBuffer to write to
* @param val int to write out
*/
public static void putInt(ByteBuffer buffer, int val) {
ConverterHolder.BEST_CONVERTER.putInt(buffer, val);
}
public static int putInt(ByteBuffer buffer, int index, int val) {
return ConverterHolder.BEST_CONVERTER.putInt(buffer, index, val);
}
/**
* Reads a double value at the given buffer's offset.
* @param buffer
* @param offset offset where double is
* @return double value at offset
*/
public static double toDouble(ByteBuffer buffer, int offset) {
return Double.longBitsToDouble(toLong(buffer, offset));
}
/**
* Reads a BigDecimal value at the given buffer's offset.
* @param buffer
* @param offset
* @return BigDecimal value at offset
*/
public static BigDecimal toBigDecimal(ByteBuffer buffer, int offset, int length) {
if (buffer == null || length < Bytes.SIZEOF_INT + 1 ||
(offset + length > buffer.limit())) {
return null;
}
int scale = toInt(buffer, offset);
byte[] tcBytes = new byte[length - Bytes.SIZEOF_INT];
copyFromBufferToArray(tcBytes, buffer, offset + Bytes.SIZEOF_INT, 0, length - Bytes.SIZEOF_INT);
return new BigDecimal(new BigInteger(tcBytes), scale);
}
/**
* Put a short value out to the given ByteBuffer's current position in big-endian format.
* This also advances the position in buffer by short size.
* @param buffer the ByteBuffer to write to
* @param val short to write out
*/
public static void putShort(ByteBuffer buffer, short val) {
ConverterHolder.BEST_CONVERTER.putShort(buffer, val);
}
public static int putShort(ByteBuffer buffer, int index, short val) {
return ConverterHolder.BEST_CONVERTER.putShort(buffer, index, val);
}
public static int putAsShort(ByteBuffer buf, int index, int val) {
buf.put(index + 1, (byte) val);
val >>= 8;
buf.put(index, (byte) val);
return index + Bytes.SIZEOF_SHORT;
}
/**
* Put a long value out to the given ByteBuffer's current position in big-endian format.
* This also advances the position in buffer by long size.
* @param buffer the ByteBuffer to write to
* @param val long to write out
*/
public static void putLong(ByteBuffer buffer, long val) {
ConverterHolder.BEST_CONVERTER.putLong(buffer, val);
}
public static int putLong(ByteBuffer buffer, int index, long val) {
return ConverterHolder.BEST_CONVERTER.putLong(buffer, index, val);
}
/**
* Copies the bytes from given array's offset to length part into the given buffer. Puts the bytes
* to buffer's current position. This also advances the position in the 'out' buffer by 'length'
* @param out
* @param in
* @param inOffset
* @param length
*/
public static void copyFromArrayToBuffer(ByteBuffer out, byte[] in, int inOffset, int length) {
if (out.hasArray()) {
System.arraycopy(in, inOffset, out.array(), out.arrayOffset() + out.position(), length);
// Move the position in out by length
out.position(out.position() + length);
} else if (UNSAFE_AVAIL) {
UnsafeAccess.copy(in, inOffset, out, out.position(), length);
// Move the position in out by length
out.position(out.position() + length);
} else {
out.put(in, inOffset, length);
}
}
/**
* Copies bytes from given array's offset to length part into the given buffer. Puts the bytes
* to buffer's given position. This doesn't affact the position of buffer.
* @param out
* @param in
* @param inOffset
* @param length
*/
public static void copyFromArrayToBuffer(ByteBuffer out, int outOffset, byte[] in, int inOffset,
int length) {
if (out.hasArray()) {
System.arraycopy(in, inOffset, out.array(), out.arrayOffset() + outOffset, length);
} else if (UNSAFE_AVAIL) {
UnsafeAccess.copy(in, inOffset, out, outOffset, length);
} else {
ByteBuffer outDup = out.duplicate();
outDup.position(outOffset);
outDup.put(in, inOffset, length);
}
}
/**
* Copies specified number of bytes from given offset of 'in' ByteBuffer to
* the array. This doesn't affact the position of buffer.
* @param out
* @param in
* @param sourceOffset
* @param destinationOffset
* @param length
*/
public static void copyFromBufferToArray(byte[] out, ByteBuffer in, int sourceOffset,
int destinationOffset, int length) {
if (in.hasArray()) {
System.arraycopy(in.array(), sourceOffset + in.arrayOffset(), out, destinationOffset, length);
} else if (UNSAFE_AVAIL) {
UnsafeAccess.copy(in, sourceOffset, out, destinationOffset, length);
} else {
ByteBuffer inDup = in.duplicate();
inDup.position(sourceOffset);
inDup.get(out, destinationOffset, length);
}
}
/**
* Similar to {@link Arrays#copyOfRange(byte[], int, int)}
* @param original the buffer from which the copy has to happen
* @param from the starting index
* @param to the ending index
* @return a byte[] created out of the copy
*/
public static byte[] copyOfRange(ByteBuffer original, int from, int to) {
int newLength = to - from;
if (newLength < 0) throw new IllegalArgumentException(from + " > " + to);
byte[] copy = new byte[newLength];
ByteBufferUtils.copyFromBufferToArray(copy, original, from, 0, newLength);
return copy;
}
// For testing purpose
public static String toStringBinary(final ByteBuffer b, int off, int len) {
StringBuilder result = new StringBuilder();
// Just in case we are passed a 'len' that is > buffer length...
if (off >= b.capacity())
return result.toString();
if (off + len > b.capacity())
len = b.capacity() - off;
for (int i = off; i < off + len; ++i) {
int ch = b.get(i) & 0xFF;
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
|| " `~!@#$%^&*()-_=+[]{}|;:'\",.<>/?".indexOf(ch) >= 0) {
result.append((char) ch);
} else {
result.append(String.format("\\x%02X", ch));
}
}
return result.toString();
}
public static String toStringBinary(final ByteBuffer b) {
return toStringBinary(b, 0, b.capacity());
}
}