繁体   English   中英

字符串迭代器到字节输入流

[英]Iterator of Strings to Inputstream of bytes

我想将字符串的迭代器转换为字节的输入流。 通常,我可以通过在StringBuilder中附加所有字符串并执行以下操作来做到这一点: InputStream is = new ByteArrayInputStream(sb.toString().getBytes());

但我想懒惰地做,因为我的迭代是由 Spark 提供的,而且长度可能非常大。 我在 Scala 中找到了这个例子:

  def rowsToInputStream(rows: Iterator[String], delimiter: String): InputStream = {
  val bytes: Iterator[Byte] = rows.map { row =>
    (row + "\n").getBytes
  }.flatten

  new InputStream {
    override def read(): Int = if (bytes.hasNext) {
      bytes.next & 0xff // bitwise AND - make the signed byte an unsigned int from 0-255
    } else {
      -1
    }
  }
}

但我找不到将其转换为 Java 的简单方法。 我使用 Spliterators.spliteratorUnknownSize 将iterator转换为Spliterators.spliteratorUnknownSize但是getBytes输出一个不容易变平的数组。 总的来说,它变得非常混乱。

在 Java 中是否有一种优雅的方法可以做到这一点?

如果你想要一个支持快速批量操作的InputStream ,你应该实现
int read(byte[] b, int off, int len)方法,不仅可以被读取InputStream的代码直接调用,而且是继承方法的后端

  • int read(byte b[])
  • long skip(long n)
  • byte[] readAllBytes() (JDK 9)
  • int readNBytes(byte[] b, int off, int len) (JDK 9)
  • long transferTo(OutputStream out) (JDK 9)
  • byte[] readNBytes(int len) (JDK 11)
  • void skipNBytes(long n) (JDK 14)

当所述方法具有有效实施时,它将更有效地工作。

public class StringIteratorInputStream extends InputStream {
    private CharsetEncoder encoder;
    private Iterator<String> strings;
    private CharBuffer current;
    private ByteBuffer pending;

    public StringIteratorInputStream(Iterator<String> it) {
        this(it, Charset.defaultCharset());
    }
    public StringIteratorInputStream(Iterator<String> it, Charset cs) {
        encoder = cs.newEncoder();
        strings = Objects.requireNonNull(it);
    }

    @Override
    public int read() throws IOException {
        for(;;) {
            if(pending != null && pending.hasRemaining())
                return pending.get() & 0xff;
            if(!ensureCurrent()) return -1;
            if(pending == null) pending = ByteBuffer.allocate(4096);
            else pending.compact();
            encoder.encode(current, pending, !strings.hasNext());
            pending.flip();
        }
    }

    private boolean ensureCurrent() {
        while(current == null || !current.hasRemaining()) {
            if(!strings.hasNext()) return false;
            current = CharBuffer.wrap(strings.next());
        }
        return true;
    }

    @Override
    public int read(byte[] b, int off, int len) {
        // Objects.checkFromIndexSize(off, len, b.length); // JDK 9
        int transferred = 0;
        if(pending != null && pending.hasRemaining()) {
            boolean serveByBuffer = pending.remaining() >= len;
            pending.get(b, off, transferred = Math.min(pending.remaining(), len));
            if(serveByBuffer) return transferred;
            len -= transferred;
            off += transferred;
        }
        ByteBuffer bb = ByteBuffer.wrap(b, off, len);
        while(bb.hasRemaining() && ensureCurrent()) {
            int r = bb.remaining();
            encoder.encode(current, bb, !strings.hasNext());
            transferred += r - bb.remaining();
        }
        return transferred == 0? -1: transferred;
    }
}

一个ByteBuffer基本上是byte buf[];的组合。 , int pos; , 和int count; 解决方案的变量。 但是,只有在调用者真正使用int read()方法读取单个字节时,才会初始化pending的缓冲区。 否则,代码会创建一个ByteBuffer来包装调用者提供的目标缓冲区,以将字符串直接编码到其中。

CharBuffer遵循相同的概念,仅适用于char序列。 在此代码中,它将始终是其中一个字符串的包装器,而不是具有自己存储空间的缓冲区。 所以在最好的情况下,这个InputStream实现会将所有迭代器提供的字符串编码到调用者提供的缓冲区中,而不需要中间存储。

这个概念已经暗示了惰性处理,因为没有中间存储,只有适合调用者提供的缓冲区,换句话说,只要调用者请求,将从迭代器中获取。

根据@Kayaman 的建议,我从ByteArrayInputStream中获取了一个页面,并手动使用Iterator<String>处理了字节数组的切换。 这比流方法的性能要高得多:

import java.io.InputStream;
import java.util.Iterator;

public class StringIteratorInputStream extends InputStream {
    protected byte buf[];
    protected int pos;
    protected int count;
    private Iterator<String> rows;

    public StringIteratorInputStream(Iterator<String> rows) {
        this.rows = rows;
        this.count = -1;
    }

    private void init(byte[] buf) {
        this.buf = buf;
        this.pos = 0;
        this.count = buf.length;
    }

    public int read() {
        if (pos < count) {
           return (buf[pos++] & 0xff);
        } else if (rows.hasNext()) {
            init(rows.next().getBytes());
            return (buf[pos++] & 0xff);
        } else {
            return -1;
        }
    }

}

我没有扩展ByteArrayInputStream因为它的read是同步的,我不需要它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM