Reading and Writing Files
A whiteboard is wiped clean at the end of every class. A notebook is still there tomorrow.
Every program you've written so far has had the memory of a whiteboard. Variables, arrays, ArrayLists, objects: they all live in the computer's memory while the program runs, and the moment it ends, everything is wiped. Run it again, and it starts from nothing.
Real programs remember. A gradebook keeps last week's grades. A game keeps your high score. To do that, a program has to save its data somewhere that lasts: a file.
Working with files is called file I/O, short for input/output: reading data in from a file, and writing data out to one.
Whiteboards and notebooks
During class, a teacher writes on the whiteboard. It's fast and easy to change, but it gets wiped at the end of the day. Anything worth keeping gets copied into a notebook, which is slower to write in, but still there next week.
Memory is the whiteboard. A file is the notebook. Your program works in memory, and saves to a file when something needs to last, then loads it back the next time it runs.
This lesson sticks to text files: plain files, like .txt or .csv, that you could also open in any text editor. That makes them easy to check while you're learning.
Paths and the Files class
Modern Java handles files with two tools from the java.nio.file package:
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;- A
Pathis the location of a file, likegrades.txtordata/grades.txt. Create one withPath.of("grades.txt"). Filesis a class full of static methods (like theMathmethods from static and this) that do the actual reading and writing.
A plain file name like grades.txt is a relative path: it's found in the folder your program was started from. If you run your program from a terminal in your java-practice folder, that's where the file will appear. If you use your editor's Run button, it's usually the project's main folder.
Writing a file
The simplest way to save text is Files.writeString:
Path file = Path.of("note.txt");
Files.writeString(file, "Remember to study for the quiz!\n");That creates note.txt if it doesn't exist, and replaces everything in it if it does. Be careful with that: writing to an existing file wipes out what was there.
To save a whole list of lines at once, Files.write takes a list and puts each item on its own line:
ArrayList<String> lines = new ArrayList<>();
lines.add("Maria,91");
lines.add("Ben,84");
lines.add("Carlo,95");
Files.write(Path.of("grades.txt"), lines);To add to the end of a file instead of replacing it, pass one more argument, StandardOpenOption.APPEND (from java.nio.file too). CREATE makes the file first if it doesn't exist yet:
Files.writeString(Path.of("log.txt"), "Program started\n", StandardOpenOption.CREATE, StandardOpenOption.APPEND);Reading a file
Files.readAllLines reads a whole text file and gives you an ArrayList-style list with one String per line:
List<String> lines = Files.readAllLines(Path.of("grades.txt"));
for (String line : lines) {
System.out.println(line);
}List here is the interface you met in Interfaces. It works just like the ArrayList you already know: size(), get(i), and for-each loops. Import it from java.util.
For a short file, Files.readString(path) gives you the whole thing as one String instead.
Turning lines back into data
A file only holds text. To get your data back, you have to take each line apart, the reverse of how you saved it. The format above, with values separated by commas, is called CSV (comma-separated values), and it's the simplest common way to save rows of data:
for (String line : lines) {
String[] parts = line.split(",");
String name = parts[0];
int score = Integer.parseInt(parts[1]);
System.out.println(name + " scored " + score);
}split(",") cuts the line at each comma, giving you an array of the pieces. Then Integer.parseInt turns the score text back into an int, just like reading user input.
Files can contain mistakes, especially ones people edit by hand. A line might be blank, or have letters where a number should be. That's exactly what Exceptions are for: wrap the parsing in try/catch and skip bad lines with a warning, instead of crashing on them.
Files can fail: IOException
Lots of things can go wrong with files, and none of them are bugs in your code: the file doesn't exist, it's open in another program, the disk is full, you don't have permission. So every Files method can throw an IOException.
IOException is a checked exception, as you saw at the end of the last lesson. The compiler won't let you ignore it:
List<String> lines = Files.readAllLines(Path.of("grades.txt"));
// error: unreported exception IOException; must be caught or declared to be thrownYou have two choices.
Catch it, and respond sensibly:
try {
List<String> lines = Files.readAllLines(Path.of("grades.txt"));
System.out.println("Loaded " + lines.size() + " students.");
} catch (IOException e) {
System.out.println("Couldn't read grades.txt: " + e.getMessage());
}Or declare it with throws, and let the caller deal with it:
static List<String> loadGrades() throws IOException {
return Files.readAllLines(Path.of("grades.txt"));
}A good pattern is to declare throws IOException on small helper methods, and catch it in one place, higher up, where you can tell the user what happened.
You can also check first, with Files.exists(path). That's handy for friendly messages like "No saved data yet, starting fresh." But keep the try/catch anyway: a file can exist and still fail to open.
Reading line by line, and closing files
readAllLines loads the entire file into memory at once. That's perfect for the small files in this track. For very large files, like a log with millions of lines, you'd read one line at a time instead, with a BufferedReader:
try (BufferedReader reader = Files.newBufferedReader(Path.of("grades.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("Couldn't read the file: " + e.getMessage());
}Two new things here:
reader.readLine()gives back the next line, ornullwhen there are no lines left. Thewhilecondition reads a line and checks it in one step.- The parentheses after
tryare try-with-resources. Anything opened there is closed automatically when the block ends, whether it finished normally or threw an exception. An open file is like a borrowed library book: other programs may be waiting for it, so you must always give it back. Try-with-resources makes sure you do, without writing afinallyblock yourself.
Files.writeString, Files.write, and Files.readAllLines open and close the file for you, which is one more reason they're a good place to start.
Try it
This program saves a small gradebook to a CSV file, adds a late entry, then loads the file back, skipping a broken line, and prints a summary. It finishes by trying to read a file that doesn't exist. Predict the output.
Running Java here
Running Java right in the browser is coming to Syntaxia soon. On your own computer, save this program as Main.java and run it with java Main.java. Then look in the same folder: a new grades.csv file will be there. Open it in your editor to see exactly what was saved.
Check your prediction
Saved 4 rows to grades.csv
Maria: 91
Skipping bad line: Ben,eighty
Carlo: 95
Dina: 78
Class average: 88.0
Couldn't load missing.csv (NoSuchFileException)Files.write created the file with three lines, and the APPEND write added Dina to the end instead of replacing everything. Ben's line has eighty instead of a number, so the inner catch skips just that line and the loop carries on. The missing file throws a NoSuchFileException, which is a kind of IOException, so the second catch handles it.
Try it yourself
- Run the program twice on your computer. Does
grades.csvend up with Dina listed twice? Why or why not? (Hint: what doesFiles.writedo to an existing file?) - Open
grades.csvin your editor, fix Ben's line toBen,80, and write a second, small program that only reads the file and prints the average. - Add a blank line to the middle of
grades.csvby hand, then run your reading program. Which exception happens, and how would you skip blank lines? (Hint:line.isBlank().)
Check your understanding
Up next
That's the Errors and Files chapter done. You can find bugs, handle problems gracefully, and make your programs remember. Before the final project, one last lesson collects the habits that make Java code easy to read and hard to break: Best Practices and Common Mistakes.
