Concepts / Reading and writing byte streams

Reading and writing byte streams

A copy program must use the byte streams InputStream and OutputStream because it cannot assume the file holds human-readable data.

  • Programming

Why Copying a File Is a Repeated Process

Copying a file sounds like a single action, but inside a program it becomes a small repeated process. The program takes a piece of data from one place, puts it in another place, and keeps going until nothing is left to copy. Before reading the explanation in this article, try to picture what the copy loop does on each pass. Building that picture first makes the rest of the material much easier to follow.

The rest of this article follows the loop step by step. You will trace what happens to a single piece of data, see how the program decides when to stop, and learn why the whole copy has to be protected against failure.

Tracing One Pass Through the Copy Loop

The heart of the copy is a while loop. On each pass, the program calls source.read() to get one byte, and then calls copy.write(data) to store that byte in the output. Here source refers to the InputStream connected to the source file, and copy refers to the OutputStream connected to the destination file. Each pass through the loop therefore performs one read and, unless the read signalled the end of the data, one write.

Source fileinput bytesInputStreamsource.read()dataone byte valueOutputStreamcopy.write(data)Destination filecopied bytesread fromassigned tohanded tostores into
What happens to each byte as it travels from the source file, through the program's variable, to the destination file, and then how the cycle repeats?

The diagram shows the path for a single byte. The variable data is the link between the two streams. It is assigned the value returned by source.read(), and that same value is then passed to copy.write(data). The program does not hold the whole file in memory. Nothing is stored beyond this one value, so the file is copied as a stream of individual bytes. Once the byte has been written, the loop starts over and the next byte follows the same path.

java
Notes on the code
  • source.read() reads one byte from the InputStream and gives it to the variable data
  • if (data < 0) checks for the -1 signal that marks the end of the input
  • copy.write(data) writes the byte to the OutputStream

This skeleton uses only the calls described in the source. The source describes the loop in these terms: a while loop that calls source.read() and copy.write(data), with a check for -1 that ends the loop through break. The loop is written with while(true), which never ends on its own, so the break is the only exit.

Why Byte Streams Instead of Text

A copy program has to work for any file, not only for files that contain text a person could read. Images, compiled programs, and plain text notes all have to be copied correctly. The program therefore cannot assume that the contents are human-readable characters, and it cannot rely on tools that interpret data as text. Instead, it works with the raw data, using the byte streams InputStream and OutputStream.

This choice makes the copy program indifferent to content. The loop never looks at what the bytes represent. An image, a compiled program, and a plain text note all pass through the same loop unchanged. The only thing the loop needs to know is when the input has run out.

Unit: byteone raw valueUnit: charactera readable symbolContent: any fileimages, programs, textContent: readabletextassumed human-readableInterpretation: noneloop never reads meaningInterpretation: textrulestools decode the data
How does the same file look when read as raw bytes compared with being interpreted as human-readable characters, and why does that difference matter for a copy program?

Reading the -1 End-of-Data Signal

A loop needs a way to stop, and in this copy the stopping signal comes from the input itself. The function source.read() returns the value -1 once every byte in the input file has been read. Each time the loop reads, the program checks the result with if (data < 0). When that check succeeds, the program leaves the loop with break. A returned value of -1 is therefore never copied; it only tells the program that the input is finished.

call source.read()gives datadata < 0 ?is the result -1?break: exit loopcopy is finishedcopy.write(data)store the bytecheck resultyes, -1no, a byterepeat
On each pass, what does the program check after read() returns, and which path does it take when the result is -1 versus a byte value?
Worked example

Copying a three-byte input

Suppose the source file holds exactly three bytes. Trace each call to read() and each check, and say what the destination file contains at the end.

  1. 1
    Pass 1

    source.read() returns the first byte. The check data < 0 is false, so the program calls copy.write(data) and writes that byte.

  2. 2
    Pass 2

    source.read() returns the second byte. The check is false again, so that byte is written.

  3. 3
    Pass 3

    source.read() returns the third byte. The check is false, and the third byte is written.

  4. 4
    Pass 4

    source.read() finds no bytes left and returns -1. The check data < 0 is true, so break runs and the loop ends without writing anything.

Result

The destination file contains the same three bytes as the source, in the same order. The fourth read did not add anything to the output, because the -1 was used only as the stopping signal.

Notice that the loop runs as long as each read returns a real byte. It writes that byte and then starts again. The first read that returns -1 sends control out of the loop, and at that point the copy is finished.

Wrapping the Loop in try..catch

The loop shown so far is only the heart of the program. Reading from an input and writing to an output are both I/O operations, and either one may fail while the copy is in progress. Input and output operations can throw exceptions, so the source requires that this work be placed inside a try..catch statement. The loop is the code that performs the reads and writes, so it is the part that must be protected.

Because a failure can happen partway through the copy, the program must be ready for the case where some bytes have already been read or written when the problem occurs. The try block holds the loop, and the catch block is where the program responds to the failure. Placing the loop inside try..catch means the failure is handled in one defined place instead of stopping the program without warning.

java
Notes on the code
  • The try block contains the whole copy loop, because every read and write is an I/O operation
  • The catch block is a placeholder; the source does not specify what it should do

Mistakes Beginners Make With the Copy Loop

The copy loop is short, so a few details carry most of the weight. The mistakes below are the ones most likely to break the copy or hide what it is doing.

  • Common mistake

    Writing the -1 value to the output

    Why it fails

    The value -1 is a signal that the input has ended, not a byte of the file. The check if (data < 0) must run before copy.write(data), so the signal is never written.

    Do this instead

    Check for the -1 result immediately after read(), and call break before any write happens.

  • Common mistake

    Assuming the file holds readable text and using a text tool to copy it

    Why it fails

    A copy program must handle any file, including images and compiled programs that are not meant to be read as characters. Tools that interpret data as text can change or fail on such content.

    Do this instead

    Use the byte streams InputStream and OutputStream so the loop moves raw data without interpreting it.

  • Common mistake

    Removing the check and expecting the loop to stop on its own

    Why it fails

    The loop is written with while(true), which never ends by itself. Without the check for -1 and the break, the loop has no exit.

    Do this instead

    Keep the if (data < 0) check and the break inside the loop.

  • Common mistake

    Leaving the loop outside the try..catch statement

    Why it fails

    Reads and writes are I/O operations and can fail partway through the copy. Code outside protection has no defined response to that failure.

    Do this instead

    Place the whole copy loop inside the try block, with the failure handled in the catch block.

Predicting the Result for an Empty File

What do you think happens?

What does the copy loop write to the destination when the source file is empty and contains no bytes?

  • Nothing is written, because the first read returns -1

  • One byte of zero is written before the loop checks for the end

  • The loop runs forever because there is no data

Reveal answer
Answer:

Nothing is written, because the first read returns -1

The source states that read() returns -1 once every byte in the input has been read. An empty file has no bytes, so the very first call to source.read() returns -1. The check data < 0 is true, break runs, and copy.write(data) is never called. The destination is left empty.

Practice: Tracing a Five-Byte Copy

EASY

Suppose the source file contains five bytes. Trace the copy loop pass by pass. For each pass, state the value returned by source.read(), whether the check data < 0 is true or false, and whether copy.write(data) runs. Then state how many writes happen in total and which pass sends control out of the loop.

Hints
  • Count how many passes return a real byte before the first -1 appears.
  • Only passes where the check is false call copy.write(data).
  • The read on the pass after the last byte returns -1.

Key Takeaways

  • A copy program uses the byte streams InputStream and OutputStream because it cannot assume the file holds human-readable text, so it moves raw data that works for any file.
  • Each pass of the copy loop calls source.read() to get one byte into the variable data and then calls copy.write(data) to store it, so the file is copied one byte at a time.
  • The read call returns -1 when all bytes have been read; the check if (data < 0) followed by break uses that signal to end the loop, and -1 is never written to the output.
  • Because reads and writes are I/O operations that can fail partway through a copy, the copy loop must be placed inside a try..catch statement.