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
| Step | Reading with Scanner | Writing with PrintStream |
|---|---|---|
| Open | new Scanner(file), where file is a new File(name) | new PrintStream(name) |
| Opening fails when | the file is missing or cannot be read | the file cannot be created or opened, for example when the name is a folder |
| Exception on failure | FileNotFoundException, checked | FileNotFoundException, checked |
| Use | hasNextInt() and nextInt(), hasNext() and next(), hasNextLine() and nextLine() | print, println |
| Close | in.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;
}
}
}
- The
tryholds only the line that can throw. - The flag becomes
trueonly after the open succeeds. A failed open leaves itfalse. e.getMessage()is the text stored in the exception. For aPrintStreamon a folder namedreports, it holds the name and the system's reason. On macOS that isreports (Is a directory).new File(name)builds a path and does not touch the disk, so it succeeds for a name that does not exist.file.exists()asks the disk and returnstrueorfalse.
| Mistake | What happens |
|---|---|
Assigning the stream to a local, Scanner reader = new Scanner(file);, inside the open method | The 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 succeeds | A failed open leaves the class saying a file is open when none is. |
Setting the stream field to null without calling close() first | The 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 first | Then read | What 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 |
| Token | hasNextInt() | hasNextDouble() |
|---|---|---|
42 | true | true |
3.0 | false | true |
007 | true, and nextInt() returns 7 | true |
hi | false | false |
a1.0 | false | false |
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.
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.
- 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. - The constructor summary lists what a
Scannercan read from. TheFileconstructor is marked as throwingFileNotFoundException. - The method summary lists the methods by name. The
hasNextmethods pair with thenextmethods:hasNextIntwithnextInt,hasNextDoublewithnextDouble. - The method's own entry gives the details.
hasNextIntreturns whether the next token can be read as anint, 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 APE | openInputFile, openOutputFile | the other 4 | working InputFile and OutputFile for the other 4 |
|---|---|---|---|
| Practice exam 1 | General/Sorting | General/Files | InputFile.java and OutputFile.java in General/Files |
| Practice exam 2 | src/general | src/files | compiled classes inside Winter2018APEUtils.jar |
- The comment at the top of each file is the specification. It gives every message, every field to set and every value to return.
- Do the checks in the order the comment lists them. A
nullcheck has to come before any method call on the name, becausename.equals("")on anullname throws aNullPointerException, not the exception the comment names. - The
openInputFilemessage ends withe.getMessage(), and theopenOutputFilemessage does not. Copy each message as its own comment writes it. - In Practice exam 1, the working copies in
General/Filesare source files you can read. Write your own open methods inGeneral/Sortingbefore you look at them. - In
openInputFile, the comment shows 2 spaces afteropenInputFile:in the does-not-exist message. The department's test expects 1 space there, as infile name passed to openInputFile: I_do_not_exist.txt, does not exist.
Readiness check
Work each one on paper and write the answer down before you open the answers.
sumInts runs on a Scanner reading this 1 line.
42 3.0 007 hi 12
- What does
sumIntsreturn? - Which tokens go through the
elsebranch? - 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.
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.
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.
A file holds these 2 lines.
5
Ada Lovelace
int n = in.nextInt();
String name = in.nextLine();
- What are
nandname? - Fix the code so
nameholdsAda 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
- Scanner (Java SE 19 & JDK 19). The opening paragraph on tokens, then the
hasNextandnextmethods. - PrintStream (Java SE 19 & JDK 19). The class behind
System.out; theprintlnandcloseentries. - File (Java SE 19 & JDK 19). A
Fileis a path name;exists()checks the disk. - FileNotFoundException (Java SE 19 & JDK 19). The checked exception both constructors can throw.
- NoSuchElementException (Java SE 19 & JDK 19). What a
nextmethod throws when no token is left. - InputMismatchException (Java SE 19 & JDK 19). What
nextIntthrows when the next token is not anint. - Scanning (The Java Tutorials). Breaking input into tokens, including changing the separator.
- Formatting (The Java Tutorials). What
print,printlnandformateach write. - From CSCD 210: Reading a File with Scanner and Tokens, Lines, and the hasNext Cascade.
- Java also has a
trythat opens a stream in its parentheses and closes it at the end of the block: The try-with-resources Statement. It does not fit a stream kept in a field and closed by a separate method.