All Downloads are FREE. Search and download functionalities are using the official Maven repository.

alluxio.util.io.BufferUtils Maven / Gradle / Ivy

There is a newer version: 313
Show newest version
/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.util.io;

import alluxio.Constants;
import alluxio.util.CommonUtils;

import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.concurrent.ThreadSafe;

/**
 * Utilities related to buffers, not only {@link ByteBuffer}.
 */
@ThreadSafe
public final class BufferUtils {
  private static final Logger LOG = LoggerFactory.getLogger(BufferUtils.class);
  private static final Object LOCK = new Object();
  private static final int TRANSFER_BUFFER_SIZE = 4 * Constants.MB;
  private static volatile Method sCleanerCleanMethod;
  private static volatile Method sByteBufferCleanerMethod;
  private static volatile Class sUnsafeClass;

  /**
   * Converts a byte to an integer.
   *
   * @param b the byte to convert
   * @return the integer representation of the byte
   */
  public static int byteToInt(byte b) {
    return b & 0xFF;
  }

  /**
   * Forces to unmap a direct buffer if this buffer is no longer used. After calling this method,
   * this direct buffer should be discarded. This is unsafe operation and currently a work-around to
   * avoid huge memory occupation caused by memory map.
   *
   * @param buffer bytebuffer
   */
  public static void cleanDirectBuffer(ByteBuffer buffer) {
    Preconditions.checkNotNull(buffer, "buffer is null");
    Preconditions.checkArgument(buffer.isDirect(), "buffer isn't a DirectByteBuffer");
    int javaVersion = CommonUtils.getJavaVersion();
    if (javaVersion < 9) {
      cleanDirectBufferJava8(buffer);
    } else {
      cleanDirectBufferJava11(buffer);
    }
  }

  /**
   * 

* Note: This calls the cleaner method on jdk 9+. * See more discussion. * * @param buffer the byte buffer to be unmapped, this must be a direct buffer */ private static void cleanDirectBufferJava11(ByteBuffer buffer) { if (sByteBufferCleanerMethod == null || sUnsafeClass == null) { synchronized (LOCK) { try { if (sByteBufferCleanerMethod == null || sUnsafeClass == null) { try { sUnsafeClass = Class.forName("sun.misc.Unsafe"); } catch (Exception e) { // jdk.internal.misc.Unsafe doesn't yet have an invokeCleaner() method, // but that method should be added if sun.misc.Unsafe is removed. sUnsafeClass = Class.forName("jdk.internal.misc.Unsafe"); } sByteBufferCleanerMethod = sUnsafeClass.getMethod("invokeCleaner", ByteBuffer.class); sByteBufferCleanerMethod.setAccessible(true); } } catch (Exception e) { // Force to drop reference to the buffer to clean buffer = null; return; } } } try { Field theUnsafeField = sUnsafeClass.getDeclaredField("theUnsafe"); theUnsafeField.setAccessible(true); Object theUnsafe = theUnsafeField.get(null); sByteBufferCleanerMethod.invoke(theUnsafe, buffer); } catch (Exception e) { LOG.warn("Failed to unmap direct ByteBuffer: {}, error message: {}", buffer.getClass().getName(), e.toString()); } } /** *

* NOTE: DirectByteBuffers are not guaranteed to be garbage-collected immediately after their * references are released and may lead to OutOfMemoryError. This function helps by calling the * Cleaner method of a DirectByteBuffer explicitly. See more discussion. * * @param buffer the byte buffer to be unmapped, this must be a direct buffer */ private static void cleanDirectBufferJava8(ByteBuffer buffer) { if (sByteBufferCleanerMethod == null) { synchronized (LOCK) { try { if (sByteBufferCleanerMethod == null) { sByteBufferCleanerMethod = buffer.getClass().getMethod("cleaner"); sByteBufferCleanerMethod.setAccessible(true); } } catch (Exception e) { // Force to drop reference to the buffer to clean buffer = null; return; } } } try { final Object cleaner = sByteBufferCleanerMethod.invoke(buffer); if (cleaner == null) { if (buffer.capacity() > 0) { LOG.warn("Failed to get cleaner for ByteBuffer: {}", buffer.getClass().getName()); } // The cleaner could be null when the buffer is initialized as zero capacity. return; } if (sCleanerCleanMethod == null) { synchronized (LOCK) { if (sCleanerCleanMethod == null) { sCleanerCleanMethod = cleaner.getClass().getMethod("clean"); } } } sCleanerCleanMethod.invoke(cleaner); } catch (Exception e) { LOG.warn("Failed to unmap direct ByteBuffer: {}, error message: {}", buffer.getClass().getName(), e.toString()); } finally { // Force to drop reference to the buffer to clean buffer = null; } } /** * Clones a {@link ByteBuffer}. *

* The new bytebuffer will have the same content, but the type of the bytebuffer may not be the * same. * * @param buf The ByteBuffer to copy * @return The new ByteBuffer */ public static ByteBuffer cloneByteBuffer(ByteBuffer buf) { ByteBuffer ret = ByteBuffer.allocate(buf.limit() - buf.position()); if (buf.hasArray()) { ret.put(buf.array(), buf.position(), buf.limit() - buf.position()); } else { // direct buffer ret.put(buf); } ret.flip(); return ret; } /** * Clones a list of {@link ByteBuffer}s. * * @param source the list of ByteBuffers to copy * @return the new list of ByteBuffers */ public static List cloneByteBufferList(List source) { List ret = new ArrayList<>(source.size()); for (ByteBuffer b : source) { ret.add(cloneByteBuffer(b)); } return ret; } /** * Puts a byte (the first byte of an integer) into a {@link ByteBuffer}. * * @param buf ByteBuffer to use * @param b byte to put into the ByteBuffer */ public static void putIntByteBuffer(ByteBuffer buf, int b) { buf.put((byte) (b & 0xFF)); } /** * Gets an increasing sequence of bytes starting at zero. * * @param len the target length of the sequence * @return an increasing sequence of bytes */ public static byte[] getIncreasingByteArray(int len) { return getIncreasingByteArray(0, len); } /** * Gets an increasing sequence of bytes starting with the given value. * * @param start the starting value to use * @param len the target length of the sequence * @return an increasing sequence of bytes */ public static byte[] getIncreasingByteArray(int start, int len) { byte[] ret = new byte[len]; for (int k = 0; k < len; k++) { ret[k] = (byte) (k + start); } return ret; } /** * Checks if the given byte array starts with a constant sequence of bytes of the given value * and length. * * @param value the value to check for * @param len the target length of the sequence * @param arr the byte array to check * @return true if the byte array has a prefix of length {@code len} that is a constant * sequence of bytes of the given value */ public static boolean equalConstantByteArray(byte value, int len, byte[] arr) { if (arr == null || arr.length != len) { return false; } for (int k = 0; k < len; k++) { if (arr[k] != value) { return false; } } return true; } /** * Checks if the given byte array starts with an increasing sequence of bytes of the given * length, starting from 0. * * @param len the target length of the sequence * @param arr the byte array to check * @return true if the byte array has a prefix of length {@code len} that is an increasing * sequence of bytes starting at zero */ public static boolean equalIncreasingByteArray(int len, byte[] arr) { return equalIncreasingByteArray(0, len, arr); } /** * Checks if the given byte array starts with an increasing sequence of bytes of the given * length, starting from the given value. The array length must be equal to the length checked. * * @param start the starting value to use * @param len the target length of the sequence * @param arr the byte array to check * @return true if the byte array has a prefix of length {@code len} that is an increasing * sequence of bytes starting at {@code start} */ public static boolean equalIncreasingByteArray(int start, int len, byte[] arr) { if (arr == null || arr.length != len) { return false; } for (int k = 0; k < len; k++) { if (arr[k] != (byte) (start + k)) { return false; } } return true; } /** * Gets a {@link ByteBuffer} containing an increasing sequence of bytes starting at zero. * * @param len the target length of the sequence * @return ByteBuffer containing an increasing sequence of bytes */ public static ByteBuffer getIncreasingByteBuffer(int len) { return getIncreasingByteBuffer(0, len); } /** * Gets a {@link ByteBuffer} containing an increasing sequence of bytes starting at the given * value. * * @param len the target length of the sequence * @param start the starting value to use * @return ByteBuffer containing an increasing sequence of bytes */ public static ByteBuffer getIncreasingByteBuffer(int start, int len) { return ByteBuffer.wrap(getIncreasingByteArray(start, len)); } /** * Checks if the given {@link ByteBuffer} starts with an increasing sequence of bytes starting at * the given value of length equal to or greater than the given length. * * @param start the starting value to use * @param len the target length of the sequence * @param buf the ByteBuffer to check * @return true if the ByteBuffer has a prefix of length {@code len} that is an increasing * sequence of bytes starting at {@code start} */ public static boolean equalIncreasingByteBuffer(int start, int len, ByteBuffer buf) { if (buf == null) { return false; } buf.rewind(); if (buf.remaining() != len) { return false; } for (int k = 0; k < len; k++) { if (buf.get() != (byte) (start + k)) { return false; } } return true; } /** * Checks if the given byte array has a sub array equals an increasing sequence of bytes, * starting from 0. * * @param startIndex the start index in the array to check against * @param endIndex the end index in the array to check against * @param arr the byte array to check * @return true if match, false otherwise */ public static boolean matchIncreasingByteArray(int startIndex, int endIndex, byte[] arr) { return matchIncreasingByteArray(0, startIndex, endIndex, arr); } /** * Checks if the given byte array has a sub array equals an increasing sequence of bytes, * starting from the given value. * * @param startNum the starting value to use * @param startIndex the start index in the array to check against * @param endIndex the end index in the array to check against * @param arr the byte array to check * @return true if match, false otherwise */ public static boolean matchIncreasingByteArray( int startNum, int startIndex, int endIndex, byte[] arr) { if (arr == null) { return false; } for (int k = startIndex; k < endIndex; k++) { if (arr[k] != (byte) (startNum + k)) { return false; } } return true; } /** * Checks if the given byte buffer has a sub buffer equals an increasing sequence of bytes, * starting from the given value. * * @param startIndex the start index in the array to check against * @param endIndex the end index in the array to check against * @param buf the byte buffer to check * @return true if match, false otherwise */ public static boolean matchIncreasingByteBuffer(int startIndex, int endIndex, ByteBuffer buf) { return matchIncreasingByteBuffer(0, startIndex, endIndex, buf); } /** * Checks if the given byte buffer has a sub buffer equals an increasing sequence of bytes, * starting from the given value. * * @param startNum the starting value to use * @param startIndex the start index in the array to check against * @param endIndex the end index in the array to check against * @param buf the byte buffer to check * @return true if match, false otherwise */ public static boolean matchIncreasingByteBuffer( int startNum, int startIndex, int endIndex, ByteBuffer buf) { if (buf == null) { return false; } buf.rewind(); buf.position(startIndex); for (int k = 0; k < endIndex - startIndex; k++) { if (buf.get() != (byte) (startNum + k)) { return false; } } return true; } /** * Writes buffer to the given file path. * * @param path file path to write the data * @param buffer raw data */ public static void writeBufferToFile(String path, byte[] buffer) throws IOException { try (FileOutputStream os = new FileOutputStream(path)) { os.write(buffer); } } /** * An efficient copy between two channels with a fixed-size buffer. * * @param src the source channel * @param dest the destination channel */ public static void transfer(final ReadableByteChannel src, final WritableByteChannel dest) throws IOException { final ByteBuffer buffer = ByteBuffer.allocateDirect(TRANSFER_BUFFER_SIZE); try { while (src.read(buffer) != -1) { buffer.flip(); dest.write(buffer); buffer.compact(); } buffer.flip(); while (buffer.hasRemaining()) { dest.write(buffer); } } finally { cleanDirectBuffer(buffer); } } /** * Gets the unsigned byte value of an integer. Useful for comparing to the result of reading * from an input stream. * @param i the integer to convert * @return the integer value after casting as an unsigned byte */ public static int intAsUnsignedByteValue(int i) { return ((byte) i) & 0xFF; } /** * Creates a byte array from the given ByteBuffer, the position property of input * {@link ByteBuffer} remains unchanged. * * @param buf source ByteBuffer * @return a newly created byte array */ public static byte[] newByteArrayFromByteBuffer(ByteBuffer buf) { final int length = buf.remaining(); byte[] bytes = new byte[length]; // transfer bytes from this buffer into the given destination array buf.duplicate().get(bytes, 0, length); return bytes; } /** * Creates a new ByteBuffer sliced from a given ByteBuffer. The new ByteBuffer shares the * content of the existing one, but with independent position/mark/limit. After slicing, the * new ByteBuffer has position 0, and the input ByteBuffer is unmodified. * * @param buffer source ByteBuffer to slice * @param position position in the source ByteBuffer to slice * @param length length of the sliced ByteBuffer * @return the sliced ByteBuffer */ public static ByteBuffer sliceByteBuffer(ByteBuffer buffer, int position, int length) { ByteBuffer slicedBuffer = ((ByteBuffer) buffer.duplicate().position(position)).slice(); slicedBuffer.limit(length); return slicedBuffer; } /** * Convenience method for {@link #sliceByteBuffer(ByteBuffer, int, int)} where the last parameter * is the number of remaining bytes in the new buffer. * * @param buffer source {@link ByteBuffer} to slice * @param position position in the source {@link ByteBuffer} to slice * @return the sliced {@link ByteBuffer} */ public static ByteBuffer sliceByteBuffer(ByteBuffer buffer, int position) { // The following is an optimization comparing to directly calling // sliceByteBuffer(ByteBuffer, int, int) needs to compute the length of the sliced buffer and // set the limit, but those operations should have been taken care of by the slice() method. return ((ByteBuffer) buffer.duplicate().position(position)).slice(); } private BufferUtils() {} // prevent instantiation /** * An efficient copy between two channels with a fixed-size buffer. * * @param src the source channel * @param dest the destination channel */ public static void fastCopy(final ReadableByteChannel src, final WritableByteChannel dest) throws IOException { // TODO(JiamingMai): make the buffer size configurable final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024); while (src.read(buffer) != -1) { buffer.flip(); dest.write(buffer); buffer.compact(); } buffer.flip(); while (buffer.hasRemaining()) { dest.write(buffer); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy