了解 java 字节缓冲区

Understanding java ByteBuffer

我一直在努力理解 Java ByteBuffer 是如何工作的。我的目标是将 string 写入 ByteBuffer 并读回。我想了解 ByteBuffer 属性如 Limit, Capacity, Remaining, Position 是如何因 read/write 操作而受到影响的。

下面是我的测试程序(为简洁起见删除了导入语句)。

public class TestBuffer {

private ByteBuffer bytes;
private String testStr = "Whosebug is a great place to discuss tech stuff!";

public TestBuffer() {
    bytes = ByteBuffer.allocate(1000);
    System.out.println("init: " + printBuffer());
}

public static void main(String a[]) {
    TestBuffer buf = new TestBuffer();
    try {
        buf.writeBuffer();
    } catch (IOException e) {
        e.printStackTrace();
    }
    buf.readBuffer();
}

// write testStr to buffer
private void writeBuffer() throws IOException {
    byte[] b = testStr.getBytes();
    BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(b));
    in.read(bytes.array());
    in.close();
    System.out.println("write: " + printBuffer());
}

// read buffer data back to byte array and print
private void readBuffer() {
    bytes.flip();
    byte[] b = new byte[bytes.position()];
    bytes.position(0);
    bytes.get(b);
    System.out.println("data read: " + new String(b));
    System.out.println("read: " + printBuffer());
}

public String printBuffer() {
    return "ByteBuffer [limit=" + bytes.limit() + ", capacity=" + bytes.capacity() + ", position="
            + bytes.position() + ", remaining=" + bytes.remaining() + "]";
}

}

输出

init: ByteBuffer [limit=1000, capacity=1000, position=0, remaining=1000]
write: ByteBuffer [limit=1000, capacity=1000, position=0, remaining=1000]
data read: 
read: ByteBuffer [limit=0, capacity=1000, position=0, remaining=0]

可以看到,调用readBuffer()后没有数据,各个字段经过读写操作后,值也没有变化。

更新

下面是 Android Screen Library 中我最初试图理解的工作代码

// retrieve the screenshot
            // (this method - via ByteBuffer - seems to be the fastest)
            ByteBuffer bytes = ByteBuffer.allocate (ss.width * ss.height * ss.bpp / 8);
            is = new BufferedInputStream(is);   // buffering is very important apparently
            is.read(bytes.array());             // reading all at once for speed
            bytes.position(0);                  // reset position to the beginning of ByteBuffer

请帮助我理解这一点。

谢谢


您没有在 writeBuffer() 方法中写入任何内容。

您可以使用 bytes.put(b).

您的缓冲区永远不会被填满。 bytes.array() 只是检索支持字节数组。如果您对此写入任何内容,那么 ByteBuffer 字段 - 当然除了数组本身 - 不受影响。所以位置保持在零。

您在 in.read(bytes.array()) 中所做的与 byte[] tmp = bytes.array() 后跟 in.read(tmp) 相同。对 tmp 变量的更改无法反映在 bytes 实例中。支持数组已更改,这可能意味着 ByteBuffer 的内容也已更改。但是后备字节数组的偏移量 - 包括 positionlimit - 不是。

您应该只使用任何 put 方法(不采用索引)填充 ByteBuffer,例如 put(byte[]).


我将提供一个代码片段,可能会让您思考如何处理字符串、编码以及字符和字节缓冲区:

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;

public class TestBuffer {

    private static final String testStr = "Whosebug is a great place to discuss tech stuff!";
    private static final boolean END_OF_INPUT = true;

    private ByteBuffer bytes = ByteBuffer.allocate(1000);

    public TestBuffer() {

        System.out.println("init   : " + bytes.toString());
    }

    public static void main(String a[]) {
        TestBuffer buf = new TestBuffer();
        buf.writeBuffer();
        buf.readBuffer();
    }

    // write testStr to buffer
    private void writeBuffer() {
        CharBuffer testBuffer = CharBuffer.wrap(testStr);
        CharsetEncoder utf8Encoder = StandardCharsets.UTF_8.newEncoder();
        CoderResult result = utf8Encoder.encode(testBuffer, bytes, END_OF_INPUT);
        if (result.isError()) {
            bytes.clear();
            throw new IllegalArgumentException("That didn't go right because " + result.toString());
        }
        if (result.isOverflow()) {
            bytes.clear();
            throw new IllegalArgumentException("Well, too little buffer space.");
        }
        System.out.println("written: " + bytes.toString());
        bytes.flip();
    }

    // read buffer data back to byte array and print
    private void readBuffer() {
        byte[] b = new byte[bytes.remaining()];
        bytes.get(b);
        System.out.println("data   : " + new String(b, StandardCharsets.UTF_8));
        System.out.println("read   : " + bytes.toString());
        bytes.clear();
    }
}

请注意,缓冲区和流实际上是处理顺序数据的两种不同方式。如果您尝试同时使用它们,那么您可能想聪明。

你也可以在没有 CharBufferByteBuffer 的情况下使用 byte[] 缓冲区和 StringReader wrapped by a ReaderInputStream.

来解决这个问题

Android 段代码完全滥用了 ByteBuffer。它应该只是创建了一个 byte[] 并包装了它,将 limit 设置为 capacity。无论您做什么,都不要将其用作 ByteBuffer 处理 的示例。这让我厌恶地流泪了。像这样的代码是一个等待发生的错误。

虽然这个问题早就有人回答了,但还是补充一下吧。这里。

writeBuffer()readBuffer()方法分别有2个问题,导致你没有得到预期的结果。

1) writeBuffer() 方法

正如上面 Maarten Bodewes 所解释的关于字节缓冲区数组的性质,您不能直接使用 byteBuffer.array()

中的 Stream 中读取

或者,如果你想继续测试 InputStreamByteBuffer 之间的关系作为你的样本(这也是一个常见的做法用于处理传入消息的服务器端应用程序),您将需要一个额外的字节数组。

2) readBuffer() 方法

原始代码很好地使用了一个额外的字节数组来检索字节缓冲区中的上下文以进行打印。

但是这里的问题是flip()position()方法使用不当

  1. flip() 方法只能在您想要将字节缓冲区的 statestoring context 更改为 exporting context。所以这里的这个方法应该出现在 bytes.get(b); 行之前。在提供的示例中,在 byte[] b = new byte[bytes.position()]; 行之前调用此方法还为时过早,因为 flip() 方法会将字节缓冲区的 position 标志更改为 0 而将 limit 标志设置为当前位置。

  2. 示例代码中没有明确将字节缓冲区的位置设置为 0 的要点。如果您想在以后的某个时间从当前位置开始再次将上下文存储到字节缓冲区(即不覆盖其中的现有上下文),您应该遵循以下工作流程:

    2.1 存储字节缓冲区的当前位置:int pos = bytebuffer.position();
    2.2 处理字节缓冲区可能会影响它的位置标志:e.g. bytebuffer.get(byte[] dst)
    2.3 将bytebuffer的position flag恢复为原来的值:bytebuffer.position(pos);


我在这里稍微修改了您的示例代码以实现您想要做的事情:

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

public class TestBuffer {

    private ByteBuffer bytes;
    private String testStr = "Whosebug is a great place to discuss tech stuff!";

    public TestBuffer() {
        bytes = ByteBuffer.allocate(1000);
        System.out.println("init: " + printBuffer());
    }

    public static void main(String a[]) {
        TestBuffer buf = new TestBuffer();
        try {
            buf.writeBuffer();
        } catch (IOException e) {
            e.printStackTrace();
        }
        buf.readBuffer();
    }

    // write testStr to buffer
    private void writeBuffer() throws IOException {
        byte[] b = testStr.getBytes();
        BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(b));
        // in.read(bytes.array());
        byte[] dst = new byte[b.length];
        in.read(dst);
        bytes.put(dst);
        in.close();
        System.out.println("write: " + printBuffer());
    }

    // read buffer data back to byte array and print
    private void readBuffer() {
        //bytes.flip();
        byte[] b = new byte[bytes.position()];
        //bytes.position(0);
        int pos = bytes.position();
        bytes.flip();   // bytes.rewind(); could achieve the same result here, use which one depends on whether:
                        // (1) reading to this bytebuffer is finished and fine to overwrite the current context in this bytebuffer afterwards: can use flip(); or 
                        // (2) just want to tentatively traverse this bytebuffer from the begining to current position, 
                        //      and keep writing to this bytebuffer again later from current position.      
        bytes.get(b);
        bytes.position(pos);
        System.out.println("data read: " + new String(b));
        System.out.println("read: " + printBuffer());
    }

    public String printBuffer() {
        return "ByteBuffer [limit=" + bytes.limit() + ", capacity=" + bytes.capacity() + ", position="
                + bytes.position() + ", remaining=" + bytes.remaining() + "]";
    }

}