[英]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.