Unit 4.6: Using Text Files
Files and Persistent Storage
A file provides storage for data that persists when the program is not running. Unlike variables and objects stored in RAM, data saved to a file remains even after the program has finished executing or the computer is shut down. The data in a file can be retrieved during program execution.
- Variables are stored in RAM (volatile) and disappear when the program stops. Files are stored on disk (persistent) and can be accessed again later.
Connecting to a File
A file can be connected to a program using the File and Scanner classes. These two classes work together to bridge your Java code with the physical data stored on your hard drive or SSD.
- The
Fileclass identifies where the data is, and theScannerclass provides the tools to read that data into your program.
The File Object Constructor
A file is opened by creating a File object, using the name of the file as the argument of the constructor.
- The constructor does not create a new file; it simply creates a Java object that "points" to a location on your computer.
File(String str): This is theFileconstructor that accepts aStringrepresenting the pathname for the file. The pathname tells Java exactly where the file is located on the computer.
Creating a Physical File
It is important to remember that the constructor new File("name.txt") only creates a memory representation of the path. To actually create a new, empty file on your disk, you must use the createNewFile() method.
Task: Creating a new physical file on the disk.
// In the main method ...
public void makeNewFile() throws IOException
{
File file = new File("newfile.txt");
// createNewFile() returns true if the file was created,
// and false if the file already exists.
if (file.createNewFile())
{
System.out.println("File created: " + file.getName());
}
else
{
System.out.println("File already exists.");
}
}
Accessing Files in Folders
In simple exercises, you might provide a basic file name like scores.txt. However, in a real-world Java application, you cannot simply retrieve a file from a random folder using a relative path. Instead, files must be organized into packages that are part of the Classpath.
What is the Classpath?
The Classpath is a collection of directories and ZIP/JAR files that the Java Virtual Machine (JVM) searches to find your compiled .class files and other required resources (like .txt files). It essentially defines the "world" of data that your program is allowed to see and interact with.
Organization into Packages
To ensure your program is portable and reliable, data files are typically placed within the project's folder structure inside specific packages. When the program executes, these packages are included in the classpath, allowing the code to locate the files consistently.
Task: Identifying file paths within a project structure.
// In the main method ...
// Accessing a file directly in the project root
File f1 = new File("scores.txt");
// Accessing a file inside a specific package structure (e.g., "assets/data")
File f2 = new File("src/assets/data/results.txt");
Handling IOException
When working with files, operations can fail (e.g., the file doesn't exist). This can cause an IOException. Java requires you to indicate what to do if the file with the provided name cannot be opened. The simplest way to handle this possibility is to add throws IOException to the header of the method where the file is being accessed.
- Java forces you to acknowledge that file operations are risky. Using
throws IOExceptionin the method header signals that this method might fail due to external file issues.
Task: Using throws IOException in a method header.
import java.io.File; // Represents the file pathname
import java.io.IOException; // Handles potential file errors
import java.util.Scanner; // Provides methods to read the file
public class DataReader
{
// The "throws" keyword goes after the parentheses in the header
public void printScores() throws IOException
{
// This will fail because such a file does not exist
File myFile = new File("no_such_file_exists.txt");
Scanner sc = new Scanner(myFile); // This line throws IOException
while (sc.hasNext())
{
System.out.println(sc.next());
}
sc.close();
}
}
If the file name provided is invalid and the exception is not handled, the program will terminate.
Packages and Imports
The File and IOException classes are part of the java.io package. An import statement must be used to make these classes available for use in your program.
import java.io.File; // Bridge to the file system
import java.io.IOException; // Error handling for file access
import java.util.Scanner; // Tools for reading data (from java.util)
- Standard file tools are kept separate from the core language classes. You must explicitly import
java.io.*or the specific classes to use them.
Using the Wildcard (*)
If you are using many classes from the same package, you can use the asterisk (*) wildcard to import every class in that package at once.
import java.io.*; // Imports File, IOException, and everything else in java.io
import java.util.*; // Imports Scanner, ArrayList, and everything else in java.util
The Scanner Class for File Input
The following Scanner methods and constructor—including what they do and when they are used—are part of the Java Quick Reference.
Scanner(File f): This is the required constructor for this unit. It takes an existingFileobject and connects the scanner to the contents of that file.
- The scanner breaks data into "tokens" (individual words or numbers). You must use the correct method (e.g.,
nextInt()for integers) to convert these tokens into Java data types.
Assume a file contains:
10 20.5 true
Java is fun
| Method | Description | Result (assuming first call) |
|---|---|---|
nextInt() |
Returns the next int read from the file. If the next item is not an integer or is out of range, it will result in an InputMismatchException. |
10 |
nextDouble() |
Returns the next double read from the file. If the next item is not a double, it results in an InputMismatchException. |
10.0 |
nextBoolean() |
Returns the next boolean read from the file. If the next item is not a boolean, it results in an InputMismatchException. |
(Exception: 10 is not boolean) |
next() |
Returns the next String (word) read from the input source. |
"10" |
nextLine() |
Returns the next line of text as a String. It can return an empty string if called immediately after another Scanner method that is reading from the file. |
"10 20.5 true" |
hasNext() |
Returns true if there is a next item to read in the file; returns false otherwise. |
true |
close() |
Closes the scanner and the underlying file access. | (None) |
Task: Reading diverse tokens using Scanner.
// In the main method ...
Scanner sc = new Scanner(new File("data.txt"));
int val = sc.nextInt();
double dVal = sc.nextDouble();
String sVal = sc.next();
nextLine() and Whitespace Handling
Mixing nextLine() with other Scanner methods (like nextInt()) requires caution because numeric methods leave the newline character (\n) in the buffer.
- Methods like
nextInt()leave the trailing newline character in the input buffer. IfnextLine()follows immediately, it will consume that newline and return an empty string.
Task: Demonstrating the nextLine() buffer pitfall.
// Logic Error Example:
int age = sc.nextInt(); // Reads 18, but leaves \n in buffer
String name = sc.nextLine(); // Reads the \n and results in an EMPTY string!
The split() Method
The split(String del) method is used to break a single String into an array of smaller strings. The parameter del stands for delimiter.
split()allows you to convert a single formatted string (like a CSV row) into a workable array of separate data points.
What is a Delimiter?
A delimiter is a character or sequence of characters that marks the boundary between separate pieces of data.
Examples of Different Delimiters
1. Splitting by Space (The most common "default")
If you want to break a sentence into individual words, use a space " " as the delimiter.
// In the main method ...
String sentence = "Java is fun";
String[] words = sentence.split(" ");
// result: {"Java", "is", "fun"}
2. Splitting by Comma (CSV Data) Comma-Separated Values (CSV) are common in data files.
String csvData = "Apples,10,0.99";
String[] items = csvData.split(",");
// result: {"Apples", "10", "0.99"}
3. Splitting by Semicolon or Colon You can use any character that separates your data points.
String record = "ID101:John:Smith";
String[] parts = record.split(":");
// result: {"ID101", "John", "Smith"}
Handling Empty Strings
When using split(), consecutive delimiters or delimiters at the boundaries of a string can produce empty strings (""). Java treats these in two different ways:
- Leading and Middle Empty Strings: These are preserved and included in the resulting array.
- Trailing Empty Strings: By default, any empty strings at the end of the resulting array are discarded.
Task: Observing how split() handles empty resulting strings.
// In the main method ...
// 1. Leading and Middle: Both are preserved
String s1 = ",apple,,banana";
String[] res1 = s1.split(","); // {"", "apple", "", "banana"}
// 2. Trailing: These are discarded
String s2 = "apple,banana,,";
String[] res2 = s2.split(",");
// result: {"apple", "banana"} (length 2)
// The two empty strings at the end were thrown away!
Scanner: Automatically splits data by any whitespace (spaces, tabs, newlines) unless you tell it otherwise.String.split(): Requires you to explicitly state the delimiter. It does not have an automatic default.
Example Comparison:
String data = "apple banana cherry";
// 1. Scanner handles spaces automatically
Scanner sc = new Scanner(data);
String s1 = sc.next(); // Result: "apple"
// 2. String.split() requires you to pass " "
String[] fruits = data.split(" "); // Result: {"apple", "banana", "cherry"}
Traversing a File
A while loop can be used to detect if the file still contains elements to read by using the hasNext method as the condition of the loop.
- Because you often don't know how many items are in a file,
hasNext()is the essential tool for safety, ensuring you never try to read past the end of the data.
Task: Using a while loop to read all tokens in a file.
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public void printFile(File f) throws IOException
{
Scanner sc = new Scanner(f);
while (sc.hasNext())
{
System.out.println(sc.next());
}
sc.close();
}
Resource Management
A file should be closed when the program is finished using it. The close() method from the Scanner class is called to close the file and release the system resources.
close()tells the operating system that your program is done with the file, allowing other programs to access it and freeing up memory.
Scanner Pitfalls and Exceptions
Working with the Scanner class requires careful management of its internal state.
1. Multiple next() calls in one loop
A common mistake is calling next() more than once inside a while (sc.hasNext()) loop. Each call to next() moves the cursor forward. If you call it twice, you might "skip" data or run out of tokens before the next check.
Task: Identifying dangerous multiple-call logic.
// DANGEROUS CODE:
while (sc.hasNext())
{
String first = sc.next();
String second = sc.next(); // Error if there is only one word left!
}
2. Common Runtime Exceptions
NoSuchElementException: Thrown if you callnext(),nextInt(), etc., but the scanner has already reached the end of the data. Always usehasNext()before reading!IllegalStateException: Thrown if you attempt to use any scanner method (likenext()orhasNext()) after theclose()method has been called.InputMismatchException: Thrown if the data being read doesn't match the expected type (e.g., callingnextInt()when the next word is"hello").
Excluded from AP CSA Exam
The following concepts and syntaxes are outside the scope of the AP Computer Science A course and exam.
- Excluded: Accepting input from the keyboard via
System.in.
// EXCLUDED: Interactive console input
Scanner sc = new Scanner(System.in);
- Excluded: Writing or analyzing code that uses both
nextLineand otherScannermethods (likenextInt) on the same input source. The Problem (Why it's tricky): Methods likenextInt()read only the digits. If you type18and press Enter, the18is read, but the "Enter" (newline\n) remains in the buffer. If you callnextLine()immediately after, it sees that\nand thinks the line is finished, returning an empty string. The "Professional" Fix:
int age = sc.nextInt();
sc.nextLine(); // "Consume" the leftover newline manually
String name = sc.nextLine(); // NOW it waits for the actual name
You will not need to deal with this "consuming" step on the AP Exam.
- Excluded: Using special properties of regular expressions (e.g.,
\\*,\\.) as the delimiter in thesplitmethod. What are Regular Expressions (Regex)? In Java, thesplit()method doesn't just look for a simple character; it looks for a pattern. Some characters have "superpowers" in regex: .(period) matches any character.*(asterisk) matches zero or more of the previous character. The Problem (Escaping): If you want to split a string by a literal period (like in a filename), regex will think you want to match "everything." To fix this, you must "escape" the character with double backslashes.
// EXCLUDED: Splitting by a literal period
String filename = "data.txt";
String[] parts = filename.split("\\."); // The \\ tells Java "this is just a dot"
On the AP Exam, you will only be tested on simple delimiters like , or : that do not require special regex knowledge.