web-dev-qa-db-fra.com

Envelopper un ByteBuffer avec un InputStream

J'ai une méthode qui prend un InputStream et en lit les données. Je voudrais également utiliser cette méthode avec un ByteBuffer. Existe-t-il un moyen d'envelopper un ByteBuffer pour qu'il soit accessible en tant que flux?

39
Erik

Rien dans le JDK, mais il existe de nombreuses implémentations, google pour ByteBufferInputStream. Fondamentalement, ils encapsulent un ou plusieurs ByteBuffers et gardent en mémoire un index qui enregistre la quantité déjà lue. Quelque chose comme ça revient beaucoup, mais est apparemment buggé, voir @ la réponse de Mike Houston pour une version améliorée ).

13
Thilo

Il semble y avoir quelques bugs avec l'implémentation référencée par Thilo, et également copiés et collés textuellement sur d'autres sites:

  1. ByteBufferBackedInputStream.read() renvoie une représentation de signe entier étendu de l'octet qu'il lit, ce qui est faux (la valeur doit être dans la plage [-1..255])
  2. ByteBufferBackedInputStream.read(byte[], int, int) ne renvoie pas -1 lorsqu'il n'y a plus d'octets dans le tampon, conformément aux spécifications de l'API

ByteBufferBackedOutputStream semble relativement solide.

Je présente une version "fixe" ci-dessous. Si je trouve plus de bugs (ou si quelqu'un les signale) je le mettrai à jour ici.

Mis à jour: supprimé synchronized mots-clés des méthodes de lecture/écriture

Flux d'entrée

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;
    }
}

OutputStream

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);
    }

}
67
Mike Houston

S'il est soutenu par un tableau d'octets, vous pouvez utiliser un ByteArrayInputStream et obtenir le tableau d'octets via ByteBuffer.array(). Cela lèvera une exception si vous l'essayez sur un ByteBuffer natif.

7
EboMike

Voici ma version de l'implémentation de InputStream & OutputStream:

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);
    }
}
2
bob

Utilisez le tampon de tas (tableau d'octets) directement s'il est disponible, sinon utilisez un tampon d'octets enveloppé (voir la réponse 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);
}

Notez également que le tampon encapsulé peut prendre en charge efficacement les opérations de marquage/réinitialisation et de saut.

1
rmuller