Byte Streams
When data is just bytes
An image, an archive, a PDF, or a network packet cannot be correctly described as a sequence of characters.
At the usual low level, Java works with such data through byte streams:
InputStream reads bytes, and OutputStream writes them.
A concrete source or destination is defined by a subclass. For example,
FileInputStream reads bytes from a file,
while FileOutputStream writes them to a file.
Code higher in the chain can still work with the general stream type and remain independent of the source.
try (InputStream in = new FileInputStream("input.bin")) {
int first = in.read();
System.out.println(first);
}
The read() method returns an int, not a byte:
values 0..255 represent the byte that was read,
and -1 means end of stream.
This lets one return value distinguish every possible byte value from the special EOF marker.
Reading in blocks
Reading a large file one byte at a time is inconvenient and usually inefficient. That is why data is often read directly into a byte array.
byte[] buffer = new byte[4096];
try (InputStream in = new FileInputStream("input.bin")) {
int read;
while ((read = in.read(buffer)) != -1) {
System.out.println("Read: " + read);
}
}
The read variable tells you how many array elements were actually filled on that iteration.
The last block can easily be shorter than the buffer size, so the entire array must not automatically be treated as new data.
Writing bytes
OutputStream is symmetrical: it accepts individual bytes or byte arrays.
If data must be appended to the end of an existing file, that behavior is selected when the concrete file stream is created.
byte[] data = {10, 20, 30};
try (OutputStream out = new FileOutputStream("output.bin")) {
out.write(data);
}