Reputation: 1529
I have been trying to understand how Java ByteBuffer works. My aim is to write a string to ByteBuffer and read it back. I want to understand how ByteBuffer
properties like Limit, Capacity, Remaining, Position
gets affected due to read/write operations.
Below is my test program (removed import statements for brevity).
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() + "]";
}
}
Output
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]
As you can see, there is no data after calling readBuffer()
and no change in value if various fields after write and read operations.
Update
Below is the working piece of code from Android Screen Library which I was originally trying to understand
// 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
Please help me to understand this.
Thanks
Upvotes: 2
Views: 8380
Reputation: 2427
Although this question has been answered long time ago, let me also supplement some info. here.
There are 2 problems in the writeBuffer()
and readBuffer()
method separately which leads you failed to get your expected result.
writeBuffer()
methodAs explained by Maarten Bodewes above regarding the nature of bytebuffer's array, you cannot use byteBuffer.array()
directly for reading from a Stream in your
Alternatively, if you want to keep testing the relationship between InputStream and ByteBuffer as your sample (which is also a common practice on server side application for handling incoming messages), you would require an additional byte array.
readBuffer()
methodThe original code is good of using an extra byte array for retrieving the context in bytebuffer for printing it.
However, the problem here is the improper use of flip()
and position()
method.
The flip()
method should only be called right before you want to change the state of the bytebuffer from storing context
to exporting context
. So this method here should appear right before the line of bytes.get(b);
instead. In the provided sample, it was too early to call this method before the line byte[] b = new byte[bytes.position()];
, as flip()
method would change the position flag of the bytebuffer to 0 while setting the limit flag to current position.
There are no points of setting the bytebuffer's position to 0 explicitly in the sample codes. In case you want to keep storing context to the bytebuffer again at some later time by starting from current position (i.e. without overwriting existing context in it), you should follow this workflow:
2.1 store bytebuffer's current position: i.e. int pos = bytebuffer.position();
2.2 process with the bytebuffer which may affect the position flag of it: e.g. bytebuffer.get(byte[] dst)
etc.
2.3 restore bytebuffer's position flag to original value: i.e. bytebuffer.position(pos);
Here I have slightly modified your sample code for achieving what you want to do:
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() + "]";
}
}
Upvotes: 1
Reputation: 93958
Your buffer is never filled. bytes.array()
simply retrieves the backing byte array. If you write anything to this then the ByteBuffer
fields - except the array itself of course - are unaffected. So the position stays at zero.
What you are doing in in.read(bytes.array())
is identical to byte[] tmp = bytes.array()
followed by in.read(tmp)
. Changes to the tmp
variable cannot be reflected in the bytes
instance. The backing array is changed which may mean that the contents of the ByteBuffer
is changed as well. But the offsets into the backing byte array - including the position and limit - aren't.
You should only fill the ByteBuffer
using any of the put
methods (that do not take an index) such as put(byte[])
.
I'll provide a code fragment that may get you thinking on how to handle strings, encodings and character and byte buffers:
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();
}
}
Note that buffers and streams are really two separate ways of handling sequential data. If you are trying to use both of them at the same time you may be trying to be too clever.
You could also solve this without CharBuffer
and ByteBuffer
using a byte[]
buffer and a StringReader
wrapped by a ReaderInputStream
.
That Android piece of code completely abuses the ByteBuffer
. It should just have created a byte[]
and wrapped that, setting the limit to the capacity. Whatever you do, do not use it as an example on ByteBuffer
handling. It made my eyes water in disgust. Code like that is a bug waiting to happen.
Upvotes: 4
Reputation: 2848
You are not writting anything in the writeBuffer()
method.
You may use something like bytes.put(b)
.
Upvotes: 1