Explain flip(), clear(), rewind(), and compact() in terms of how each moves the Buffer's position, limit, and mark.
answer
- flip: limit=position, position=0 (fill->drain)
- clear: position=0, limit=capacity (does NOT erase)
- rewind: position=0, limit unchanged (re-read)
- compact: slide unread to front, position=#unread, limit=capacity
- canonical loop: read; flip; write; compact
basics
~20 sflip() gets a buffer ready to read what you just wrote: it sets limit to the current position, then position to 0. clear() gets it ready to write again: position 0, limit at capacity (data is left untouched). rewind() sets position to 0 to re-read. compact() keeps the unread bytes and lets you keep writing.
solid answer
~40 sThese methods just reposition the markers. flip(): limit = position; position = 0; mark discarded — switches from fill mode to drain mode so you read exactly what was written. clear(): position = 0; limit = capacity; mark discarded — switches back to fill mode; it does NOT erase data, just makes the whole buffer writable again. rewind(): position = 0; limit unchanged; mark discarded — re-read the same region from the start. compact(): copies the unread bytes (position..limit) to the front, sets position to the number of bytes copied and limit = capacity — useful when you have partially drained a buffer and want to top it up with more data without losing the leftover. The classic read loop is: while(channel.read(buf) >= 0){ buf.flip(); consume(buf); buf.compact(); }.
go deeper
Knows flip() is needed before reading what was written and clear() before refilling.
Can state the exact marker assignments each method makes and explain flip vs clear vs rewind vs compact.
Writes the correct read/copy loop, chooses compact() over clear() for partial drains, and explains that no method erases data.
Discusses the API's design (cursor-shifting vs copying), performance of compact()'s memmove, and how the fill/drain duality enables reuse and zero-copy patterns.
## The two phases of a buffer A NIO buffer cycles between two roles, and these four methods are the gear-shifts between them: - **Fill (write into the buffer):** you put data in. `position` tracks how much you have put; `limit` is at `capacity` so you can use it all. - **Drain (read out of the buffer):** you take data out. `position` walks through the data; `limit` marks where the data ends. Remember the invariant: `0 <= mark <= position <= limit <= capacity`. `position` is the *next* slot; `limit` is the *wall*. ## flip() — fill → drain ``` limit = position; // "the data I wrote ends right here" position = 0; // "start reading from the front" mark discarded ``` After you fill a buffer, `position` sits just past the last byte written. `flip()` plants `limit` there (so a reader stops at the real end, not at capacity) and rewinds `position` to 0. The accessible region `[position, limit)` is now exactly the data you wrote. Forgetting `flip()` before reading is the single most common NIO bug: you end up reading from where you stopped writing, often getting zero bytes (`position == limit`). ## clear() — drain → fill (start over) ``` position = 0; limit = capacity; mark discarded ``` `clear()` makes the *whole* buffer writable again. Crucially it **does not zero the contents** — the old bytes are still physically present; you just overwrite them as you fill. "Clear" means "clear the markers," not "clear the memory." ## rewind() — re-read ``` position = 0; limit unchanged mark discarded ``` `rewind()` is like `flip()` but leaves `limit` alone. Use it when you have already drained a buffer once and want to read the *same* region again (e.g. write the same payload to two channels). ## compact() — drain partially, then keep filling ``` copy bytes [position, limit) down to index 0 position = number of bytes copied (limit - old position) limit = capacity mark discarded ``` `compact()` handles the common case where you read *some* of a buffer but not all of it, and now want to pour more data in without losing the unread leftover. It slides the remaining unread bytes to the front, then leaves `position` right after them so the next fill appends. This is why the canonical copy loop uses `compact()`, not `clear()` — `clear()` would discard any not-yet-consumed bytes. ## The canonical loop ```java ByteBuffer buf = ByteBuffer.allocate(1024); while (in.read(buf) != -1) { // fill buf.flip(); // switch to drain out.write(buf); // drain (maybe partial) buf.compact(); // keep leftovers, switch back to fill } buf.flip(); // drain the final remainder while (buf.hasRemaining()) out.write(buf); ``` ## Summary table | method | position | limit | erases data? | use when | |---|---|---|---|---| | flip() | 0 | old position | no | finished filling, about to read | | clear() | 0 | capacity | no | done with data, start filling fresh | | rewind() | 0 | unchanged | no | re-read the same region | | compact()| #unread bytes | capacity | no | partially drained, want to keep filling | None of them touch the actual element bytes; they only move cursors (compact additionally *copies* the unread region). Each discards the mark.
- Why does the standard copy loop use compact() instead of clear()?A drain (channel.write) may be partial, leaving unread bytes in the buffer. clear() would discard them; compact() preserves the unread bytes by sliding them to the front, then lets you append more data.
- Does clear() erase the buffer's contents?No. It only resets position to 0 and limit to capacity. The underlying bytes remain; they are simply overwritten as you refill.
saying these in an interview costs you the question
- Believing clear() or flip() zeroes the buffer's bytes
- Reading before calling flip() (you read garbage / zero bytes)
- Using clear() instead of compact() in a copy loop and losing unread bytes
- Thinking rewind() resets limit too (it does not)
- Forgetting all four discard the mark