Files and reading data
Handling data is a fundamental aspect of software development. Many applications, such as music players and image editors, work by processing data in the form of music or image files. Even internet and mobile applications like Facebook, WhatsApp, and Telegram operate by handling user information stored in file-based databases. Regardless of the specific application, software must read and manipulate data to carry out its intended functions.
In all of these cases, the data being handled is ultimately stored in one or more files, often in a specific file format. This means that understanding how to work with files and file formats is an essential skill for software developers. By efficiently managing and manipulating data, software can provide valuable and meaningful experiences to users.
Reading From the Keyboard
Throughout this course, we have been using the Scanner
class to read user input. Typically, we use a while-true loop to continuously read input until we encounter a specific input that signals the end of the input block.
Scanner scanner = new Scanner(System.in);
while (true) {
String line = scanner.nextLine();
if (line.equals("end")) {
break;
}
// add the read line to a list for later
// handling or handle the line immediately
}
Text-based user interfaces typically direct user input into the input stream one line at a time. In the previous example, we used the Scanner
class to read user input and passed the system input stream (System.in
) as a parameter to its constructor.
By default, the Scanner
class reads user input as strings. If we want to handle the input as a different data type, such as integers, we must convert it to the appropriate form. For example, consider the program below, which reads user input until the user enters "end". As long as the input is not "end", it is treated as an integer and printed to the console:
Scanner scanner = new Scanner(System.in);
while (true) {
String row = scanner.nextLine();
if (row.equals("end")) {
break;
}
int number = Integer.parseInt(row);
System.out.println(row);
}
Files and the Filesystem
Files are collections of data that reside on computers and can contain various types of content, such as text, images, or music, depending on their format. The file format specifies the content of the file as well as the program required to read it. For example, PDF files require a PDF reader, and music files require a media player. Programmers are responsible for specifying the file format as part of their work in creating these programs.
Computers have different programs for browsing files, which are specific to the operating system. These programs make use of the computer's filesystem in various ways. In IntelliJ, you can browse the files of a project by selecting the "Project Files" tab, located alongside the "Projects" tab. You can also see the project files in the "Projects" tab along with the code. This provides a convenient way to manage and view the files associated with your project.
Reading From a File
To read a file in Java, the Scanner
class is used. To read a specific file, the path of that file is provided to the constructor of the Scanner
object. The path to the file can be obtained using the Paths.get
method, which takes the name of the file as a string parameter: Paths.get("filename.extension")
.
Once the Scanner
object has been created, the file can be read using a while-loop until all lines in the file have been read. However, reading a file may result in an error, which is why the process requires separate try and catch blocks to handle potential errors. Further details about handling errors during file reading will not be covered in this course.
// first
import java.util.Scanner;
import java.nio.file.Paths;
// in the program:
// we create a scanner for reading the file
try (Scanner scanner = new Scanner(Paths.get("file.txt"))) {
// we read the file until all lines have been read
while (scanner.hasNextLine()) {
// we read one line
String row = scanner.nextLine();
// we print the line that we read
System.out.println(row);
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
A file is read from the project root by default (when new Scanner(Paths.get("file.txt"))
is called), i.e., the folder that contains the folder src
(and possibly other files as well). The contents of this folder can the inspected using the Project Files
-tab in IntelliJ.
In the example below, we read all the lines of the file "file.txt", which are then added to a List.
List<String> lines = new ArrayList<>();
// we create a scanner for reading the file
try (Scanner scanner = new Scanner(Paths.get("file.txt"))) {
// we read all the lines of the file
while (scanner.hasNextLine()) {
lines.add(scanner.nextLine());
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
// we print the total number of lines
System.out.println("Total lines: " + lines.size());
Reading Data of a Specific Format From a File
The world is filled with data that is related to other data, forming collections. For example, personal information can include a name, date of birth, and a phone number. Address information, on the other hand, can include a country, city, street address, postal code, and so on.
Often, data is stored in files using a specific format. One such format that we are already familiar with is comma-separated values (CSV) format, where data is separated by commas.
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("Enter name and age separated by a comma: ");
String line = scanner.nextLine();
if (line.equals("")) {
break;
}
String[] parts = line.split(",");
String name = parts[0];
int age = Integer.valueOf(parts[1]);
System.out.println("Name: " + name);
System.out.println("Age: " + age);
}
The program works as follows:
Enter name and age separated by a comma: virpi,19 Name: virpi Age: 19 Enter name and age separated by a comma: jenna,21 Name: jenna Age: 21 Enter name and age separated by a comma: ada,20 Name: ada Age: 20
Reading the same data from a file called records.txt
would look like this:
try (Scanner scanner = new Scanner(Paths.get("records.txt"))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
String[] parts = line.split(",");
String name = parts[0];
int age = Integer.valueOf(parts[1]);
System.out.println("Name: " + name);
System.out.println("Age: " + age);
}
}
Reading Objects From a File
Creating objects from data that is read from a file is straightforward. Let's assume that we have a class called Person
, as well as the data from before.
Reading objects can be done like so:
List<Person> people = new ArrayList<>();
try (Scanner scanner = new Scanner(Paths.get("records.txt"))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
String[] parts = line.split(",");
String name = parts[0];
int age = Integer.valueOf(parts[1]);
people.add(new Person(name, age));
}
}
System.out.println("Total amount of people read: " + people.size());
Reading objects from a file is a clear responsibility in and of itself, and should for that reason be isolated into a method. This is what we'll be doing in the next exercise.