InputStream and the shape of a read
◈ 4 cardsread() returns 0 to 255 or -1, a bulk read returns a count rather than a promise, and available() is a floor — the three facts that make a network read loop correct.
Six methods, mirrored
java.io.InputStream is the mirror image of OutputStream: read(), read(byte[]), read(byte[], int offset, int length), skip(long), available(), and close(). As on the output side, the stream a Socket gives you is just an InputStream — the concrete class is hidden, and the same six methods read a file, a byte array, or a TCP connection.
read() reads a single byte and returns it as an int from 0 to 255. End of stream is signalled by returning -1. And it blocks: if no byte has arrived yet, the calling thread stops there until one does. That single fact is why module 3 exists — a server that reads on its only thread can serve exactly one client.
-1 is a signal, not a byte
Because 255 and -1 both have to fit in the return value, the result cannot be a byte. Write byte b = (byte) in.read(); and you have destroyed the signal: (byte) -1 and (byte) 255 are the same eight bits, so your loop can never tell a legitimate 0xFF payload byte from the end of the stream. Always read into an int, test it against -1, and only then narrow it.
A bulk read returns a count, not a promise
read(byte[] b) attempts to fill the array and returns the number of bytes it actually read. On a file that is almost always the whole array. On a network stream it very often is not: you may ask for 1,024 bytes when only 512 have crossed the ocean so far, and the rest are still in flight. This is a short read, it is normal, and code that ignores it processes garbage from the untouched tail of the array. The bulk reads return -1 at end of stream, and -1 is never stored in the array — the array only ever holds real data.
available() is a floor, not a length
available() returns the minimum number of bytes you can read without blocking. It is a hint about this instant, not the length of the resource: it is 0 at end of stream, and it is also 0 when the connection is perfectly healthy but nothing has arrived yet. Sizing a buffer from available() and reading once is the classic way to read the first fragment of a response and declare it complete.
Worked example — read exactly n bytes, or say why you could not
Binary protocols routinely say "the next four bytes are a length, then that many bytes are the body". You cannot express that with one read call. You need a loop that keeps asking until the count adds up:
static byte[] readFully(InputStream in, int n) throws IOException {
byte[] buf = new byte[n];
int off = 0;
while (off < n) {
int count = in.read(buf, off, n - off);
if (count == -1) throw new EOFException("wanted " + n + ", got " + off);
off += count;
}
return buf;
}
off is both the write position in the array and the running total, so n - off is always exactly how much is still missing. The -1 test is what separates "the peer is slow" from "the peer is gone": without it, a truncated stream turns the loop infinite. This method — or InputStream.readNBytes(n), which does the same thing since Java 9 — is the shape of every correct binary read in the course.