Java tutorial
/* * Copyright 2009 Red Hat, Inc. * * Red Hat 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. * * Copyright 2011 Dain Sundstrom <dain@iq80.com> * Copyright 2011 FuseSource Corp. http://fusesource.com */ package org.apache.kudu.util; import static java.nio.ByteOrder.LITTLE_ENDIAN; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; import java.nio.charset.Charset; import java.util.Arrays; import com.google.common.base.Preconditions; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.google.common.primitives.Shorts; import org.apache.kudu.annotations.InterfaceAudience; /** * Little Endian slice of a byte array. */ @InterfaceAudience.Private public final class Slice implements Comparable<Slice> { private final byte[] data; private final int offset; private final int length; private int hash; public Slice(int length) { data = new byte[length]; this.offset = 0; this.length = length; } public Slice(byte[] data) { Preconditions.checkNotNull(data, "array is null"); this.data = data; this.offset = 0; this.length = data.length; } public Slice(byte[] data, int offset, int length) { Preconditions.checkNotNull(data, "array is null"); this.data = data; this.offset = offset; this.length = length; } /** * Length of this slice. */ public int length() { return length; } /** * Gets the array underlying this slice. */ public byte[] getRawArray() { return data; } /** * Gets the offset of this slice in the underlying array. */ public int getRawOffset() { return offset; } /** * Gets a byte at the specified absolute {@code index} in this buffer. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * {@code index + 1} is greater than {@code this.capacity} */ public byte getByte(int index) { Preconditions.checkPositionIndexes(index, index + 1, this.length); index += offset; return data[index]; } /** * Gets an unsigned byte at the specified absolute {@code index} in this * buffer. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * {@code index + 1} is greater than {@code this.capacity} */ public short getUnsignedByte(int index) { return (short) (getByte(index) & 0xFF); } /** * Gets a 16-bit short integer at the specified absolute {@code index} in * this slice. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * {@code index + 2} is greater than {@code this.capacity} */ public short getShort(int index) { Preconditions.checkPositionIndexes(index, index + Shorts.BYTES, this.length); index += offset; return (short) (data[index] & 0xFF | data[index + 1] << 8); } /** * Gets a 32-bit integer at the specified absolute {@code index} in * this buffer. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * {@code index + 4} is greater than {@code this.capacity} */ public int getInt(int index) { Preconditions.checkPositionIndexes(index, index + Ints.BYTES, this.length); index += offset; return (data[index] & 0xff) | (data[index + 1] & 0xff) << 8 | (data[index + 2] & 0xff) << 16 | (data[index + 3] & 0xff) << 24; } /** * Gets a 64-bit long integer at the specified absolute {@code index} in * this buffer. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * {@code index + 8} is greater than {@code this.capacity} */ public long getLong(int index) { Preconditions.checkPositionIndexes(index, index + Longs.BYTES, this.length); index += offset; return ((long) data[index] & 0xff) | ((long) data[index + 1] & 0xff) << 8 | ((long) data[index + 2] & 0xff) << 16 | ((long) data[index + 3] & 0xff) << 24 | ((long) data[index + 4] & 0xff) << 32 | ((long) data[index + 5] & 0xff) << 40 | ((long) data[index + 6] & 0xff) << 48 | ((long) data[index + 7] & 0xff) << 56; } /** * Transfers this buffer's data to the specified destination starting at * the specified absolute {@code index}. * * @param dstIndex the first index of the destination * @param length the number of bytes to transfer * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0}, * if the specified {@code dstIndex} is less than {@code 0}, * if {@code index + length} is greater than * {@code this.capacity}, or * if {@code dstIndex + length} is greater than * {@code dst.capacity} */ public void getBytes(int index, Slice dst, int dstIndex, int length) { getBytes(index, dst.data, dstIndex, length); } /** * Transfers this buffer's data to the specified destination starting at * the specified absolute {@code index}. * * @param destinationIndex the first index of the destination * @param length the number of bytes to transfer * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0}, * if the specified {@code dstIndex} is less than {@code 0}, * if {@code index + length} is greater than * {@code this.capacity}, or * if {@code dstIndex + length} is greater than * {@code dst.length} */ public void getBytes(int index, byte[] destination, int destinationIndex, int length) { Preconditions.checkPositionIndexes(index, index + length, this.length); Preconditions.checkPositionIndexes(destinationIndex, destinationIndex + length, destination.length); index += offset; System.arraycopy(data, index, destination, destinationIndex, length); } public byte[] getBytes() { return getBytes(0, length); } public byte[] getBytes(int index, int length) { index += offset; if (index == 0) { return Arrays.copyOf(data, length); } else { byte[] value = new byte[length]; System.arraycopy(data, index, value, 0, length); return value; } } /** * Transfers this buffer's data to the specified destination starting at * the specified absolute {@code index} until the destination's position * reaches its limit. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * if {@code index + dst.remaining()} is greater than * {@code this.capacity} */ public void getBytes(int index, ByteBuffer destination) { Preconditions.checkPositionIndex(index, this.length); index += offset; destination.put(data, index, Math.min(length, destination.remaining())); } /** * Transfers this buffer's data to the specified stream starting at the * specified absolute {@code index}. * * @param length the number of bytes to transfer * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * if {@code index + length} is greater than * {@code this.capacity} * @throws java.io.IOException if the specified stream threw an exception during I/O */ public void getBytes(int index, OutputStream out, int length) throws IOException { Preconditions.checkPositionIndexes(index, index + length, this.length); index += offset; out.write(data, index, length); } /** * Transfers this buffer's data to the specified channel starting at the * specified absolute {@code index}. * * @param length the maximum number of bytes to transfer * @return the actual number of bytes written out to the specified channel * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * if {@code index + length} is greater than * {@code this.capacity} * @throws java.io.IOException if the specified channel threw an exception during I/O */ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { Preconditions.checkPositionIndexes(index, index + length, this.length); index += offset; return out.write(ByteBuffer.wrap(data, index, length)); } /** * Sets the specified 16-bit short integer at the specified absolute * {@code index} in this buffer. The 16 high-order bits of the specified * value are ignored. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * {@code index + 2} is greater than {@code this.capacity} */ public void setShort(int index, int value) { Preconditions.checkPositionIndexes(index, index + Shorts.BYTES, this.length); index += offset; data[index] = (byte) (value); data[index + 1] = (byte) (value >>> 8); } /** * Sets the specified 32-bit integer at the specified absolute * {@code index} in this buffer. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * {@code index + 4} is greater than {@code this.capacity} */ public void setInt(int index, int value) { Preconditions.checkPositionIndexes(index, index + Ints.BYTES, this.length); index += offset; data[index] = (byte) (value); data[index + 1] = (byte) (value >>> 8); data[index + 2] = (byte) (value >>> 16); data[index + 3] = (byte) (value >>> 24); } /** * Sets the specified 64-bit long integer at the specified absolute * {@code index} in this buffer. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * {@code index + 8} is greater than {@code this.capacity} */ public void setLong(int index, long value) { Preconditions.checkPositionIndexes(index, index + Longs.BYTES, this.length); index += offset; data[index] = (byte) (value); data[index + 1] = (byte) (value >>> 8); data[index + 2] = (byte) (value >>> 16); data[index + 3] = (byte) (value >>> 24); data[index + 4] = (byte) (value >>> 32); data[index + 5] = (byte) (value >>> 40); data[index + 6] = (byte) (value >>> 48); data[index + 7] = (byte) (value >>> 56); } /** * Sets the specified byte at the specified absolute {@code index} in this * buffer. The 24 high-order bits of the specified value are ignored. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * {@code index + 1} is greater than {@code this.capacity} */ public void setByte(int index, int value) { Preconditions.checkPositionIndexes(index, index + 1, this.length); index += offset; data[index] = (byte) value; } /** * Transfers the specified source buffer's data to this buffer starting at * the specified absolute {@code index}. * * @param srcIndex the first index of the source * @param length the number of bytes to transfer * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0}, * if the specified {@code srcIndex} is less than {@code 0}, * if {@code index + length} is greater than * {@code this.capacity}, or * if {@code srcIndex + length} is greater than * {@code src.capacity} */ public void setBytes(int index, Slice src, int srcIndex, int length) { setBytes(index, src.data, src.offset + srcIndex, length); } /** * Transfers the specified source array's data to this buffer starting at * the specified absolute {@code index}. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0}, * if the specified {@code srcIndex} is less than {@code 0}, * if {@code index + length} is greater than * {@code this.capacity}, or * if {@code srcIndex + length} is greater than {@code src.length} */ public void setBytes(int index, byte[] source, int sourceIndex, int length) { Preconditions.checkPositionIndexes(index, index + length, this.length); Preconditions.checkPositionIndexes(sourceIndex, sourceIndex + length, source.length); index += offset; System.arraycopy(source, sourceIndex, data, index, length); } /** * Transfers the specified source buffer's data to this buffer starting at * the specified absolute {@code index} until the source buffer's position * reaches its limit. * * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * if {@code index + src.remaining()} is greater than * {@code this.capacity} */ public void setBytes(int index, ByteBuffer source) { Preconditions.checkPositionIndexes(index, index + source.remaining(), this.length); index += offset; source.get(data, index, source.remaining()); } /** * Transfers the content of the specified source stream to this buffer * starting at the specified absolute {@code index}. * * @param length the number of bytes to transfer * @return the actual number of bytes read in from the specified channel. * {@code -1} if the specified channel is closed. * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * if {@code index + length} is greater than {@code this.capacity} * @throws java.io.IOException if the specified stream threw an exception during I/O */ public int setBytes(int index, InputStream in, int length) throws IOException { Preconditions.checkPositionIndexes(index, index + length, this.length); index += offset; int readBytes = 0; do { int localReadBytes = in.read(data, index, length); if (localReadBytes < 0) { if (readBytes == 0) { return -1; } else { break; } } readBytes += localReadBytes; index += localReadBytes; length -= localReadBytes; } while (length > 0); return readBytes; } /** * Transfers the content of the specified source channel to this buffer * starting at the specified absolute {@code index}. * * @param length the maximum number of bytes to transfer * @return the actual number of bytes read in from the specified channel. * {@code -1} if the specified channel is closed. * @throws IndexOutOfBoundsException if the specified {@code index} is less than {@code 0} or * if {@code index + length} is greater than {@code this.capacity} * @throws java.io.IOException if the specified channel threw an exception during I/O */ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { Preconditions.checkPositionIndexes(index, index + length, this.length); index += offset; ByteBuffer buf = ByteBuffer.wrap(data, index, length); int readBytes = 0; do { int localReadBytes; try { localReadBytes = in.read(buf); } catch (ClosedChannelException e) { localReadBytes = -1; } if (localReadBytes < 0) { if (readBytes == 0) { return -1; } else { break; } } else if (localReadBytes == 0) { break; } readBytes += localReadBytes; } while (readBytes < length); return readBytes; } public int setBytes(int index, FileChannel in, int position, int length) throws IOException { Preconditions.checkPositionIndexes(index, index + length, this.length); index += offset; ByteBuffer buf = ByteBuffer.wrap(data, index, length); int readBytes = 0; do { int localReadBytes; try { localReadBytes = in.read(buf, position + readBytes); } catch (ClosedChannelException e) { localReadBytes = -1; } if (localReadBytes < 0) { if (readBytes == 0) { return -1; } else { break; } } else if (localReadBytes == 0) { break; } readBytes += localReadBytes; } while (readBytes < length); return readBytes; } public Slice copySlice() { return copySlice(0, length); } /** * Returns a copy of this buffer's sub-region. Modifying the content of * the returned buffer or this buffer does not affect each other at all. */ public Slice copySlice(int index, int length) { Preconditions.checkPositionIndexes(index, index + length, this.length); index += offset; byte[] copiedArray = new byte[length]; System.arraycopy(data, index, copiedArray, 0, length); return new Slice(copiedArray); } public byte[] copyBytes() { return copyBytes(0, length); } public byte[] copyBytes(int index, int length) { Preconditions.checkPositionIndexes(index, index + length, this.length); index += offset; if (index == 0) { return Arrays.copyOf(data, length); } else { byte[] value = new byte[length]; System.arraycopy(data, index, value, 0, length); return value; } } /** * Returns a slice of this buffer's readable bytes. Modifying the content * of the returned buffer or this buffer affects each other's content * while they maintain separate indexes and marks. */ public Slice slice() { return slice(0, length); } /** * Returns a slice of this buffer's sub-region. Modifying the content of * the returned buffer or this buffer affects each other's content while * they maintain separate indexes and marks. */ public Slice slice(int index, int length) { if (index == 0 && length == this.length) { return this; } Preconditions.checkPositionIndexes(index, index + length, this.length); if (index >= 0 && length == 0) { return Slices.EMPTY_SLICE; } return new Slice(data, offset + index, length); } /** * Converts this buffer's readable bytes into a NIO buffer. The returned * buffer shares the content with this buffer. */ public ByteBuffer toByteBuffer() { return toByteBuffer(0, length); } /** * Converts this buffer's sub-region into a NIO buffer. The returned * buffer shares the content with this buffer. */ public ByteBuffer toByteBuffer(int index, int length) { Preconditions.checkPositionIndexes(index, index + length, this.length); index += offset; return ByteBuffer.wrap(data, index, length).order(LITTLE_ENDIAN); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Slice slice = (Slice) o; // do lengths match if (length != slice.length) { return false; } // if arrays have same base offset, some optimizations can be taken... if (offset == slice.offset && data == slice.data) { return true; } for (int i = 0; i < length; i++) { if (data[offset + i] != slice.data[slice.offset + i]) { return false; } } return true; } @Override public int hashCode() { if (hash != 0) { return hash; } int result = length; for (int i = offset; i < offset + length; i++) { result = 31 * result + data[i]; } if (result == 0) { result = 1; } hash = result; return hash; } /** * Compares the content of the specified buffer to the content of this * buffer. This comparison is performed byte by byte using an unsigned * comparison. */ public int compareTo(Slice that) { if (this == that) { return 0; } if (this.data == that.data && length == that.length && offset == that.offset) { return 0; } int minLength = Math.min(this.length, that.length); for (int i = 0; i < minLength; i++) { int thisByte = 0xFF & this.data[this.offset + i]; int thatByte = 0xFF & that.data[that.offset + i]; if (thisByte != thatByte) { return (thisByte) - (thatByte); } } return this.length - that.length; } /** * Decodes this buffer's readable bytes into a string with the specified * character set name. */ public String toString(Charset charset) { return toString(0, length, charset); } /** * Decodes this buffer's sub-region into a string with the specified * character set. */ public String toString(int index, int length, Charset charset) { if (length == 0) { return ""; } return Slices.decodeString(toByteBuffer(index, length), charset); } public String toString() { return getClass().getSimpleName() + '(' + "length=" + length() + ')'; } }