Class File
The conversion of a pathname string to or from an abstract pathname is inherently system-dependent. When an abstract pathname is converted into a pathname string, each name is separated from the next by a single copy of the default separator character. The default name-separator character is defined by the system property file.separator , and is made available in the public static fields separator and separatorChar of this class. When a pathname string is converted into an abstract pathname, the names within it may be separated by the default name-separator character or by any other name-separator character that is supported by the underlying system.
A pathname, whether abstract or in string form, may be either absolute or relative. An absolute pathname is complete in that no other information is required in order to locate the file that it denotes. A relative pathname, in contrast, must be interpreted in terms of information taken from some other pathname. By default the classes in the java.io package always resolve relative pathnames against the current user directory. This directory is named by the system property user.dir , and is typically the directory in which the Java virtual machine was invoked.
The parent of an abstract pathname may be obtained by invoking the getParent() method of this class and consists of the pathname’s prefix and each name in the pathname’s name sequence except for the last. Each directory’s absolute pathname is an ancestor of any File object with an absolute abstract pathname which begins with the directory’s absolute pathname. For example, the directory denoted by the abstract pathname «/usr» is an ancestor of the directory denoted by the pathname «/usr/local/bin» .
- For UNIX platforms, the prefix of an absolute pathname is always «/» . Relative pathnames have no prefix. The abstract pathname denoting the root directory has the prefix «/» and an empty name sequence.
- For Microsoft Windows platforms, the prefix of a pathname that contains a drive specifier consists of the drive letter followed by «:» and possibly followed by «\\» if the pathname is absolute. The prefix of a UNC pathname is «\\\\» ; the hostname and the share name are the first two names in the name sequence. A relative pathname that does not specify a drive has no prefix.
Instances of this class may or may not denote an actual file-system object such as a file or a directory. If it does denote such an object then that object resides in a partition. A partition is an operating system-specific portion of storage for a file system. A single storage device (e.g. a physical disk-drive, flash memory, CD-ROM) may contain multiple partitions. The object, if any, will reside on the partition named by some ancestor of the absolute form of this pathname.
A file system may implement restrictions to certain operations on the actual file-system object, such as reading, writing, and executing. These restrictions are collectively known as access permissions. The file system may have multiple sets of access permissions on a single object. For example, one set may apply to the object’s owner, and another may apply to all other users. The access permissions on an object may cause some methods in this class to fail.
Instances of the File class are immutable; that is, once created, the abstract pathname represented by a File object will never change.
Interoperability with java.nio.file package
The java.nio.file package defines interfaces and classes for the Java virtual machine to access files, file attributes, and file systems. This API may be used to overcome many of the limitations of the java.io.File class. The toPath method may be used to obtain a Path that uses the abstract path represented by a File object to locate a file. The resulting Path may be used with the Files class to provide more efficient and extensive access to additional file operations, file attributes, and I/O exceptions to help diagnose errors when an operation on a file fails.
Files and reading data
A considerable amount of software is in one way or another based on handling data. Software created for playing music handles music files and those created for the purpose of image manipulation handle image files. Applications that run on the internet and mobile devices, such as Facebook, WhatsApp, and Telegram, handle user information that is stored in file-based databases. What these all have in common is that they read and manipulate data in one way or another. Also, the data being handled is ultimately stored in some format in one or more files.
Reading From the Keyboard
We’ve been using the Scanner -class since the beginning of this course to read user input. The block in which data is read has been a while-true loop where the reading ends at a specific input.
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 >
In the example above, we pass system input ( System.in ) as a parameter to the constructor of the Scanner-class. In text-based user interfaces, the input of the user is directed into the input stream one line at a time, which means that the information is sent to be handled every time the user enters a new line.
The user input is read in string form. If we wanted to handle the input as integers, for instance, we’d have to convert it to another form. An example program has been provided below — it reads input from the user until the user inputs «end». As long as the user input is not «end» the inputs are handled as integers — in this case, the number is simply printed.
Scanner scanner = new Scanner(System.in); while (true) String row = scanner.nextLine(); if (row.equals("end")) break; > int number = Integer.valueOf(row); System.out.println(row); >
Files and the Filesystem
Files are collections of data that live in computers. These files can contain, among other things, text, images, music, or any combination of these. The file format determines the content of the file as well as the program required to read the file. For example, PDF files are read with a program suited for reading PDF files, and music files are read with a program suited for reading music files. Each of these programs is made by humans, and the creators of these programs — i.e., programmers — also specify the file format as part of the work.
Computers have several different programs for browsing files. These programs are specific to the operating system. All programs used for browsing files make use of the filesystem of the computer in one way or another.
Our development environment provides us with the ability to browse the files of a project. In NetBeans you can take a look at all the files attached to a project by selecting the Files tab, which is found in the same place as the Projects tab. If the tab cannot be be found, it can be opened from the Window menu. Clicking the project to open it will reveal all its files.
The Concrete File Storage Format
Files exist on the hard drive of a computer, which is, in reality, a large set of ones and zeros, i.e., bits. Information is made up of these bits, e.g., one variable of type int takes up 32 bits (i.e., 32 ones or zeros). Modern terabyte-sized hard drives hold about 8 trillion bits (written out the number is 8,000,000,000,000). On this scale, a single integer is very small.
Files can exist practically anywhere on a hard drive, even separated into multiple pieces. The computer’s filesystem has the responsibility of keeping track of the locations of files on the hard drive as well as providing the ability to create new files and modify them. The filesystem’s main responsibility is abstracting the true structure of the hard drive; a user or a program using a file doesn’t need to care about how, or where, the file is actually stored.
Reading From a File
Reading a file is done using the Scanner-class. When we want to read a file using the Scanner-class, we give the path for the file we want to read as a parameter to the constructor of the class. The path to the file can be acquired using Java’s Paths.get command, which is given the file’s name in string format as a parameter: Paths.get(«filename.extension») .
When the Scanner -object that reads the file has been created, the file can be read using a while-loop. The reading proceeds until all the lines of the file have been read, i.e., until the scanner finds no more lines to read. Reading a file may result in an error, and it’s for this reason that the process requires separate blocks — one for the try , and another to catch potential errors. We’ll be returning to the topic of error handling later.
// 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 the file pom.xml (and possibly other files as well). The contents of this folder can the inspected using the Files -tab in NetBeans.
In the example below, we read all the lines of the file «file.txt», which are then added to an ArrayList.
ArrayListString> 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());
An Empty Line In a File
Sometimes an empty line finds it way into a file. Skipping an empty line can be done using the command continue and the isEmpty -method of the string.
In the example below, we read from a file
Reading data is straightforward.
// we create a scanner for reading the file try (Scanner scanner = new Scanner(Paths.get("henkilot.csv"))) // we read all the lines of the file while (scanner.hasNextLine()) String line = scanner.nextLine(); // if the line is blank we do nothing if (line.isEmpty()) continue; > // do something with the data > > catch (Exception e) System.out.println("Error: " + e.getMessage()); >
Reading Data of a Specific Format From a File
The world is full of data that are related to other data — these form 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 number and so on.
Data is often stored in files using a specific format. One such format that’s already familiar to us is comma-separated values (CSV) format, i.e., data 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 so:
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:
ArrayListPerson> 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.
Remember to check your points from the ball on the bottom-right corner of the material!