Memra

InputStream and the shape of a read

◈ 4 cards

read() 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.

01234567read 1HTTPcount=4read 2HTTP/1count=2read 3HTTP/1.1count=2off = 0, then 4, then 6, then 8.
One readFully call, three trips round the loop. No single read filled the buffer and none of them was an error — the counts were 4, 2 and 2, and off walked 0, 4, 6, 8.
NORMAL ~/memra/learn/comp-348/input-streams-and-short-reads utf-8 LF