Why programs need files
A variable lives in memory, and memory is wiped when the program ends. Suppose a weather station wants a program that totals a month of rainfall. The readings cannot be typed into the source code every time; they must come from somewhere that persists. A file is storage for data that remains after the program stops running. A program can retrieve that data while it runs.
In Java you connect a file to a program with two classes:
- the
Fileclass, which represents a file by its name (or path), and - the
Scannerclass, which reads characters from a source and breaks them into pieces you can use.
The idea is the same as a library desk. The File object is the title of the book. The Scanner is the person who reads the book aloud one word (or one line) at a time, always moving forward, never going back. When you are done you hand the book back with close().
The course covers reading from text files only. Taking input from the keyboard is outside the scope of the course and exam, so every reading example here uses a file.
The standard pattern
Every file-reading method in this unit has the same skeleton:
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class Rainfall
{
public static int totalRainfall(String fileName) throws IOException
{
File f = new File(fileName); // 1. name the file
Scanner in = new Scanner(f); // 2. connect a Scanner to it
int total = 0;
while (in.hasNext()) // 3. is there another token?
{
total += in.nextInt(); // 4. read one token as an int
}
in.close(); // 5. finished with the file
return total;
}
}
Take the parts in turn.
Imports. File and IOException are in the java.io package, and Scanner is in java.util. Neither is in java.lang, so each needs an import statement.
throws IOException. Creating a Scanner from a File can fail, for example if the file name is wrong. Java insists that you say what happens in that case. The way the course uses is to add throws IOException to the header of the method that uses the file. If the name is invalid at run time, the program terminates. Without the clause the code does not compile. A method that calls your method must also deal with it (usually by throwing it onward in its own header, as main often does).
The constructors. File(String str) opens the named file for reading. Scanner(File f) accepts a File. You can combine the two: new Scanner(new File(fileName)).
The loop. hasNext() returns true if there is another item to read in the file and false otherwise, so it is the natural loop condition. It is the file-reading version of “keep going until the data runs out”.
Closing. close() tells Java the program has finished with the file.
The Scanner methods you need
The Java Quick Reference lists the methods below. Learn what each returns and what can go wrong.
| Method | Returns | Problem if it fails |
|---|---|---|
boolean hasNext() |
true if there is another item to read |
never throws for a normal file |
int nextInt() |
next token as an int |
InputMismatchException if the next token is not an int or is out of range |
double nextDouble() |
next token as a double |
InputMismatchException |
boolean nextBoolean() |
next token as a boolean |
InputMismatchException |
String next() |
next token as a String |
NoSuchElementException if nothing is left |
String nextLine() |
the rest of the current line as a String; may be "" |
NoSuchElementException if no line is left |
void close() |
nothing | none |
A token is a run of non-whitespace characters. Spaces, tabs and line breaks all separate tokens, which is why the line structure of the file does not matter to next() or nextInt().
Figure 1 shows how the Scanner sees a file.
The Scanner has a hidden cursor. Each next call returns the item after the cursor and moves the cursor past it. There is no rewind. If you need the data twice, store it (in an array or ArrayList) or open a new Scanner.
Reading whole lines and cutting them up
Many files hold records, one per line, with fields separated by a character such as a comma:
Amira,72
Bilal,88
Chen,65
Dalia,88
nextLine() returns a whole line as one String. String[] split(String del) from the Quick Reference then cuts that String around each match of del, returning the pieces in order. Here "Bilal,88".split(",") gives {"Bilal", "88"}. Use Integer.parseInt (the next topic covers it) to turn "88" into an int. A comma works as a delimiter; patterns that use the special properties of regular expressions are outside the scope of the exam.
One warning: the exam does not require code that mixes nextLine with other Scanner methods on the same source, because the methods treat whitespace differently. Keep to one style per loop: tokens with next/nextInt, or lines with nextLine.
Worked example 1: totalling rainfall
The file rain.txt contains these three lines.
12 0 7
3 25
0 8 1
Task. Trace totalRainfall("rain.txt") and state the result.
Step 1. The File is created, the Scanner is connected, and total is 0. The cursor is before 12.
Step 2. Trace the loop. After each pass the cursor has moved one token to the right.
| Pass | hasNext() |
token read by nextInt() |
total after |
|---|---|---|---|
| 1 | true | 12 | 12 |
| 2 | true | 0 | 12 |
| 3 | true | 7 | 19 |
| 4 | true | 3 | 22 |
| 5 | true | 25 | 47 |
| 6 | true | 0 | 47 |
| 7 | true | 8 | 55 |
| 8 | true | 1 | 56 |
| 9 | false | (loop ends) | 56 |
Step 3. in.close() runs, then 56 is returned. The check: 12 + 0 + 7 + 3 + 25 + 0 + 8 + 1 = 56.
Compiled and run against exactly this file, the method printed 56.
Why the line breaks did not matter. nextInt() skips whitespace, including line breaks, to find the next token. The loop made eight passes because there are eight tokens, not three lines.
Worked example 2: the top scorer
Using the four-line scores.csv file above, write topScorer(fileName), which returns the name of the person with the highest score. If two people tie, return the one who appears first.
Plan. Read line by line. Split each line at the comma. Parse the score. Keep the best score and its owner so far, as in any “find the maximum” algorithm.
public static String topScorer(String fileName) throws IOException
{
Scanner in = new Scanner(new File(fileName));
String best = "";
int bestScore = -1;
while (in.hasNext())
{
String line = in.nextLine();
String[] parts = line.split(",");
int score = Integer.parseInt(parts[1]);
if (score > bestScore)
{
bestScore = score;
best = parts[0];
}
}
in.close();
return best;
}
Trace.
| Line read | parts[0] |
parts[1] |
score > bestScore? |
best |
bestScore |
|---|---|---|---|---|---|
| (start) | “” | -1 | |||
Amira,72 |
Amira | “72” | 72 > -1, true | Amira | 72 |
Bilal,88 |
Bilal | “88” | 88 > 72, true | Bilal | 88 |
Chen,65 |
Chen | “65” | 65 > 88, false | Bilal | 88 |
Dalia,88 |
Dalia | “88” | 88 > 88, false | Bilal | 88 |
The method returns "Bilal"; running it gave exactly that. The strict > is what keeps the first of the tied people. Writing >= would return "Dalia".
Why start at -1? Scores in this file cannot be negative, so any real score beats -1. Starting the maximum at a value that no data can be lower than is a safe habit.
What can go wrong
| Situation | What Java does |
|---|---|
| File name wrong at run time | program terminates (FileNotFoundException, a kind of IOException) |
Header lacks throws IOException |
does not compile |
nextInt() meets the token two |
InputMismatchException |
Any next... call after the data has run out |
NoSuchElementException |
parts[1] on a line with no comma |
ArrayIndexOutOfBoundsException |
Forgetting close() |
usually still runs, but the file is not released as it should be |
The first, third and fourth rows were confirmed by running code: a missing file gave java.io.FileNotFoundException, the token two gave java.util.InputMismatchException and a fourth nextInt() on a file holding three numbers gave java.util.NoSuchElementException.
Common misconceptions
- “The Scanner reads the file all at once.” It reads forward a little at a time from a cursor. Once past an item it cannot return to it.
- “
nextInt()reads a whole line.” It reads one token. UsenextLine()for a whole line. - “
hasNext()moves the cursor.” It only looks. Calling it twice in a row gives the same answer. - “The loop runs once per line.” A
nextornextIntloop runs once per token. - “If the file is empty, the loop crashes.” An empty file makes
hasNext()false at once, so the loop body never runs andtotalstays at its starting value. - “
throws IOExceptionis optional.” Without it (or another way to handle the possibility) the code does not compile. - “
splitchanges the String.” Strings are immutable;splitreturns a new array and leaves the original line unchanged. - “Each
nextInt()in the same loop body reads the same value.” Each call consumes a new token. Store the value in a variable if you need it twice.
Where this leads
Reading data from a file is how the data sets of the unit arrive: values read in a loop are usually stored in an array (Topic 4.3) or an ArrayList (Topic 4.8) and then processed with the standard algorithms of Topics 4.5 and 4.10. Practise with the Topic 4.6 practice questions, recap with the revision notes, and tick off the checklist. Next comes the Topic 4.7 study guide on wrapper classes, which explains Integer.parseInt and Double.parseDouble, the methods used above to turn text into numbers.
Quick questions
Does the exam ask me to read from the keyboard?
No. Accepting input from the keyboard is outside the scope of the course and exam. Reading data from a text file with Scanner(File) is the in-scope way to bring outside data into a program.
Do I have to write a try-catch block for the file?
The course description gives one way to handle the possibility that a file cannot be opened: add throws IOException to the header of the method that uses the file. If the file name is invalid, the program ends. You will not be asked to write a try-catch to answer a typical question on this topic.
What is the difference between next() and nextLine()?
next() returns the next token, which is a run of characters without whitespace. nextLine() returns everything from the current position to the end of the line, and it can return the empty string if the cursor is already at the end of a line.
Why call close()?
Closing a Scanner tells the system that the program has finished with the file so that resources are released. The course description states that a file should be closed when the program is finished using it.