Envolviendo un ByteBuffer con un InputStream

9 minutos de lectura

Tengo un método que toma un InputStream y lee datos de él. También me gustaría usar este método con un ByteBuffer. ¿Hay alguna manera de envolver un ByteBuffer para que se pueda acceder como una secuencia?

  • ¿Es un ByteBuffer nativo o está respaldado por una matriz de bytes?

    – EboMike

    2 de diciembre de 2010 a las 6:24

  • Respaldado por una matriz de bytes en este caso

    – Erik

    2 de diciembre de 2010 a las 6:30

  • Descubrí que Jackson lo tiene: Jackson ByteBufferBackedInputStream com.fasterxml.jackson.databind.util

    – Geoffrey Hendrey

    31 de agosto de 2017 a las 4:53

avatar de usuario
Kothar

Parece que hay algunos errores con la implementación a la que se refiere Thilo, y también copiar y pegar palabra por palabra en otros sitios:

  1. ByteBufferBackedInputStream.read() devuelve una representación int extendida de signo del byte que lee, lo cual es incorrecto (el valor debe estar dentro del rango [-1..255])
  2. ByteBufferBackedInputStream.read(byte[], int, int) no devuelve -1 cuando no quedan bytes en el búfer, según la especificación API

ByteBufferBackedOutputStream parece relativamente sólido.

A continuación presento una versión ‘fijada’. Si encuentro más errores (o alguien los señala), lo actualizaré aquí.

Actualizado: remoto synchronized palabras clave de métodos de lectura/escritura

Flujo de entrada

public class ByteBufferBackedInputStream extends InputStream {

    ByteBuffer buf;

    public ByteBufferBackedInputStream(ByteBuffer buf) {
        this.buf = buf;
    }

    public int read() throws IOException {
        if (!buf.hasRemaining()) {
            return -1;
        }
        return buf.get() & 0xFF;
    }

    public int read(byte[] bytes, int off, int len)
            throws IOException {
        if (!buf.hasRemaining()) {
            return -1;
        }

        len = Math.min(len, buf.remaining());
        buf.get(bytes, off, len);
        return len;
    }
}

Salida de corriente

public class ByteBufferBackedOutputStream extends OutputStream {
    ByteBuffer buf;

    public ByteBufferBackedOutputStream(ByteBuffer buf) {
        this.buf = buf;
    }

    public void write(int b) throws IOException {
        buf.put((byte) b);
    }

    public void write(byte[] bytes, int off, int len)
            throws IOException {
        buf.put(bytes, off, len);
    }

}

  • ¿Por qué lo harías sincronizado? ¿Espera que varios subprocesos lean el mismo flujo de entrada?

    – Nim

    23 mayo 2013 a las 13:46

  • @denys, lo siento, acabo de darme cuenta de tu comentario: ¿por qué quieres el flush tener ese efecto? parece un flip sería confuso, ya que sobrescribiría los datos anteriores, que no es lo que flush() normalmente lo hace. Supongo que está tratando de usar un solo búfer envuelto en un flujo de entrada y salida como un búfer.

    – Kothar

    29 de mayo de 2013 a las 9:14

  • @jaco0646 Si bien es cierto solo tú necesitar para implementar el método abstracto único, la implementación predeterminada del otro método se implementa en términos de lectura (int) y escritura (int), por lo que contiene un bucle: for (int i = 0 ; i < len ; i++) { write(b[off + i]); } Para mayor eficiencia, podemos pasar la matriz de bytes directamente al búfer y evitar convertir a/desde valores int y hacer una llamada de función para cada byte.

    – Kothar

    5 de abril de 2017 a las 14:01


  • tal vez puedas eliminar throws IOException de la firma del método porque la implementación real nunca arroja estas excepciones.

    – Roberto

    5 de noviembre de 2017 a las 12:25

  • debería haber implementado int available() también.

    – Andreas detesta la censura

    16 de junio de 2020 a las 18:54

avatar de usuario
tilo

Nada en el JDK, pero hay muchas implementaciones, busque en Google ByteBufferInputStream. Básicamente, envuelven uno o más ByteBuffers y realizan un seguimiento de un índice que registra cuánto se ha leído ya. Alguna cosa como esto aparece mucho, pero aparentemente tiene errores, consulte la respuesta de @Mike Houston para obtener una versión mejorada).

  • Maldita sea, me sorprende que esto no sea sencillo para una persona desinformada como Meassumed.

    – Sridhar Sarnobat

    27 de marzo a las 6:45

Si está respaldado por una matriz de bytes, puede usar un ByteArrayInputStream y obtenga la matriz de bytes a través de ByteBuffer.array(). Esto generará una excepción si lo está probando en un ByteBuffer nativo.

  • Por “ByteBuffer nativo”, ¿se refiere a un objeto ByteBuffer que se creó a través de ByteBuffer.allocateDirect ()?

    – BD en Rivenhill

    9 de febrero de 2012 a las 6:50

  • Este enfoque solo funciona si está seguro de que desea leer todo el contenido de la matriz de bytes de respaldo. En los casos en los que tenga un búfer parcialmente lleno, terminará leyendo más allá del límite.

    – stevevls

    15 de noviembre de 2012 a las 20:24

  • Este enfoque es incorrecto, porque el contenido del búfer puede ser solo una parte de la matriz, y la matriz contendrá otros datos al principio y al final. Ver implementación del método get().

    – Dmitri Risenberg

    25 de febrero de 2015 a las 12:37

Use el búfer de pila (matriz de bytes) directamente si está disponible; de ​​lo contrario, use el búfer de bytes envuelto (consulte la respuesta de Mike Houston)

public static InputStream asInputStream(ByteBuffer buffer) {
    if (buffer.hasArray()) {
        // use heap buffer; no array is created; only the reference is used
        return new ByteArrayInputStream(buffer.array());
    }
    return new ByteBufferInputStream(buffer);
}

También tenga en cuenta que el búfer envuelto puede admitir de manera eficiente las operaciones de marcar/restablecer y omitir.

Esta es mi versión de InputStream & OutputStream implementación:

ByteBufferBackedInputStream:

public class ByteBufferBackedInputStream extends InputStream
{
  private ByteBuffer backendBuffer;

  public ByteBufferBackedInputStream(ByteBuffer backendBuffer) {
      Objects.requireNonNull(backendBuffer, "Given backend buffer can not be null!");
      this.backendBuffer = backendBuffer;
  }

  public void close() throws IOException {
      this.backendBuffer = null;
  }

  private void ensureStreamAvailable() throws IOException {
      if (this.backendBuffer == null) {
          throw new IOException("read on a closed InputStream!");
      }
  }

  @Override
  public int read() throws IOException {
      this.ensureStreamAvailable();
      return this.backendBuffer.hasRemaining() ? this.backendBuffer.get() & 0xFF : -1;
  }

  @Override
  public int read(@Nonnull byte[] buffer) throws IOException {
      return this.read(buffer, 0, buffer.length);
  }

  @Override
  public int read(@Nonnull byte[] buffer, int offset, int length) throws IOException {
      this.ensureStreamAvailable();
      Objects.requireNonNull(buffer, "Given buffer can not be null!");
      if (offset >= 0 && length >= 0 && length <= buffer.length - offset) {
          if (length == 0) {
              return 0;
          }
          else {
              int remainingSize = Math.min(this.backendBuffer.remaining(), length);
              if (remainingSize == 0) {
                  return -1;
              }
              else {
                  this.backendBuffer.get(buffer, offset, remainingSize);
                  return remainingSize;
              }
          }
      }
      else {
          throw new IndexOutOfBoundsException();
      }
  }

  public long skip(long n) throws IOException {
      this.ensureStreamAvailable();
      if (n <= 0L) {
          return 0L;
      }
      int length = (int) n;
      int remainingSize = Math.min(this.backendBuffer.remaining(), length);
      this.backendBuffer.position(this.backendBuffer.position() + remainingSize);
      return (long) length;
  }

  public int available() throws IOException {
      this.ensureStreamAvailable();
      return this.backendBuffer.remaining();
  }

  public synchronized void mark(int var1) {
  }

  public synchronized void reset() throws IOException {
      throw new IOException("mark/reset not supported");
  }

  public boolean markSupported() {
      return false;
  }
}

ByteBufferBackedOutputStream:

public class ByteBufferBackedOutputStream extends OutputStream
{
    private ByteBuffer backendBuffer;

    public ByteBufferBackedOutputStream(ByteBuffer backendBuffer) {
        Objects.requireNonNull(backendBuffer, "Given backend buffer can not be null!");
        this.backendBuffer = backendBuffer;
    }

    public void close() throws IOException {
        this.backendBuffer = null;
    }

    private void ensureStreamAvailable() throws IOException {
        if (this.backendBuffer == null) {
            throw new IOException("write on a closed OutputStream");
        }
    }

    @Override
    public void write(int b) throws IOException {
        this.ensureStreamAvailable();
        backendBuffer.put((byte) b);
    }

    @Override
    public void write(@Nonnull byte[] bytes) throws IOException {
        this.write(bytes, 0, bytes.length);
    }

    @Override
    public void write(@Nonnull byte[] bytes, int off, int len) throws IOException {
        this.ensureStreamAvailable();
        Objects.requireNonNull(bytes, "Given buffer can not be null!");
        if ((off < 0) || (off > bytes.length) || (len < 0) ||
            ((off + len) > bytes.length) || ((off + len) < 0))
        {
            throw new IndexOutOfBoundsException();
        }
        else if (len == 0) {
            return;
        }

        backendBuffer.put(bytes, off, len);
    }
}

avatar de usuario
HoyAdivinaQué

Basado en un derivado del código ByteArrayInputStream… Requiere que el ByteBuffer suministrado tenga la posición y el límite correctamente establecidos de antemano según corresponda.

    public class ByteBufferInputStream extends InputStream
    {
        /**
         * The input ByteBuffer that was provided.
         * The ByteBuffer should be supplied with position and limit correctly set as appropriate
         */
        protected ByteBuffer buf;

        public ByteBufferInputStream(ByteBuffer buf)
        {
            this.buf = buf;
            buf.mark(); // to prevent java.nio.InvalidMarkException on InputStream.reset() if mark had not been set
        }

        /**
         * Reads the next byte of data from this ByteBuffer. The value byte is returned as an int in the range 0-255.
         * If no byte is available because the end of the buffer has been reached, the value -1 is returned.
         * @return  the next byte of data, or -1 if the limit/end of the buffer has been reached.
         */
        public int read()
        {
            return buf.hasRemaining()
                ? (buf.get() & 0xff)
                : -1;
        }

        /**
         * Reads up to len bytes of data into an array of bytes from this ByteBuffer.
         * If the buffer has no remaining bytes, then -1 is returned to indicate end of file.
         * Otherwise, the number k of bytes read is equal to the smaller of len and buffer remaining.
         * @param   b     the buffer into which the data is read.
         * @param   off   the start offset in the destination array b
         * @param   len   the maximum number of bytes read.
         * @return  the total number of bytes read into the buffer, or -1 if there is no more data because the limit/end of
         *          the ByteBuffer has been reached.
         * @exception  NullPointerException If b is null.
         * @exception  IndexOutOfBoundsException If off is negative, len is negative, or len is greater than b.length - off
         */
        public int read(byte b[], int off, int len)
        {
            if (b == null)
            {
                throw new NullPointerException();
            }
            else if (off < 0 || len < 0 || len > b.length - off)
            {
                throw new IndexOutOfBoundsException();
            }

            if (!buf.hasRemaining())
            {
                return -1;
            }

            int remaining = buf.remaining();
            if (len > remaining)
            {
                len = remaining;
            }

            if (len <= 0)
            {
                return 0;
            }

            buf.get(b, off, len);

            return len;
        }

        /**
         * Skips n bytes of input from this ByteBuffer. Fewer bytes might be skipped if the limit is reached.
         *
         * @param   n   the number of bytes to be skipped.
         * @return  the actual number of bytes skipped.
         */
        public long skip(long n)
        {
            int skipAmount = (n < 0)
                ? 0
                : ((n > Integer.MAX_VALUE)
                ? Integer.MAX_VALUE
                : (int) n);

            if (skipAmount > buf.remaining())
            {
                skipAmount = buf.remaining();
            }

            int newPos = buf.position() + skipAmount;

            buf.position(newPos);

            return skipAmount;
        }

        /**
         * Returns remaining bytes available in this ByteBuffer
         * @return the number of remaining bytes that can be read (or skipped over) from this ByteBuffer.
         */
        public int available()
        {
            return buf.remaining();
        }

        public boolean markSupported()
        {
            return true;
        }

        /**
         * Set the current marked position in the ByteBuffer.
         * <p> Note: The readAheadLimit for this class has no meaning.
         */
        public void mark(int readAheadLimit)
        {
            buf.mark();
        }

        /**
         * Resets the ByteBuffer to the marked position.
         */
        public void reset()
        {
            buf.reset();
        }

        /**
         * Closing a ByteBuffer has no effect.
         * The methods in this class can be called after the stream has been closed without generating an IOException.
         */
        public void close() throws IOException
        {
        }
    }

¿Ha sido útil esta solución?