Java ByteBuffer清除数据

时间:2016-06-13 10:57:01

标签: java nio stringbuilder bytebuffer

我知道Java的 ByteBuffer.clear()并不是真的要清除ByteBuffer中的所有数据,所以当我每次使用StringBuilder.append()字符串时,最终的结果总是附加所有剩余的字符在ByteBuffer中,这是上次写入的旧数据,那么如何解决这个问题?

int byteRead = -1;
int readCount = 0;
int BUFFER_SIZE = 256;
StringBuilder sb = new StringBuilder();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
ReadableByteChannel readableByteChannel = Channels.newChannel(is);
while ((byteRead = readableByteChannel.read(buffer)) > 0 && readCount < 68) {
    sb.append(new String(buffer.array(), "UTF-8"));
    buffer.clear();
    readCount++;
}

3 个答案:

答案 0 :(得分:5)

正如其他答案已经指出的那样,您必须考虑缓冲区的位置,该位置由read方法更新。所以正确的代码如下:

while ((byteRead = readableByteChannel.read(buffer)) > 0 && readCount < 68) {
    sb.append(new String(buffer.array(),
        buffer.arrayOffset(), buffer.arrayOffset()+buffer.position(), "UTF-8"));
    buffer.clear();
    readCount++;
}

请注意,在您的特殊情况下,arrayOffset()将始终为零,但您最好以某种方式编写代码,以便在更改缓冲区分配代码时不会中断代码。

但是这段代码被打破了。当您读取多字节UTF-8序列时,可能会发生该序列的第一个字节在一个操作中被读取而剩余的字节在下一个字节中被读取。您尝试从这些不完整的序列创建String实例将产生无效字符。除此之外,您正在创建这些String个实例,只是为了将其内容复制到StringBuilder,效率非常低。

所以,要正确地做,你应该做类似的事情:

int readCount = 0;
int BUFFER_SIZE = 256;
StringBuilder sb = new StringBuilder();
CharsetDecoder dec=StandardCharsets.UTF_8.newDecoder();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
CharBuffer cBuffer= CharBuffer.allocate(BUFFER_SIZE);
ReadableByteChannel readableByteChannel = Channels.newChannel(is);
while(readableByteChannel.read(buffer) > 0 && readCount < 68) {
    buffer.flip();
    while(dec.decode(buffer, cBuffer, false).isOverflow()) {
        cBuffer.flip();
        sb.append(cBuffer);
        cBuffer.clear();
    }
    buffer.compact();
    readCount++;
}
buffer.flip();
for(boolean more=true; more; ) {
    more=dec.decode(buffer, cBuffer, true).isOverflow();
    cBuffer.flip();
    sb.append(cBuffer);
    cBuffer.clear();
}

请注意,ReadableByteChannelCharsetDecoder如何使用其位置和限制来处理缓冲区。您所要做的就是将flipcompact正确地用作shown in the documentation of compact

唯一的例外是附加到Stringbuilder,因为它不是NIO功能。在那里,我们必须使用clear(),因为我们知道Stringbuilder.append操作会消耗缓冲区中的所有字符。

请注意,此代码仍然不处理某些(不可避免的)错误条件,因为您在任意数量的read之后停止,因此您总是可以在多字节UTF中间切换 - 8序列。

但是这个非常复杂的逻辑已经由JRE实现了,如果你放弃了在一定数量的字节后切割的想法,你可以利用它:

int readCount = 0;
int BUFFER_SIZE = 256;
StringBuilder sb = new StringBuilder();
CharBuffer cBuffer= CharBuffer.allocate(BUFFER_SIZE);
ReadableByteChannel readableByteChannel = Channels.newChannel(is);
Reader reader=Channels.newReader(readableByteChannel, "UTF-8");
while(reader.read(cBuffer) > 0 && readCount < 68) {
    cBuffer.flip();
    sb.append(cBuffer);
    cBuffer.clear();
    readCount++;
}

现在这段代码会将读数限制为256 × 68 字符而非字节,但对于UTF-8编码数据,这只会在存在多字节序列时产生差异,你以前显然不在乎。

最后,由于您显然首先有一个InputStream,所以根本不需要ReadableByteChannel绕道而行:

int readCount = 0;
int BUFFER_SIZE = 256;
StringBuilder sb = new StringBuilder();
CharBuffer cBuffer = CharBuffer.allocate(BUFFER_SIZE);
Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);
while(reader.read(cBuffer) > 0 && readCount < 68) {
    cBuffer.flip();
    sb.append(cBuffer);
    cBuffer.clear();
    readCount++;
}

这可能看起来像“不是NIO代码”,但Reader仍然是读取字符数据的规范方式,即使使用NIO也是如此;没有替代品。 NIO的第一个版本中缺少method Reader.read(CharBuffer),但是使用了Java 5。

答案 1 :(得分:0)

使用position()获取当前缓冲区位置并使用Arrays.copyOf获取数组的一部分:

Arrays.copyOf(buffer.array(), 0, buffer.position());

在你的情况下会出现这种情况:

sb.append(new String(Arrays.copyOf(buffer.array(), 0, buffer.position()), "UTF-8"));

使用适当的String构造函数时甚至更短:

sb.append(new String(buffer.array(), 0, buffer.position(), "UTF-8"));

或者您正在寻找使用slice()的内容:     sb.append(new String(buffer.slice()。array(),&#34; UTF-8&#34;));

顺便说一句。而不是"UTF-8",最好使用StandardCharsets.UTF_8

答案 2 :(得分:0)

您可以使用new String(byte[] bytes, int offset, int length, String charsetName())构造函数。

new String(buffer.array(), 0, byteRead, "UTF-8"); 

这将阻止在创建新String时使用以前的数据。