【问题标题】:Understanding java ByteBuffer理解 java ByteBuffer
【发布时间】:2015-07-31 14:45:20
【问题描述】:

我一直在尝试了解 Java ByteBuffer 的工作原理。我的目标是将 string 写入 ByteBuffer 并将其读回。我想了解像Limit, Capacity, Remaining, Position 这样的ByteBuffer 属性如何因读/写操作而受到影响。

以下是我的测试程序(为简洁起见,删除了 import 语句)。

public class TestBuffer {

private ByteBuffer bytes;
private String testStr = "Stackoverflow 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

请帮助我理解这一点。

谢谢


【问题讨论】:

  • 你永远不应该忽略in.read(byte[])的返回值。您可能无法检索到您要求的所有字节。给定的字节数组缓冲区可能未完全填充(因为您可能在此之前到达流的末尾,或者流可能有其他原因给出部分消息作为响应)。

标签: java bytebuffer


【解决方案1】:

您的缓冲区永远不会被填满。 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 = "Stackoverflow 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[] 缓冲区和由ReaderInputStream 包裹的StringReader 来解决此问题。


那段 Android 代码完全滥用了ByteBuffer。它应该刚刚创建了一个byte[] 并将其包装起来,将limit 设置为capacity。不管你做什么,不要拿它作为ByteBuffer处理的例子。这让我厌恶地流下了眼泪。像这样的代码是一个等待发生的错误。

【讨论】:

  • 不应该in.read(bytes.array())读入ByteBuffer吗?这里bytes指的是ByteBuffer。
  • 不,它没有。它只填充后备数组,但 ByteBuffer 实例不会(不能)得到通知。也就是说,ByteBuffer的contents发生了变化,但是包括position和limit在内的offset都没有变化。这与byte[] tmp = bytes.array() 后跟in.read(tmp) 相同。
  • 将此整合到答案中,并给出了一个完全重写没有流的writeBuffer 方法的示例。它提供了如何使用缓冲区而不需要与字符串大小相同的缓冲区的线索。它不需要流,尽管替代方法是使用InputStreamReader 而不是CharBufferByteBuffer
  • 感谢您的详尽回答。我有一段 Android 代码(但不想包含它)以这种方式工作。我已经用 sn-p 更新了问题。
  • 我在回答的末尾添加了一些滥用行为来回应:)
【解决方案2】:

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

你可以使用bytes.put(b)之类的东西。

【讨论】:

  • 我认为in.read(bytes.array()); 行应该将数据读取到ByteBuffer
【解决方案3】:

虽然这个问题已经回答很久了,但我也补充一些信息。这里。

writeBuffer()readBuffer() 方法分别存在两个问题,导致您未能获得预期的结果。

1) writeBuffer()方法

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

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

2) readBuffer() 方法

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

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

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

  2. 在示例代码中将字节缓冲区的位置显式设置为 0 没有任何意义。如果您想稍后通过从当前位置开始将上下文再次存储到字节缓冲区(即不覆盖其中的现有上下文),您应该遵循以下工作流程:

    2.1 存储 bytebuffer 的当前位置:i.e. int pos = bytebuffer.position();
    2.2 使用可能影响其位置标志的字节缓冲区的进程:e.g. bytebuffer.get(byte[] dst)
    2.3 将 bytebuffer 的位置标志恢复为原始值:i.e. 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 = "Stackoverflow 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() + "]";
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-13
    • 1970-01-01
    • 1970-01-01
    • 2011-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多