Module 12, Part 1: File I/O#
In this first part of Module 12 we study how to perform input/output (I/O) operations on files.
Reading and Writing Data from/to Files#
The Java API provides several ways to read data from files, and to write data into files. In this section we explore one method to perform file I/O that is very similar to how we perform console I/O, by discussing:
how to create objects of the class ‘File’
how to read data from a File object
Creating File
Objects#
The Java API includes a class named
java.io.File,
and an object of that class represents either a file or a directory. For
instance, if we plan to work on a file named test.txt
in the current directory,
we can create a corresponding object of the class java.io.File
by writing:
(try this on the Java shell!)
var file = new java.io.File("test.txt");
We can now call various methods of our new file
— for instance,
file.exists()
returns a boolean
telling whether a file named test.txt
exists or not in the current directory.
Note
We will not focus on the methods of the class java.io.File
, but they can be
very useful for many purposes: you can have a look at
the documentation
if you want to know more.
Reading Data from a File#
To read data from a file, we can create a java.util.Scanner
object, similar to
the ones we typically use to
read inputs from the console.
The difference is that, instead of creating a Scanner
object attached to
System.in
, we create a Scanner
object attached to the File
object from
which we want to read data.
However, we need to be careful: creating a Scanner
attached to a File
object may throw a java.io.FileNotFoundException
if the file does not exist.
Such an exception is checked and
therefore we will need to decide how our code can handle it (as we did in
Example 69). This is illustrated in
Example 71 below.
(Reading all lines in a file)
Suppose we have a file called blinkenlights.txt
containing this
blinkenlights warning message:
ACHTUNG! ALLES LOOKENSPEEPERS!
Alles touristen und non-technischen looken peepers!
Das computermachine ist nicht fuer gefingerpoken und mittengrabben.
Ist easy schnappen der springenwerk, blowenfusen und poppencorken
mit spitzensparken. Ist nicht fuer gewerken bei das dumpkopfen.
Das rubbernecken sichtseeren keepen das cotten-pickenen hans in das
pockets muss; relaxen und watchen das blinkenlichten.
The program below reads all lines of this file and prints them on screen, by:
creating a
File
object for the fileblinkenlights.txt
;creating a
Scanner
object attached to that file, inside atry
-catch
statement that handles a possiblejava.io.FileNotFoundException
;reading from the scanner as long as new lines are available, similarly to how we would read inputs from the console.
1class DisplayFile {
2 public static void main(String[] args) {
3 var filename = "blinkenlights.txt";
4 var file = new java.io.File(filename);
5
6 try {
7 var scanner = new java.util.Scanner(file); // May throw IOException
8 scanner.useLocale(java.util.Locale.ENGLISH);
9
10 System.out.println("** Contents of the file '" + filename + "' **");
11 while (scanner.hasNextLine()) {
12 var line = scanner.nextLine();
13 System.out.println(line);
14 }
15 scanner.close();
16 } catch (java.io.FileNotFoundException e) {
17 System.out.println("Error: " + e.getMessage());
18 }
19 }
20}
You can experiment with the program above, e.g. by running it, and then changing
the filename
on line 3 with a non-existent file: you will see that an
exception will be thrown on line 7 and then caught on line 15.
Writing Data into a File#
To write data into a file, we can create an object of the class
java.io.PrintWriter
attached to the file we wish to write data into. So, if file
is a
File object,
then we can create a PrintWriter
object attached to it, by writing e.g.:
var printer = new java.io.PrintWriter(file);
Note that:
if the given
file
does not already exist on disk, thePrintWriter
constructor above will create it. The file will be initially empty, and it will grow when data is written into it;if the given
file
already exists on disk, thePrintWriter
constructor will truncate it, i.e., turn it into an empty file; the file will grow when data is written into it. As a consequence, the original file contents will be lost;the
PrintWriter
constructor might throw ajava.io.FileNotFoundException
if the file cannot be created: such an exception is checked and therefore we will need to decide how our code handles it (as we did in Example 69).
Importantly, objects of the class java.io.PrintWriter
provide methods that are
very similar to those offered by the object System.out
that we use for console
output. (In fact, the class of the object
System.out
is
java.io.PrintStream,
and its constructor and methods are almost identical to the class
java.io.PrintWriter
we are using here for file I/O). Therefore, we can use the printer
object
created above to call methods that have a name and a behaviour similar to what
we already know via System.out
, such as:
printer.print("Hello")
to write the stringHello
into the file to which theprinter
object is attached;printer.println("Hej")
to write the stringHej
followed by a line separator into the file to which theprinter
object is attached.
Important
When the printer
object is not needed any more, we should close it by calling
printer.close()
.
The use of a PrintWriter
object is illustrated in
Example 71 below.
(Writing data into a file)
The program below writes all numbers from 0 to 99 (in a 10 \(\times\) 10
arrangement) in the file test.txt
, by:
creating a
File
object for the filetest.txt
;creating a
PrintWriter
object attached to that file, inside atry
-catch
statement that handles a possiblejava.io.IOException
;writing strings into the stream, using the methods
.print(...)
and.println(...)
.
1class WriteFile {
2 public static void main(String[] args) {
3 var filename = "test.txt";
4 var file = new java.io.File(filename);
5
6 try {
7 var printer = new java.io.PrintWriter(file);
8
9 for (var i = 0; i < 10; i++) {
10 for (var j = 0; j < 10; j++) {
11 var padding = (i > 0) ? "" : "0"; // Initial '0' if needed
12 printer.print(padding + ((i * 10) + j) + " ");
13 }
14 printer.println(""); // Append line separator
15 }
16 printer.close();
17 } catch (java.io.IOException e) {
18 System.out.println("Error: " + e.getMessage());
19 }
20 }
21}
If you run this program, it will create a file called test.txt
(if it does not
already exist) and its contents will look like:
00 01 02 03 04 05 06 07 08 09
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99
Concluding Remarks#
You should now have an understanding of how to read and write data from/to files.
The Java API includes a large number of classes and methods for dealing with files, and you may use them when writing larger programs: see the References and Further Readings for some pointers.
References and Further Readings#
You are invited to read the following sections of the reference book:
Section 11.6 - “I/O Exceptions”
Section 5.5 - “Iterators” - subsection “Reading Text Files”
As mentioned above,
the class of the object
System.out
is
java.io.PrintStream,
which has constructors and methods that are almost identical to the class
java.io.PrintWriter
that we have used for writing data into a file.
Still, it is possible to use PrintStream
instead of PrintWriter
for writing
data into a file: the difference between the two classes is
quite subtle and is
important for some applications — but it is not crucial for the simple
programs addressed in this course, where the two classes can be used
interchangeably.
If you are curious, you can also explore the documentation of the following
classes, that contains useful information (e.g., how to create a PrintWriter
or PrintStream
or a Scanner
directly with a file name, without first
creating a File
object):
Lab and Weekly Assessments#
During the lab you can try begin your work on the weekly assessments below.
Important
For each assessment, you can download the corresponding handout and submit your solution on DTU Autolab: https://autolab.compute.dtu.dk/courses/02312-E24.
For details on how to use Autolab and the assessment handouts, and how to submit your solutions, please read these instructions.
If you have troubles, you can get help from the teacher and TAs.
01 - Mini Grep#
The goal of this assessment is to write a program that works like a simplified version of the “grep” command-line utility.
Edit the file MiniGrep.java
provided in the handout, and write a program that
takes two command-line arguments:
the first argument is a string to be searched;
the second argument is the name of a file.
First, the program must check whether it is being executed with exactly two arguments. If not, it must print and error and end immediately. For instance, if the program is executed as:
java MiniGrep.java aaa bbb ccc ddd
then the program must print the following message, and then end:
Error: expected 2 arguments, got 4 instead
If the program is being executed with exactly two arguments, then it must read each line of the file with the name specified as the second argument, and print each line that contains the string given as first argument. If the file does not exist, the program must print an error and end.
For instance, suppose that the program is executed as:
java MiniGrep.java Hello hello-world.txt
If the file hello-world.txt
does not exist, the program must print the
following message, and then end:
Error: file hello-world.txt not found
Now, suppose instead that the file hello-world.txt
exists, and contains the
following lines:
Hello, human!
This is a test
I just wanted to say: Hello, World!
The file ends here
Then, the program must print each line that contains the string Hello
(which
was given as first command-line argument). Therefore, the program must print:
Hello, human!
I just wanted to say: Hello, World!
When you are done, submit the modified file MiniGrep.java
on DTU Autolab.
Warning
The automatic grading on DTU Autolab includes some additional secret checks that test your submission with more command-line arguments and input files. After you submit, double-check your grading result on DTU Autolab: if the secret checks fail, then your solution is not correct, and you should fix it and resubmit.
Hint
You might want to revise how to access the array of command-line arguments.
To read all lines in a file, you can follow Example 71.
To check whether a
String
objectstr1
contains another stringstr2
, you could write the string search code yourself, e.g. by adapting the solution to the assessment 09 - Sub-Array…Otherwise, you can call the method
str1.contains(str2)
(provided by all objects of typeString
): you can find more details here. Note that, according to the documentation, the method.contains(...)
takes as argument any object of typeCharSequence
— which is an interface defined in the Java API that is implemented by the classString
(and this is why the method.contains(...)
can take an object of typeString
as argument).
02 - Cow Say, Part 2#
This is a follow-up to the assessment 02 - Cow Say,
and the starting point is its solution, i.e. the file CowSay.java
(you can
use either your own file, or the one provided by the teacher as a solution to
02 - Cow Say).
The purpose of this assessment is to extend the program CowSay.java
so it can
optionally read its input from a file, and write its output into a file.
More in detail, the updated program must behave as follows:
If
CowSay.java
is executed with no command-line arguments, then it must keep working as in 02 - Cow Say — i.e., it must read it input from the terminal, and then print its output on the terminal.If
CowSay.java
is executed with one command-line argument, then it must read its input from the file specified by the command-line argument, and then print its output on the terminal. You can assume that the input file contains exactly one line of text. For instance, if the program is executed as:java CowSay.java joke1.txt
then the program must read the joke contained in the file
joke1.txt
, and display on the terminal a cow telling that joke.If
CowSay.java
is executed with two command-line arguments, then it must read its input from a file specified by the first command-line argument, and then write its output into a file specified by the second command-line argument. You can assume that the input file contains exactly one line of text; the program must not display any output on the terminal. For instance, if the program is executed as:java CowSay.java joke2.txt output.txt
then the program must read the joke contained in the file
joke2.txt
, and write into the fileoutput.txt
the text representing a cow telling that joke.
When you are done, submit the modified file CowSay.java
on DTU Autolab.
Hint
You may want to revise how to read the command-line arguments of a Java program.
Depending on how you structure your code, you may need to declare that the
main
method of the classCowSay
might throw ajava.io.IOException
. Therefore, the method may look like:public static void main(String[] args) throws java.io.IOException { ... }
You can reuse and adapt most of the code of the solution to 02 - Cow Say (either yours or the teacher’s) by observing that its input/output operations use an object of type
Scanner
to read text, and the objectSystem.out
(which has typejava.io.PrintStream
) to produce output. Therefore, you could adapt the code (and avoid duplication) by moving most of it into a utility method like:private static void readTextPrintCow(java.util.Scanner scanner, java.io.PrintStream out)
which reads a line from the given
scanner
object and produces output using the givenout
object. When this method is called, the argumentscanner
might be attached either toSystem.in
or to an input file; instead, the argumentout
might be either the objectSystem.out
or a PrintStream object attached to an output file…
03 - Maze, Part 2#
This is a follow-up to the assessment 02 - Maze,
and the starting point is its solution, i.e. the file Maze.java
(you can
use either your own file, or the one provided by the teacher as a solution to
02 - Maze).
Your task is to add the capability of loading a maze from a file, and saving a maze in a file. The contents of a file containing a maze look as follows:
6,5
### X
# #
# #
## #
## ##
# #
The first line of the file contains the number of rows and columns of the maze
separated by a comma ,
(in this example, 6 rows and 5 columns), and the
remaining lines contain each row of the maze, with one character per cell (the
meaning of each character is described in 02 - Maze).
Note
When a maze with n
columns is saved in a file, each row of the maze must
contain exacty n
characters. For instance, in the file contents shown above,
each row of the maze contains 5 characters (some of them are spaces: you can see
them more clearly by selecting the text).
Edit the file Maze.java
and implement the following static methods:
public static char[][] load(String fname) throws java.io.IOException
This static method loads a maze from a text file called
fname
. You can assume that the contents of the input file respect the description above.public static void save(char[][] maze, String fname) throws java.io.IOException
This static method saves the given
maze
in a file calledfname
. If the file already exists, this static method must overwrite it.
When you are done, submit the modified file Maze.java
on DTU Autolab.
Hint
To extract the number of rows and columns of the maze from the input file, you might split the first input line (which is a
String
) around the separator","
. For more details, see Example 35.To retrieve the character at position
n
of a string objectstr
, you can usestr.charAt(n)
. For more details, see Some Useful String Methods.To convert the content of a
String
object into anint
eger value, you can use the static methodInteger.parseInt(...)
(provided by the Java API). For instance,Integer.parseInt("42")
returns theint
value 42.
Warning
The automatic grading on DTU Autolab includes some additional secret checks that test your submission by loading and saving other mazes. After you submit, double-check your grading result on DTU Autolab: if the secret checks fail, then your solution is not correct, and you should fix it and resubmit.