This material is licensed under the Creative Commons BY-NC-SA license, which means that you can use it and distribute it freely so long as you do not erase the names of the original authors. If you make changes in the material and want to distribute this altered version of the material, you have to license it with a similar free license. The use of the material for commercial use is prohibited without a separate agreement.
Authors: Arto Hellas, Matti Luukkainen
Translators to English: Emilia Hjelm, Alex H. Virtanen, Matti Luukkainen, Virpi Sumu, Birunthan Mohanathas, Etiënne Goossens
Extra material added by: Etiënne Goossens, Maurice Snoeren, Johan Talboom
The course is maintained by Technische Informatica Breda
A relevant part of programming is related to stored files, in one way or in another. Let’s take the first steps in Java file handling. Java’s API provides the class File, whose contents can be read using the already known Scanner class.
If we read the desciption of the File
API we notice the File
class has the constructor File(String pathname)
, which creates a new File instance by converting the given pathname string into an abstract pathname. This means the File
class constructor can be given the pathname of the file we want to open.
In the NetBeans programming environment, files have got their own tab called Files, which contains all our project files. If we add a file to a project root – that is to say outside all folders – we can refer to it by writing only the its name. We create a file object by fiving the file pathname to it as parameter:
File file = new File("file-name.txt");
System.in
input stream is not the only reading source we can give to the constructor of a Scanner
class. For instance, the reading source can be a file, in addition to the user keyboard. Scanner
provides the same methods to read a keyboard input and a file. In the following example, we open a file and we print all the text contained in the file using the System.out.println
statement. At the end, we close the file using the statement close
.
// The file we read
File file = new File("filename.txt");
Scanner reader = new Scanner(file);
while (reader.hasNextLine()) {
String line = reader.nextLine();
System.out.println(line);
}
reader.close();
The Scanner
class constructor public Scanner(File source) (Constructs a new Scanner that produces values scanned from the specified file.) throws a FileNotFoundException when the specified file is not found. The FileNotFoundException
is different than RuntimeException,
and we have either to handle it or throw it forward. At this point, you only have to know that the programming environment tells you whether you have to handle the exception or not. Let’s first create a try-catch block where we handle our file as soon as we open it.
public void readFile(File f) {
// the file we read
Scanner reader = null;
try {
reader = new Scanner(f);
} catch (Exception e) {
System.out.println("We couldn't read the file. Error: " + e.getMessage());
return; // we exit the method
}
while (reader.hasNextLine()) {
String line = reader.nextLine();
System.out.println(line);
}
reader.close();
}
Another option is to delegate the exception handling responsibility to the method caller. We delegate the exception handling responsibility by adding the definition throws ExceptionType
to the method. For instance, we can add throws Exception
because the type of all exceptions is Exception
. When a method has the attribute throws Exception
, whatever chunk of code which calls that method knows that it may throw an exception, and it should be prepared for it.
public void readFile(File f) throws Exception {
// the file we read
Scanner reader = new Scanner(f);
while (reader.hasNextLine()) {
String line = reader.nextLine();
System.out.println(line);
}
reader.close();
}
In the example, the method readFile
receives a file as parameter, and prints all the file lines. At the end, the reader is closed, and the file is closed with it, too. The attribute throws Exception
tells us that the method may throw an exception. Same kind of attributes can be added to all the methods that handle files.
Note that the Scanner
object’s method nextLine
returns a string, but it does not return a new line at the end of it. If you want to read a file and still maintain the new lines, you can add a new line at the end of each line:
public String readFileString(File f) throws Exception {
// the file we read
Scanner reader = new Scanner(f);
String string = "";
while (reader.hasNextLine()) {
String line = reader.nextLine();
string += line;
string += "\n";
}
reader.close();
return string;
}
Because we use the Scanner
class to read files, we have all Scanner
methods available for use. For instance the method hasNext()
returns the boolean value true if the file contains something more to read, and the method next()
reads the following word and returns a String
object.
The following program creates a Scanner
object which opens the file file.txt. Then, it prints every fifth word of the file.
File f = new File("file.txt");
Scanner reader = new Scanner(f);
int whichNumber = 0;
while (reader.hasNext()) {
whichNumber++;
String word = reader.next();
if (whichNumber % 5 == 0) {
System.out.println(word);
}
}
Below, you find the text contained in the file, followed by the program output.
Exception handling is the process of responding to the occurrence, during computation, of exceptions – anomalous or exceptional events
requiring special processing – often changing the normal flow of program execution. ...
process
occurrence,
–
requiring
changing
program
When we read a text file (or when we save something into a file), Java has to find out the character set used by the operating system. Knowledge of the character set is required both to save text on the computer harddisk in binary format, and to translate binary data into text.
There have been developed standard character sets, and “UTF-8” is the most common nowadays. UTF-8 character set contains both the alphabet letters of everyday use and more particular characters such as the Japanese kanji characters or the information need to read and save the chess pawns. From a simplified programming angle, we could think a character set both as a character-number hashmap and a number-character hashmap. The character-number hashmap shows what binary number is used to save each character into a file. The number-character hashmap shows how we can translate into characters the values we obtain reading a file.
Almost each operating system producer has also got their own standards. Some support and want to contribute to the use of open source standards, some do not. If you have got problems with the use of Scandinavian characters such as ä and ö (expecially Mac and Windows users), you can tell which character set you want to use when you create a Scanner
object. In this course, we always use the the “UTF-8” character set.
You can create a Scanner object which to read a file which uses the UTF-8 character set in the following way:
File f = new File("examplefile.txt");
Scanner reader = new Scanner(f, "UTF-8");
Anther thing you can do to set up a character set is using an environment variable. Macintosh and Windows users can set up an the value of the environment variable JAVA_TOOL_OPTIONS
to the string -Dfile.encoding=UTF8
. In such case, Java always uses UTF-8 characters as a default.
Exercise fileio-1: Printer
Create the class
Printer
, its constructorpublic Printer(String fileName)
which receives aString
standing for the file name, and the methodpublic void printLinesWhichContain(String word)
which initializes the FileIO, Scanner and prints the file lines which contain the parameter word (lower and upper case make difference in this excercise; for instance, “test” and “Test” are not the considered the same); the lines are printed in the same order as they are inside the file.If the argument is an empty
String
, all of the file is printed.If the file is not found, the method delegates the exception with no need for a try-catch statement; the method simply has to be defined in the following way:
public Printer { public void printLinesWhichContain(String word) throws Exception { // ... } // ... }
The file textFile has been place into the default package of your project to help the tests. When you define the file name of the constructor of
Printer
, you have to write src/textfile.txt. The file contains an extract of Kalevala, a Finnish epic poem:Siinä vanha Väinämöinen katseleikse käänteleikse Niin tuli kevätkäkönen näki koivun kasvavaksi Miksipä on tuo jätetty koivahainen kaatamatta Sanoi vanha Väinämöinen
The following example shows what the program should do:
Printer printer = new Printer("src/textfile.txt"); printer.printLinesWhichContain("Väinämöinen"); System.out.println("-----"); printer.printLinesWhichContain("Frank Zappa"); System.out.println("-----"); printer.printLinesWhichContain(""); System.out.println("-----");
Prints:
Siinä vanha Väinämöinen Sanoi vanha Väinämöinen ----- ----- Siinä vanha Väinämöinen katseleikse käänteleikse Niin tuli kevätkäkönen näki koivun kasvavaksi Miksipä on tuo jätetty koivahainen kaatamatta Sanoi vanha Väinämöinen
In the project, you also find the whole Kalevala; the file name is src/kalevala.txt
Exercise fileio-2: File Analysis
In this exercise, we create an application to calculate the number of lines and characters.
Exercise fileio-2.1: Number of lines
Create the class
Analysis
in the packagefile
; the class has the constructorpublic Analysis(File file)
. Create the methodpublic int lines()
, which returns the number of lines of the file the constructor received as parameter.The method cannot be “disposable”, that is to say it has to return the right value even though it is called different times in a raw. Note that after you create a
Scanner
object for a file and read its whole contents usingnextLine
method calls, you can’t use the same scanner to read the file again!Attention: if the tests report a timeout, it probably means that you haven’t been reading the file at all, meaning that the
nextLine
method calls miss!Exercise fileio-2.2: Number of Characters
Create the method
public int characters()
in the classAnalysis
; the method returns the number of characters of the file the constructor received as parameter.The method cannot be “disposable”, that is to say it has to return the right value even though it is called different times in a raw.
You can decide yourself what to do if the constructor parameter file does not exist.
The file testFile has been place into the test package of your project to help the tests. When you define the file name of the constructor of
Analysis
, you have to write test/testfile.txt. The file contains the following text:there are 3 lines, and characters because line breaks are also characters
The following example shows what the program should do:
File file = new File("test/testfile.txt"); Analysis analysis = new Analysis(file); System.out.println("Lines: " + analysis.lines()); System.out.println("Characters: " + analysis.characters());
Lines: 3 Characters: 74
Exercise fileio-3: Word Inspection
Create the class
WordInspection
, which allows for different kinds of analyses on words. Implement the class in the packagewordinspection
.The Institute for the Languages of Finland (Kotimaisten kielten tutkimuskeskus, Kotus) has published online a list of Finnish words. In this exercise we use a modified version of that list, which can be found in the exercise source folder src with the name wordList.txt; the relative path is “src/wordList.txt”. Because the word list if quite long, in fact, a shortList.txt was created in the project for the tests; the file can be found following the path “src/shortList.txt”.
If you have problems with Scandinavian letters (Mac and Windows users) create your Scanner object assigning it the “UTF-8” character set, in the following way:
Scanner reader = new Scanner(file, "UTF-8");
Problems come expecially when the tests are executed.Exercise fileio-3.1: Word Count
Create the constructor
public WordInspection(File file)
to yourWordInspection
class.Create the method
public int wordCount()
, which counts the file words and prints their number. In this part, you don’t have to do anything with the words, you should only count how many there are. For this exercise, you can expect there is only one word in each row.Exercise fileio-3.2: z
Create the method
public List<String> wordsContainingZ()
, which returns all the file words which contain a z; for instance, jazz and zombie.Exercise fileio-3.3: Ending l
Create the method
public List<String> wordsEndingInL()
, which returns all the Finnish words of the file which end in l; such words are, for instance, kannel and sammal.Attention! If you read the file various different times in your program, you notice that your code contains a lot of copy-paste, so far. It would be useful to think whether it would be possible to read the file in an different place, maybe inside the constructor or as a method, which the constructor calls. In such case, the methods could use a list which was read before and then create a new list which suits their search criteria. In week 12, we will come back again with an ortodox way to eliminate copy-paste.
Exercise fileio-3.4: Palindromes
Create the method
public List<String> palindromes()
, which returns all the palindrome words of the file. Such words are, for instance, ala and enne.
In section 12.1, we learnt that reading from a file happened with the help of the classes Scanner
and File
. The class FileWriter provides the functionality to write to a file. The FileWriter
constructor is given as parameter a String
illustrating the file location.
FileWriter writer = new FileWriter("file.txt");
writer.write("Hi file!\n"); // the line break has to be written, too!
writer.write("Adding text\n");
writer.write("And more");
writer.close(); // the call closes the file and makes sure the written text goes to the file
In the example we write the string “Hi file!” to the file “file.txt”; that is followed by a line break, and by more text. Note that when you use the write
method, it does not produce line breaks, but they have to be added later manually.
Both the FileWriter
constructor and the write
method may throw an exception, which has to be either handled or the responsibility has to be delegated to the calling method. The method which is given as parameter the file name and the text to write into it can look like the following.
public class FileHandler {
public void writeToFile(String fileName, String text) throws Exception {
FileWriter writer = new FileWriter(fileName);
writer.write(text);
writer.close();
}
}
In the above method writeToFile
, we first create a FileWriter
object, which writes into the fileName
file stored at the location specified as parameter. After this, we write into the file using the write
method. The exception the constructor and write method can possibly throw has to be handled either with the help of a try-catch
block or delegating the responsibility. In the method writeToFile
the responsibility was delegated.
Let’s create a main
method where we call the writeToFile
method of a FileHandler
object. The exception does not have to be handled in the main
method either, but the method can declare to throw possibly an exception throw the definition throws Exception
.
public static void main(String[] args) throws Exception {
FileHandler handler = new FileHandler();
handler.writeToFile("diary.txt", "Dear Diary, today was a nice day.");
}
When we call the method above, we create the file “diary.txt”, where we write the text “Dear Diary, today was a nice day.”. If the file exists already, the old content is erased and the new one is written. FileWriter
allows us to add text at the end of the already existing file by providing additional parameter boolean append
, without erasing the existing text. Let’s add the method appendToFile()
to the class FileHandler
; the method appends the text received as parameter to the end of the file.
public class FileHandler {
public void writeToFile(String fileName, String text) throws Exception {
FileWriter writer = new FileWriter(fileName);
writer.write(text);
writer.close();
}
public void appendToFile(String fileName, String text) throws Exception {
FileWriter writer = new FileWriter(fileName, true);
writer.write(text);
writer.close();
}
}
In most of the cases, instead of writing text at the end of a file with the method append
, it is easier to write all the file again.
Exercise fileio-4: File Manager
Together with the exercise body, you find the class
FileManager
, which contains the method bodies to read a write a file.Exercise fileio-4.1: File Reading
Implement the method
public ArrayList<String> read(String file)
to return the lines of the parameter file inArrayList
form, each file line being aString
contained by theArrayList
.There are two text files to help testing the project: src/testinput1.txt and src/testinput2.txt. The methods are supposed to be used in the following way:
public static void main(String[] args) throws FileNotFoundException, IOException { FileManager f = new FileManager(); for (String line : f.read("src/testinput1.txt")) { System.out.println(line); } }
The print output should look like the following
first second
Exercise fileio-4.2: Writing a Line
Modify the method
public void save(String file, String text)
so that it would write the string of the second argument into the file of the first argument. If the file already exists, the string is written over the old version.Exercise fileio-4.3: Writing a List
Modify the method
public void save(String file, ArrayList<String> texts)
so that it would write the strings of the second argument into the file of the first argument; each string of the array list has to go to its own line. If the file already exists, the strings are written over the old version.Exercise fileio-5: Two-Direction Dictionary
With this exercise, we develop the dictionary we implemented earlier, so that words can be both read and written into the file. Also, the dictionary has to translate in both directions, from Finnish into English and from English into Finnish (in this exercise, we suppose unofficially that Finnish and English do not have words which are spellt the same). Your task is creating the dictionary in the class
MindfulDictionary
. The class has to be implemented in the packagedictionary
.Exercise fileio-5.1: Forgetful Basic Functionality
Create a parameterless constructor, as well as the methods:
public void add(String word, String translation)
adds a word to the dictionary. Each word has only one translation; if the same word is added twice, nothing happens.public String translate(String word)
returns the word translation; if the word isn’t recognised, it returns nullAt this point, the dictionary has to work in the following way:
MindfulDictionary dict = new MindfulDictionary(); dict.add("apina", "monkey"); dict.add("banaani", "banana"); dict.add("apina", "apfe"); System.out.println( dict.translate("apina") ); System.out.println( dict.translate("monkey") ); System.out.println( dict.translate("programming") ); System.out.println( dict.translate("banana") );
Prints:
monkey apina null banaani
As you notice from the example, after adding a pair the dictionary can translate in both directions.
Exercise fileio-5.2: Removing Words
Add the method
public void remove(String word)
, which removes the given word and its translation from your dictionary.At this point, the dictionary has to work in the following way:
MindfulDictionary dict = new MindfulDictionary(); dict.add("apina", "monkey"); dict.add("banaani", "banana"); dict.add("ohjelmointi", "programming"); dict.remove("apina"); dict.remove("banana"); System.out.println( dict.translate("apina") ); System.out.println( dict.translate("monkey") ); System.out.println( dict.translate("banana") ); System.out.println( dict.translate("banaani") ); System.out.println( dict.translate("ohjelmointi") );
Prints:
null null null null programming
As you see, the delection happens in both ways: whether you remove a word or its translation, the dictionary loses the both the pieces of information.
Exercise fileio-5.3: Loading a File
Create the constructor
public MindfulDictionary(String file)
and the methodpublic boolean load()
, which loads a file whose name is given as parameter in the dictionary constructor. If opening or reading the file does not work, the method returns false and otherwise true.Each line of the dictionary file contains a word and its translation, divided by the character “:”. Together with the exercise body, you find a dictionary file meant to help the tests. It looks like the following:
apina:monkey alla oleva:below olut:beer
Read the dictionary file line by line with the reader method
nextLine
. You can split the lines with theString
methodsplit
, in the following way:Scanner fileReader = new ... while ( fileReader.hasNextLine() ){ String line = fileReader.nextLine(); String[] parts = line.split(":"); // the line is split at : System.out.println( parts[0] ); // the part of the line before : System.out.println( parts[1] ); // the part of the line after : }
The dictionary is used in the following way:
MindfulDictionary dict = new MindfulDictionary("src/words.txt"); dict.load(); System.out.println( dict.translate("apina") ); System.out.println( dict.translate("ohjelmointi") ); System.out.println( dict.translate("alla oleva") );
Printing:
monkey null below
Exercise fileio-5.4: Saving Data
Create the method
public boolean save()
; when the method is called, the dictionary contents are written into the file whose name was given as parameter to the constructor. The method returns false if the file can’t be saved; otherwise it returns true. Dictionary files have to be saved in the form described above, meaning that the program has to be able to read its own files.Attention: even though the dictionary can translate in both directions, only one direction has to be stored into the dictionary file. For instance, if the dictionary knows that tietokone = computer, you have to write either the line:
tietokone:computer
or the line
computer:tietokone
but not both!
It may be useful to write the new translation list over the old file; in fact, the
append
command which came out in the material should not be used. The final version of your dictionary should be used in the following way:MindfulDictionary dict = new MindfulDictionary("src/words.txt"); dict.load(); // using the dictionary dict.save();
At the beginning we load the dictionary from our file, and we save it back in the end, so that the changes made to the dictionary will be available next time, too.