← General program design CS Skill Tree

File input and output

Java reads a file with a Scanner and writes a file with a PrintStream. You know both already. A Scanner reads the keyboard, and System.out is a PrintStream. Opening a file can throw an exception the compiler makes you handle (a checked exception).

Scanner keyboard = new Scanner(System.in);    // reads the keyboard
File file = new File("scores.txt");
Scanner in = new Scanner(file);               // reads a file

System.out.println("hello");                  // writes to the console
PrintStream out = new PrintStream("report.txt");
out.println("hello");                         // writes to a file
StepReading with ScannerWriting with PrintStream
Opennew Scanner(file), where file is a new File(name)new PrintStream(name)
Opening fails whenthe file is missing or cannot be readthe file cannot be created or opened, for example when the name is a folder
Exception on failureFileNotFoundException, checkedFileNotFoundException, checked
UsehasNextInt() and nextInt(), hasNext() and next(), hasNextLine() and nextLine()print, println
Closein.close()out.close()

You handle a checked exception by catching it in a try and catch, or by adding throws FileNotFoundException to the method header. Exception handling covers both. On the APE, catch it. The comment says to handle the exception, and a throws clause changes the method header, so the department's test no longer compiles.

These classes need import java.io.*; for File, PrintStream and FileNotFoundException, and import java.util.Scanner;.

Opening a file

When several methods share 1 open file, the stream (the Scanner or PrintStream object) lives in a static field, not in a local variable. A boolean field records whether the file is open, and a String field can record what went wrong. This class keeps 1 file for reading and 1 for writing.

public class ReportFile {
    public static Scanner reader;
    public static PrintStream writer;
    public static boolean readerOpen = false;
    public static boolean writerOpen = false;
    public static String status = "";

    public static boolean openForReading(String name) {
        File file = new File(name);
        try {
            reader = new Scanner(file);
        } catch (FileNotFoundException e) {
            status = "cannot read " + name;
            return false;
        }
        readerOpen = true;
        return true;
    }

    public static boolean openForWriting(String name) {
        try {
            writer = new PrintStream(name);
        } catch (FileNotFoundException e) {
            status = "cannot write " + name + ": " + e.getMessage();
            return false;
        }
        writerOpen = true;
        return true;
    }

    public static void closeAll() {
        if (readerOpen) {
            reader.close();
            readerOpen = false;
        }
        if (writerOpen) {
            writer.close();
            writerOpen = false;
        }
    }
}
MistakeWhat happens
Assigning the stream to a local, Scanner reader = new Scanner(file);, inside the open methodThe field stays null. The local disappears at the method's closing brace, and the next method that reads the field gets a NullPointerException.
Setting the open flag to true before the open succeedsA failed open leaves the class saying a file is open when none is.
Setting the stream field to null without calling close() firstThe file can stay open until the program ends, because no code can reach the stream to close it.
Using a stream after close()A Scanner throws IllegalStateException. A PrintStream writes nothing and throws nothing; its checkError() method returns true.

What close() does

close() ends the connection to the file and releases the file to the operating system. A PrintStream first flushes (writes out any data it still holds in memory), then closes the file. A Scanner closes the file it reads from. A closed stream cannot be opened again; open a new one instead.

After a close, the fields should match the file's state. The flag goes back to false, and some classes also set the stream field to null. The comment at the top of the problem lists every field to reset, and the test checks each one, including the message field.

Reading tokens

A Scanner splits its input into tokens (runs of characters separated by spaces, tabs or line breaks). A next method reads 1 token. A hasNext method checks the next token without reading it.

Check firstThen readWhat it reads
hasNext()next()any token, as a String
hasNextInt()nextInt()a token that can be read as an int
hasNextDouble()nextDouble()a token that can be read as a double
hasNextLine()nextLine()the rest of the current line
TokenhasNextInt()hasNextDouble()
42truetrue
3.0falsetrue
007true, and nextInt() returns 7true
hifalsefalse
a1.0falsefalse

42 passes both tests, because a whole number can also be read as a double. So when a method tests for both types, it tests hasNextInt() first. Otherwise every whole number is read as a double.

This method adds up the int tokens and skips every other token.

public static int sumInts(Scanner in) {
    int sum = 0;
    while (in.hasNext()) {
        if (in.hasNextInt()) {
            sum = sum + in.nextInt();
        } else {
            in.next();    // skip a token that is not an int
        }
    }
    return sum;
}

Without in.next() in the else branch, the loop checks the same token forever, because hasNextInt() does not move past it. A Scanner can read a String as well as a file, so sumInts(new Scanner("4 x 5")) tests the method without a file and returns 9.

nextLine() after nextInt()

nextInt() stops at the end of the number and leaves the line break after it. A nextLine() right after it returns the empty string, the rest of that line. Call nextLine() once more to read the next line.

Writing with println

println(x) writes the text of x, then 1 line separator (the characters that end a line). For an object, the text is what its toString() returns, and for null it is the word null. The line separator is \r\n on Windows and \n on Linux and macOS.

out.println(42);            // writes 42, then a line separator
out.println("done");        // writes done, then a line separator
out.print("no newline");    // writes the text only

Adding "\n" yourself, as in out.println(text + "\n"), writes 2 line endings and leaves a blank line in the file.

Reading the Java API

The Java API documentation says what each class can do. To find out whether a Scanner can check the next token's type, open the Scanner page and read these parts in order.

  1. The paragraph at the top says what the class is for. Scanner's says it breaks its input into tokens, separated by whitespace by default.
  2. The constructor summary lists what a Scanner can read from. The File constructor is marked as throwing FileNotFoundException.
  3. The method summary lists the methods by name. The hasNext methods pair with the next methods: hasNextInt with nextInt, hasNextDouble with nextDouble.
  4. The method's own entry gives the details. hasNextInt returns whether the next token can be read as an int, and it does not move past the token.

In the practice APE

Practice exams 1 and 2 each have the same 6 file methods, in different folders. read, writeToOutputFile, closeInputFile and closeOutputFile use the public fields of the InputFile and OutputFile classes.

Practice APEopenInputFile, openOutputFilethe other 4working InputFile and OutputFile for the other 4
Practice exam 1General/SortingGeneral/FilesInputFile.java and OutputFile.java in General/Files
Practice exam 2src/generalsrc/filescompiled classes inside Winter2018APEUtils.jar

Readiness check

Work each one on paper and write the answer down before you open the answers.

TraceTokens

sumInts runs on a Scanner reading this 1 line.

42 3.0 007 hi 12
  1. What does sumInts return?
  2. Which tokens go through the else branch?
  3. The line in.next(); is deleted. What happens?
Answers

1. 61. The int tokens are 42, 007 (read as 7) and 12.

2. 3.0 and hi.

3. The loop never ends. At 3.0, hasNext() is true and hasNextInt() is false, and nothing moves past the token, so the same checks repeat forever.

TraceWhat the file holds
PrintStream out = new PrintStream("t.txt");
out.print("a");
out.println("b");
out.println();
out.println(3 + 4);
out.println("x" + 3 + 4);
out.close();

Write the lines of t.txt, in order.

Answers

4 lines: ab, an empty line, 7, x34. print ends no line, so a and b share 1 line. println() with no argument writes only a line separator. 3 + 4 adds 2 numbers. "x" + 3 + 4 joins left to right, starting with a String, so both numbers become text.

TraceReportFile, call by call

Use the ReportFile class. missing.txt is not on the disk, and out.txt does not exist yet. Give the return value and the fields after each call.

ReportFile.openForReading("missing.txt");   // 1
ReportFile.openForWriting("out.txt");       // 2
ReportFile.writer.println("harbor");        // 3
ReportFile.closeAll();                      // 4
ReportFile.writer.println("again");         // 5
Answers

1. Returns false. status is cannot read missing.txt, readerOpen stays false, and reader stays null.

2. Returns true. out.txt now exists and is empty, and writerOpen is true. A PrintStream creates a file that is not there. status still holds cannot read missing.txt, because a successful open does not clear it.

3. Writes harbor and a line separator.

4. readerOpen is false, so the reader part is skipped. The writer is flushed and closed, and writerOpen becomes false. out.txt holds 1 line, harbor.

5. Nothing is written and nothing is thrown, because the stream is closed. writer still refers to the closed stream, and writer.checkError() now returns true.

TracenextInt, then nextLine

A file holds these 2 lines.

5
Ada Lovelace
int n = in.nextInt();
String name = in.nextLine();
  1. What are n and name?
  2. Fix the code so name holds Ada Lovelace.
Answers

1. n is 5, and name is the empty string, the rest of the first line after the 5.

2. Add in.nextLine(); between the 2 lines. It reads the end of the first line, so the second nextLine() reads Ada Lovelace.

Further reading