Chapter 14 I/O - Tips Flashcards

1
Q

Conceptualizing the File System

  • file
  • directory
  • file system
  • root directory
  • path
  • file separators
  • absolute path
  • relative path
  • path symbol
  • symbolic link
A
  • A file within the storage device holds data. Files are organized into hierarchies using directories.
  • A directory is a location that can contain files as well as other directories.
  • The file system is in charge of reading and writing data within a computer. Different operating systems use different file systems to manage their data.
  • root directory is the topmost directory in the file system, ex: Windows C:\ and Linux /
  • A path is a representation of a file or directory within a file system.
  • File Separators, Unix-based systems use the forward slash, /, for paths, whereas Windows-based systems use the backslash, \
  • The absolute path of a file or directory is the full path from the root directory to the file or directory,
  • the relative path of a file or directory is the path from the current working directory to the file or directory.
  • A path symbol is one of a reserved series of characters with special meaning in some file systems.
    • . A reference to the current directory
    • .. A reference to the parent of the current directory
  • A symbolic link is a special file within a file system that serves as a reference or pointer to another file or directory.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

NOTE

> [!NOTE]
I/O APIs do not support symbolic links,
NIO.2 includes full support for creating, detecting, and navigating symbolic links within the file system.
both the I/O and NIO.2 classes can interact with a URI.
A uniform resource identifier (URI) is a string of characters that identifies a resource.
It begins with a schema that indicates the resource type, followed by a path value such as file:// for local file systems and http://, https://, and ftp:// for remote file systems.

A
  • I/O APIs do not support symbolic links
  • NIO.2 support symbolic links
  • both the I/O and NIO.2 classes can interact with a URI
  • A uniform resource identifier (URI) is a string of characters that identifies a resource. It begins with a schema that indicates the resource type, followed by a path value such as file:// for local file systems and http://, https://, and ftp:// for remote file systems.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Operating System File Separators

System.out.print(System.getProperty("file.separator"));

A

Java offers a system property to retrieve the local separator character for the current environment:
System.out.print(System.getProperty("file.separator"));

we used two backslashes (\\) in the path String, such as C:\data\zoo.txt.
When the compiler sees a \\ inside a String expression, it interprets it as a single \ value.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Creating a File or Path

  • java.io.File
    • public File(String pathname)
    • public File(File parent, String child)
    • public File(String parent, String child)
  • java.nio.file.Path
    • public static Path of(String first, String… more)
    • public static Path of(URI uri)
  • java.nio.file.Paths
    • public static Path get(String first, String… more)
    • public static Path get(URI uri)
A
  • java.io.File class is cread by calling its constructor.
    • 3 constructors
      • public File(String pathname)
      • public File(File parent, String child)
      • public File(String parent, String child)
    • If we passed null as the parent to the final constructor, it would be ignored, and the method would behave the same way as the single String constructor.
  • java.nio.file.Path interface, obtain a Path object is to use a static factory method defined on Path or Paths.
    Both methods allow passing a varargs parameter to pass additional path elements. The values are combined and automatically separated by the operating system–dependent file separator.
    • java.nio.file.Path
      • public static Path of(String first, String… more)
      • public static Path of(URI uri)
    • java.nio.file.Paths
      • public static Path get(String first, String… more)
      • public static Path get(URI uri)
  • java.io.nio.file.Files NIO.2 helper class, Files operates on Path instances
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Switching between File and Path

  • default File toFile()
  • public Path toPath()
A

When working with newer applications, you should rely on NIO.2’s Path interface, as it contains a lot more features.

File file = new File("rabbit");
Path nowPath = file.toPath();
File backToFile = nowPath.toFile();
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Obtaining a Path from the FileSystems Class

  • public static FileSystem getDefault()
  • public abstract Path getPath(String first, String… more)
A
  • The FileSystems class creates instances of the abstract FileSystem class.
  • The latter includes methods for working with the file system directly.
  • Both Paths.get() and Path.of() are shortcuts for this FileSystem method.
Path zooPath1 = FileSystems.getDefault().getPath("/home/tiger/data/stripes.txt");
Path zooPath2 = FileSystems.getDefault().getPath("/home", "tiger", "data", "stripes.txt");
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Reviewing I/O and NIO.2 Relationships

A

FIGURE 14.3 I/O and NIO.2 class and interface relationships

  • FileSystems create FileSystem
  • FileSystem create Path
  • Paths create Path
  • Files uses Path
  • Path covert java.net.URI
  • Path covert java.io.File
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

TABLE 14.2 Options for creating File and Path

A

java.io.File

  • public File(String pathname)
  • public File(File parent, String child)
  • public File(String parent, String child)
  • public Path toPath()

java.nio.file.Path

  • public default File toFile()
  • public static Path of(String first, String… more)
  • public static Path of(URI uri)

java.nio.file.Paths

  • public static Path get(String first, String… more)
  • public static Path get(URI uri)

java.nio.file.FileSystem

  • public Path getPath(String first, String… more)

java.nio.file.FileSystems

  • public static FileSystem getDefault()
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

TABLE 14.3 Common File and Path operations

A
  • Gets name of file/directory
    • public String getName()
    • getFileName()
  • Retrieves parent directory or null if there is none
    • public String getParent()
    • getParent()
  • Checks if file/directory is absolute path
    • public boolean isAbsolute()
    • isAbsolute()
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

TABLE 14.4 Common File and Files operations
I/O

  • Deletes file/directory
    • public boolean delete()
  • Checks if file/directory exists
    • public boolean exists()
  • Retrieves absolute path of file/directory
    • public String getAbsolutePath()
  • Checks if resource is directory
    • public boolean isDirectory()
  • Checks if resource is file
    • public boolean isFile()
  • Returns the time the file was last modified
    • public long lastModified()
  • Retrieves number of bytes in file
    • public long length()
  • Lists contents of directory
    • public File[] listFiles()
  • Creates directory
    • public boolean mkdir()
  • Creates directory including any nonexistent parent directories
    • public boolean mkdirs()
  • Renames file/directory denoted
    • public boolean renameTo(File dest)
A

TABLE 14.4 Common File and Files operations
NIO.2

  • Deletes file/directory
    • public static void delete(Path path) throws IOException
    • public static boolean deleteIfExists(Path path) throws IOException
  • Checks if file/directory exists
    • public static boolean exists(Path path, LinkOption... options)
  • Retrieves absolute path of file/directory
    • Path toAbsolutePath() <–this is Path interface method
  • Checks if resource is directory
    • public static boolean isDirectory(Path path, LinkOption... options)
  • Checks if resource is file
    • public static boolean isRegularFile(Path path, LinkOption... options)
  • Returns the time the file was last modified
    • public static FileTime getLastModifiedTime(Path path, LinkOption... options) throws IOException
  • Retrieves number of bytes in file
    • public static long size(Path path) throws IOException
  • Lists contents of directory
    • public static Stream<Path> list(Path dir) throws IOException
  • Creates directory
    • public static Path createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException
  • Creates directory including any nonexistent parent directories
    • public static Path createDirectories(Path dir, FileAttribute<?>... attrs) throws IOException
  • Renames file/directory denoted
    • public static Path move(Path source, Path target, CopyOption... options) throws IOException
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

TABLE 14.4 Common File and Files operations

A
  • Deletes file/directory
    • public boolean delete()
    • deleteIfExists(Path p) throws IOException
  • Checks if file/directory exists
    • public boolean exists()
    • exists(Path p, LinkOption… o)
  • Retrieves absolute path of file/directory
    • public String getAbsolutePath()
    • Path toAbsolutePath() <–this is Path interface method
  • Checks if resource is directory
    • public boolean isDirectory()
    • isDirectory(Path p, LinkOption… o)
  • Checks if resource is file
    • public boolean isFile()
    • isRegularFile(Path p, LinkOption… o)
  • Returns the time the file was last modified
    • public long lastModified()
    • getLastModifiedTime(Path p, LinkOption… o) throws IOException
  • Retrieves number of bytes in file
    • public long length()
    • size(Path p) throws IOException
  • Lists contents of directory
    • public File[] listFiles()
    • list(Path p) throws IOException
  • Creates directory
    • public boolean mkdir()
    • createDirectory(Path p, FileAttribute… a) throws IOException
  • Creates directory including any nonexistent parent directories
    • public boolean mkdirs()
    • createDirectories(Path p, FileAttribute… a) throws IOException
  • Renames file/directory denoted
    • public boolean renameTo(File dest)
    • move(Path src, Path dest, CopyOption… o) throws IOException
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Closing the Stream
Stream object inside a try-with-resources?

36: try (Stream stream = Files.list(path)) {
37:     stream.forEach(p -> 
38:         System.out.println("   " + p.getName()));
39: }
A
  • The NIO.2 stream-based methods open a connection to the file system that must be properly closed;
  • otherwise, a resource leak could ensue. A resource leak within the file system means the path may be locked from modification long after the process that used it is completed.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Handling Methods That Declare IOException

A

Many of the methods presented in this chapter declare IOException.
Common causes of a method throwing this exception include the following:

  • Loss of communication to the underlying file system.
  • File or directory exists but cannot be accessed or modified.
  • File exists but cannot be overwritten.
  • File or directory is required but does not exist.

Methods that access or change files and directories, such as those in the Files class, often declare IOException. There are exceptions to this rule, as we will see. For example, the method Files.exists() does not declare IOException. If it did throw an exception when the file did not exist, it would never be able to return false!

As a rule of thumb, if a NIO.2 method declares an IOException, it usually requires the paths it operates on to exist.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Providing NIO.2 Optional Parameters

  • LinkOption
  • StandardCopyOption
  • StandardOpenOption
  • FileVisitOption
A

TABLE 14.5 Common NIO.2 method arguments

  • LinkOption.NOFOLLOW_LINKS-Do not follow symbolic links.
  • StandardCopyOption.ATOMIC_MOVE- Move file as atomic file system operation.
  • StandardCopyOption.COPY_ATTRIBUTES-Copy existing attributes to new file.
  • StandardCopyOption.REPLACE_EXISTING-Overwrite file if it already exists.
  • StandardOpenOption.APPEND-If file is already open for write, append to the end.
  • StandardOpenOption.CREATE-Create new file if it does not exist.
  • StandardOpenOption.CREATE_NEW-Create new file only if it does not exist; fail otherwise.
  • StandardOpenOption.READ-Open for read access.
  • StandardOpenOption.TRUNCATE_EXISTING-If file is already open for write, erase file and append to beginning.
  • StandardOpenOption.WRITE-Open for write access.
  • FileVisitOption.FOLLOW_LINKS-Follow symbolic links.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

What the following call to Files.exists() with the LinkOption does in the following code snippet?

Path path = Paths.get("schedule.xml");
boolean exists = Files.exists(path, LinkOption.NOFOLLOW_LINKS);
A
  • The Files.exists() simply checks whether a file exists.
  • But if the parameter is a symbolic link, the method checks whether the target of the symbolic link exists, instead.
  • Providing LinkOption.NOFOLLOW_LINKS means the default behavior will be overridden, and the method will check whether the symbolic link itself exists.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

For example, the Files.move() method takes a CopyOption vararg so it can take enums of different types, and more options can be added over time.

public static Path copy(Path source,  Path target,  CopyOption... options) throws IOException

LinkOption and StandardCopyOption Implemented CopyOption

void copy(Path source, Path target) throws IOException {
   Files.move(source, target, 
      LinkOption.NOFOLLOW_LINKS,
      StandardCopyOption.ATOMIC_MOVE);
}
A

Files.move() method takes a CopyOption vararg so it can take enums of different types, and more options can be added over time.

public static Path copy(Path source,  Path target,  CopyOption... options) throws IOException

LinkOption and StandardCopyOption Implemented CopyOptionAnswer

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Interacting with NIO.2 Path

A

Path instances are immutable

In the following example, the Path operation on the second line is lost since p is immutable:

Path p = Path.of("whale");
p.resolve("krill");
System.out.println(p);  // whale

Many of the methods available in the Path interface transform the path value in some way and return a new Path object, allowing the methods to be chained. We demonstrate chaining in the following example, the details of which we discuss in this section of the chapter:

Path.of("/zoo/../home").getParent().normalize().toAbsolutePath();
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Viewing the Path

  • String toString()
  • int getNameCount()
  • Path getName(int index)
  • Path getFileName()
    Returns the name of the file or directory denoted by this path as a Path object. The file name is the farthest element from the root in the directory hierarchy.
A

The Path interface contains three methods to retrieve basic information about the path representation.

The toString() method returns a String representation of the entire path. In fact, it is the only method in the Path interface to return a String.

The getNameCount() and getName() methods are often used together to retrieve the number of elements in the path and a reference to each element, respectively. These two methods do not include the root directory as part of the path.

Path path = Paths.get("/land/hippo/harry.happy");
System.out.println("The Path Name is: " + path);
for(int i=0; i<path.getNameCount(); i++)
   System.out.println("   Element " + i + " is: " + path.getName(i));

The code prints the following:

The Path Name is: /land/hippo/harry.happy
Element 0 is: land
Element 1 is: hippo
Element 2 is: harry.happy

The getNameCount() and getName() methods do not consider the root part of the path.

var p = Path.of("/");
System.out.print(p.getNameCount()); // 0
System.out.print(p.getName(0)); // IllegalArgumentException

Notice that if you try to call getName() with an invalid index, it will throw an exception at runtime.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

Creating Part of the Path

  • Path subpath(int beginIndex, int endIndex)
var p = Paths.get("/mammal/omnivore/raccoon.image");
System.out.println("Path is: " + p);
for (int i = 0; i < p.getNameCount(); i++) {
   System.out.println("   Element " + i + " is: " + p.getName(i));
}
System.out.println();
System.out.println("subpath(0,3): " + p.subpath(0, 3));
System.out.println("subpath(1,2): " + p.subpath(1, 2));
System.out.println("subpath(1,3): " + p.subpath(1, 3));
A

The Path interface includes the subpath() method to select portions of a path. It takes two parameters: an inclusive beginIndex and an exclusive endIndex.

The output of this code snippet is the following:

Path is: /mammal/omnivore/raccoon.image
   Element 0 is: mammal
   Element 1 is: omnivore
   Element 2 is: raccoon.image
 
subpath(0,3): mammal/omnivore/raccoon.image
subpath(1,2): omnivore
subpath(1,3): omnivore/raccoon.image

Like getNameCount() and getName(), subpath() is zero-indexed and does not include the root. Also like getName(), subpath() throws an exception if invalid indices are provided.

var q = p.subpath(0, 4); // IllegalArgumentException
var x = p.subpath(1, 1); // IllegalArgumentException

The first example throws an exception at runtime, since the maximum index value allowed is 3. The second example throws an exception since the start and end indexes are the same, leading to an empty path value.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Accessing Path Elements

  • Path getFileName()
    Returns the name of the file or directory denoted by this path as a Path object. The file name is the farthest element from the root in the directory hierarchy.
  • Path getParent()
    Returns the parent path, or null if this path does not have a parent.
  • Path getRoot()
    Returns the root component of this path as a Path object, or null if this path does not have a root component.
A
  • The getFileName() method returns the Path element of the current file or directory,
  • while getParent() returns the full path of the containing directory.
  • The getParent() method returns null if operated on the root path or at the top of a relative path.
  • The getRoot() method returns the root element of the file within the file system, or null if the path is a relative path.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

Resolving Paths

  • public Path resolve(String p)
  • public Path resolve(Path p)
A
  • On the exam, when you see resolve(), think concatenation.
  • The resolve() method provides overloaded versions that let you pass either a Path or String parameter.
    • public Path resolve(String p)
    • public Path resolve(Path p)
  • The object on which the resolve() method is invoked becomes the basis of the new Path object, with the input argument being appended onto the Path.
    Apply resolve() to an absolute path and a relative path:
Path path1 = Path.of("/cats/../panther");
Path path2 = Path.of("food");
System.out.println(path1.resolve(path2));

output :
/cats/../panther/food

For the exam, you should be cognizant of
* mixing absolute and relative paths with the resolve() method.
* If an absolute path is provided as input to the method, that is the value returned.
* Simply put, you cannot combine two absolute paths using resolve().

Path path3 = Path.of("/turkey/food");
System.out.println(path3.resolve("/tiger/cage")); 

output :
/tiger/cage

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

Relativizing a Path

  • Path relativize(Path other)
    Constructs a relative path between this path and a given path.

What do you think the following examples will print?

var path1 = Path.of("fish.txt");
var path2 = Path.of("friendly/birds.txt");
System.out.println(path1.relativize(path2));
System.out.println(path2.relativize(path1));

The following example demonstrates this property when run on a Windows computer:

Path path3 = Paths.get("E:\\habitat");
Path path4 = Paths.get("E:\\sanctuary\\raven\\poe.txt");
System.out.println(path3.relativize(path4));
System.out.println(path4.relativize(path3));

The relativize() method requires both paths to be absolute or relative and throws an exception if the types are mixed.

Path path1 = Paths.get("/primate/chimpanzee");
Path path2 = Paths.get("bananas.txt");
path1.relativize(path2); // IllegalArgumentException

On Windows-based systems, it also requires that if absolute paths are used, both paths must have the same root directory or drive letter. For example, the following would also throw an IllegalArgumentException on a Windows-based system:

Path path3 = Paths.get("C:\\primate\\chimpanzee");
Path path4 = Paths.get("D:\\storage\\bananas.txt");
path3.relativize(path4); // IllegalArgumentException
A

The Path interface includes a relativize() method for constructing the relative path from one Path to another, often using path symbols.

  • If both path values are relative, the relativize() method computes the paths as if they are in the same current working directory.
  • Alternatively, if both path values are absolute, the method computes the relative path from one absolute location to another, regardless of the current working directory.
  • The relativize() method requires both paths to be absolute or relative and throws an exception if the types are mixed.
  • On Windows-based systems, it also requires that if absolute paths are used, both paths must have the same root directory or drive letter
var path1 = Path.of("fish.txt");
var path2 = Path.of("friendly/birds.txt");
System.out.println(path1.relativize(path2)); // ../friendly/birds.txt
System.out.println(path2.relativize(path1)); // ../../fish.txt
Path path3 = Paths.get("E:\\habitat");
Path path4 = Paths.get("E:\\sanctuary\\raven\\poe.txt");
System.out.println(path3.relativize(path4)); // ..\sanctuary\raven\poe.txt
System.out.println(path4.relativize(path3)); // ..\..\..\habitat
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

Normalizing a Path

  • Path normalize()
    Returns a path that is this path with redundant name elements eliminated.
    The normalize() method removes any redundant elements, which includes any "." or "directory/.." occurrences.
A

The normalize() method removes any redundant elements, which includes any "." or "directory/.." occurrences.

var p1 = Path.of("./armadillo/../shells.txt");
System.out.println(p1.normalize()); // shells.txt
 
var p2 = Path.of("/cats/../panther/food");
System.out.println(p2.normalize()); // /panther/food

The normalize() method does not remove all of the path symbols, only the ones that can be reduced.

var p3 = Path.of("../../fish.txt");
System.out.println(p3.normalize()); // ../../fish.txt
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

Consider the following example:

var p1 = Paths.get("/pony/../weather.txt");
var p2 = Paths.get("/weather.txt");
System.out.println(p1.equals(p2)); // false
System.out.println(p1.normalize().equals(p2.normalize())); // true
A
  • The normalize() method also allows us to compare equivalent paths.
  • The equals() method returns true if two paths represent the same value.
  • In the first comparison, the path values are different.
  • In the second comparison, the path values have both been reduced to the same normalized value, /weather.txt.
  • This is the primary function of the normalize() method: to allow us to better compare different paths.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
Q

Retrieving the Real File System Path

  • Path toRealPath(LinkOption... options)throws IOException
    Returns the real path of an existing file.
A
  • Verify that the path exists within the file system using toRealPath().
  • This method is similar to normalize() in that it eliminates any redundant path symbols.
  • It is also similar to toAbsolutePath(), in that it will join the path with the current working directory if the path is relative.
  • Unlike those two methods, though, toRealPath() will throw an exception if the path does not exist.
  • In addition, it will follow symbolic links, with an optional LinkOption varargs parameter to ignore them.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
26
Q

Let’s say that we have a file system in which we have a symbolic link from /zebra to /horse.
What do you think the following will print, given a current working directory of /horse/schedule?

System.out.println(Paths.get("/zebra/food.txt").toRealPath());
System.out.println(Paths.get(".././food.txt").toRealPath());
A

The output of both lines is the following:

/horse/food.txt

In this example, the absolute and relative paths both resolve to the same absolute file, as the symbolic link points to a real file within the file system.

We can also use the toRealPath() method to gain access to the current working directory as a Path object.

System.out.println(Paths.get(".").toRealPath());

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
27
Q

Reviewing NIO.2 Path APIs

A

TABLE 14.6 Path APIs

  • File path as string - public String toString()
  • Single segment - public Path getName(int index)
  • Number of segments - public int getNameCount()
  • Segments in range - public Path subpath(int beginIndex, int endIndex)
  • Final segment - public Path getFileName()
  • Immediate parent - public Path getParent()
  • Top-level segment - public Path getRoot()
  • Concatenate paths - public Path resolve(String p), public Path resolve(Path p)
  • Construct path to one provided - public Path relativize(Path p)
  • Remove redundant parts of path - public Path normalize()
  • Follow symbolic links to find path on file system - public Path toRealPath()
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
28
Q

Creating, Moving, and Deleting Files and Directories
* Making Directories
* public static Path createDirectory(Path dir, FileAttribute<?>… attrs) throws IOException
* public static Path createDirectories(Path dir, FileAttribute<?>… attrs) throws IOException
* Copying Files
* public static Path copy(Path source, Path target, CopyOption… options) throws IOException

  • Copying Files with I/O Streams
    • public static long copy(InputStream in, Path target, CopyOption… options) throws IOException
    • public static long copy(Path source, OutputStream out) throws IOException
  • Moving or Renaming Paths with move()
    • public static Path move(Path source, Path target, CopyOption… options) throws IOException
  • Deleting a File with delete() and deleteIfExists()
    • public static void delete(Path path) throws IOException
    • public static boolean deleteIfExists(Path path) throws IOException
A
  • Making Directories
    Both of these methods also accept an optional list of FileAttribute<?> values to apply to the newly created directory or directories.
    • public static Path createDirectory(Path dir, FileAttribute<?>… attrs) throws IOException
      • create a directory
      • throw an exception if it already exists or if the paths leading up to the directory do not exist
    • public static Path createDirectories(Path dir, FileAttribute<?>… attrs) throws IOException
      • Creates the target directory along with any nonexistent parent directories leading up to the path
      • Do nothing If all of the directories already exist
      • Use to ensure a directory exists and create it if it does not.
  • Copying Files
    The Files class provides a method for copying files and directories within the file system.
    When directories are copied, the copy is shallow.
    A shallow copy means that the files and subdirectories within the directory are not copied.
    • public static Path copy(Path source, Path target, CopyOption… options) throws IOException
  • Copying Files with I/O Streams
    • public static long copy(InputStream in, Path target, CopyOption… options) throws IOException
    • public static long copy(Path source, OutputStream out) throws IOException
  • Moving or Renaming Paths with move()
    • public static Path move(Path source, Path target, CopyOption… options) throws IOException
  • Deleting a File with delete() and deleteIfExists()
    • public static void delete(Path path) throws IOException
    • public static boolean deleteIfExists(Path path) throws IOException
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
29
Q

The following shows how to create directories:

Files.createDirectory(Path.of("/bison/field"));
Files.createDirectories(Path.of("/bison/field/pasture/green"));
A
  • The first example creates a new directory, field, in the directory /bison, assuming /bison exists; otherwise, an exception is thrown.
  • Contrast this with the second example, which creates the directory green along with any of the following parent directories if they do not already exist, including bison, field, and pasture.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
30
Q

The following shows an example of copying a file and a directory:

Files.copy(Paths.get("/panda/bamboo.txt"), Paths.get("/panda-save/bamboo.txt")); 
Files.copy(Paths.get("/turtle"), Paths.get("/turtleCopy"));
A

When directories are copied, the copy is shallow.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
31
Q

A deep copy typically requires recursion, where a method calls itself.

public void copyPath(Path source, Path target) {
   try {
      Files.copy(source, target);
      if(Files.isDirectory(source))
         try (Stream s = Files.list(source)) {
            s.forEach(p -> copyPath(p, target.resolve(p.getFileName())));
         }
   } catch(IOException e) {
      // Handle exception
   }
}
A

The method first copies the path, whether a file or a directory. If it is a directory, only a shallow copy is performed. Next, it checks whether the path is a directory and, if it is, performs a recursive copy of each of its elements. What if the method comes across a symbolic link? Don’t worry: the JVM will not follow symbolic links when using the list() method.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
32
Q

Copying and Replacing Files

Files.copy(Paths.get("book.txt"), Paths.get("movie.txt"), StandardCopyOption.REPLACE_EXISTING);
A
  • By default, if the target already exists, the copy() method will throw an exception.
  • You can change this behavior by providing the StandardCopyOption enum value REPLACE_EXISTING to the method.
  • The following method call will overwrite the movie.txt file if it already exists:
Files.copy(Paths.get("book.txt"), Paths.get("movie.txt"), StandardCopyOption.REPLACE_EXISTING);

For the exam, you need to know that without the REPLACE_EXISTING option, this method will throw an exception if the file already exists.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
33
Q

Copying Files with I/O Streams

  • public static long copy(InputStream in, Path target, CopyOption… options) throws IOException
  • public static long copy(Path source, OutputStream out) throws IOException
A

The Files class includes two copy() methods that operate with I/O streams.

public static long copy(InputStream in, Path target, CopyOption… options) throws IOException
 public static long copy(Path source, OutputStream out) throws IOException

The first method reads the contents of an I/O stream and writes the output to a file. The second method reads the contents of a file and writes the output to an I/O stream. These methods are quite convenient if you need to quickly read/write data from/to disk.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
34
Q

The following are examples of each copy() method:

try (var is = new FileInputStream("source-data.txt")) {
   // Write I/O stream data to a file
   Files.copy(is, Paths.get("/mammals/wolf.txt"));
}
 
Files.copy(Paths.get("/fish/clown.xsl"), System.out);
A

While we used FileInputStream in the first example, the I/O stream could have been any valid I/O stream including website connections, in-memory stream resources, and so forth. The second example prints the contents of a file directly to the System.out stream.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
35
Q

Copying Files into a Directory

A

For the exam, it is important that you understand how the copy() method operates on both files and directories.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
36
Q

What do you think is the result of executing the following process?

var file = Paths.get("food.txt");
var directory = Paths.get("/enclosure");
Files.copy(file, directory);
A
  • If you said it would create a new file at /enclosure/food.txt, you’re way off.
  • It throws an exception.
  • The command tries to create a new file named /enclosure.
  • Since the path /enclosure already exists, an exception is thrown at runtime.
  • On the other hand, if the directory did not exist, the process would create a new file with the contents of food.txt, but the file would be called /enclosure.
  • Remember, we said files may not need to have extensions, and in this example, it matters.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
37
Q

The correct way to copy the file into the directory is to do the following :

A
var file = Paths.get("food.txt");
var directory = Paths.get("/enclosure/food.txt");
Files.copy(file, directory);
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
38
Q

Moving or Renaming Paths with move()

public static Path move(Path source, Path target, CopyOption… options) throws IOException
A

The Files class provides a useful method for moving or renaming files and directories.

public static Path move(Path source, Path target,  CopyOption… options) throws IOException

The following sample code uses the move() method:

Files.move(Path.of("C:\\zoo"), Path.of("C:\\zoo-new"));
 
Files.move(Path.of("C:\\user\\addresses.txt"),  Path.of("C:\\zoo-new\\addresses2.txt"));
  • The first example renames the zoo directory to a zoo-new directory, keeping all of the original contents from the source directory.
  • The second example moves the addresses.txt file from the directory user to the directory zoo-new and renames it addresses2.txt.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
39
Q

Similarities between move() and copy()

A
  • Like copy(), move() requires REPLACE_EXISTING to overwrite the target if it exists; otherwise, it will throw an exception.
  • Also like copy(), move() will not put a file in a directory if the source is a file and the target is a directory. Instead, it will create a new file with the name of the directory.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
40
Q

Performing an Atomic Move

Files.move(Path.of("mouse.txt"), Path.of("gerbil.txt"),  StandardCopyOption.ATOMIC_MOVE);
A

Another enum value that you need to know for the exam when working with the move() method is the StandardCopyOption value ATOMIC_MOVE.

Files.move(Path.of("mouse.txt"), Path.of("gerbil.txt"),  StandardCopyOption.ATOMIC_MOVE);
  • You may remember the atomic property from Chapter 13, “Concurrency,” and the principle of an atomic move is similar.
  • An atomic move is one in which a file is moved within the file system as a single indivisible operation.
  • Put another way, any process monitoring the file system never sees an incomplete or partially written file.
  • If the file system does not support this feature, an AtomicMoveNotSupportedException will be thrown.
  • Note that while ATOMIC_MOVE is available as a member of the StandardCopyOption type, it will likely throw an exception if passed to a copy() method.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
41
Q

Deleting a File with delete() and deleteIfExists()

public static void delete(Path path) throws IOException
 
public static boolean deleteIfExists(Path path) throws IOException
A

The Files class includes two methods that delete a file or empty directory within the file system.

public static void delete(Path path) throws IOException
 
public static boolean deleteIfExists(Path path) throws IOException
  • To delete a directory, it must be empty. Both of these methods throw an exception if operated on a nonempty directory.
  • In addition, if the path is a symbolic link, the symbolic link will be deleted, not the path that the symbolic link points to.
  • The methods differ on how they handle a path that does not exist.
    • The delete() method throws an exception if the path does not exist,
    • while the deleteIfExists() method returns true if the delete was successful or false otherwise.
  • Similar to createDirectories(), deleteIfExists() is useful in situations where you want to ensure that a path does not exist and delete it if it does.

Here we provide sample code that performs delete() operations:

Files.delete(Paths.get("/vulture/feathers.txt"));
Files.deleteIfExists(Paths.get("/pigeon"));
  • The first example deletes the feathers.txt file in the vulture directory, and it throws a NoSuchFileException if the file or directory does not exist.
  • The second example deletes the pigeon directory, assuming it is empty. If the pigeon directory does not exist, the second line will not throw an exception.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
42
Q

Comparing Files with isSameFile() and mismatch()

  • public static boolean isSameFile(Path path, Path path2) throws IOException
    Tests if two paths locate the same file.
  • public static long mismatch(Path path, Path path2) throws IOException
    Finds and returns the position of the first mismatched byte in the content of two files, or -1L if there is no mismatch. The position will be in the inclusive range of 0L up to the size (in bytes) of the smaller file.
A

Since a path may include path symbols and symbolic links within a file system, the equals() method can’t be relied on to know if two Path instances refer to the same file.
Luckily, there is the isSameFile() method.

  • This method takes two Path objects as input,
  • resolves all path symbols,
  • and follows symbolic links.
  • Despite the name, the method can also be used to determine whether two Path objects refer to the same directory.
  • While most uses of isSameFile() will trigger an exception if the paths do not exist, there is a special case in which it does not.
  • If the two path objects are equal in terms of equals(), the method will just return true without checking whether the file exists.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
43
Q

Assume that the file system exists, as shown in Figure 14.4, with a symbolic link from /animals/snake to /animals/cobra.

Given the structure defined in Figure 14.4, what does the following output?

System.out.println(Files.isSameFile(
   Path.of("/animals/cobra"),
   Path.of("/animals/snake")));
	 
System.out.println(Files.isSameFile(
   Path.of("/animals/monkey/ears.png"),
   Path.of("/animals/wolf/ears.png")));
A

Since snake is a symbolic link to cobra, the first example outputs true.
In the second example, the paths refer to different files, so false is printed.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
44
Q

mismatch()

A

Sometimes you want to compare the contents of the file rather than whether it is physically the same file. For example, we could have two files with text hello.

  • The mismatch() method was introduced in Java 12 to help us out here.
  • It takes two Path objects as input.
  • The method returns -1 if the files are the same;
  • otherwise, it returns the index of the first position in the file that differs.
System.out.println(Files.mismatch(
   Path.of("/animals/monkey.txt"),
   Path.of("/animals/wolf.txt")));

Suppose monkey.txt contains the name Harold and wolf.txt contains the name Howler. The previous code prints 1 in that case because the second position is different, and we use zero-based indexing in Java. Given those values, what do you think this code prints?

System.out.println(Files.mismatch(
   Path.of("/animals/wolf.txt"),
   Path.of("/animals/monkey.txt")));

The answer is the same as the previous example. The code prints 1 again. The mismatch() method is symmetric and returns the same result regardless of the order of the parameters.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
45
Q

Understanding I/O Stream Fundamentals

A
  • The contents of a file may be accessed or written via an I/O stream,
  • which is a list of data elements presented sequentially.
  • Each type of I/O stream segments data into a wave or block in a particular way.
  • On top of that, some I/O stream classes read or write larger groups of bytes or characters at a time, specifically those with the word Buffered in their name.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
46
Q

> [!NOTE]
Although the java.io API is full of I/O streams that handle characters, strings, groups of bytes, and so on, nearly all are built on top of reading or writing an individual byte or an array of bytes at a time.
Higher-level I/O streams exist for convenience as well as performance.
Although I/O streams are commonly used with file I/O, they are more generally used to handle the reading/writing of any sequential data source. For example, you might construct a Java application that submits data to a website using an output stream and reads the result via an input stream.

A

Higher-level I/O streams exist for convenience as well as performance.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
47
Q

Byte Streams vs. Character Streams

A

The java.io API defines two sets of I/O stream classes for reading and writing I/O streams:

  • byte I/O streams
    • read/write binary data (0s and 1s)
    • class names that end in InputStream or OutputStream
  • and character I/O streams.
    • read/write text data
    • class names that end in Reader or Writer

The API frequently includes similar classes for both byte and character I/O streams, such as FileInputStream and FileReader.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
48
Q

> [!NOTE]
Throughout the chapter, we refer to both InputStream and Reader as input streams, and we refer to both OutputStream and Writer as output streams.
The byte I/O streams are primarily used to work with binary data, such as an image or executable file, while character I/O streams are used to work with text files. For example, you can use a Writer class to output a String value to a file without necessarily having to worry about the underlying character encoding of the file.

A
  • The byte I/O streams are primarily used to work with binary data, such as an image or executable file,
  • while character I/O streams are used to work with text files.
  • For example, you can use a Writer class to output a String value to a file without necessarily having to worry about the underlying character encoding of the file.
  • The character encoding determines how characters are encoded and stored in bytes in an I/O stream and later read back or decoded as characters.
  • Although this may sound simple, Java supports a wide variety of character encodings, ranging from ones that may use one byte for Latin characters, UTF-8 and ASCII for example, to using two or more bytes per character, such as UTF-16.
  • For the exam, you don’t need to memorize the character encodings, but you should be familiar with the names.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
49
Q

Character Encoding in Java

Charset usAsciiCharset = Charset.forName("US-ASCII");
Charset utf8Charset = Charset.forName("UTF-8");
Charset utf16Charset = Charset.forName("UTF-16");
A

In Java, the character encoding can be specified using the Charset class by passing a name value to the static Charset.forName() method, such as in the following examples:

Charset usAsciiCharset = Charset.forName("US-ASCII");
Charset utf8Charset = Charset.forName("UTF-8");
Charset utf16Charset = Charset.forName("UTF-16");

Java supports numerous character encodings, each specified by a different standard name value.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
50
Q

Input vs. Output Streams

A
  • Most InputStream classes have a corresponding OutputStream class, and vice versa.
  • For example, the FileOutputStream class writes data that can be read by a FileInputStream.
  • There are exceptions to this rule. For the exam, you should know that
    • PrintWriter has no accompanying PrintReader class.
    • Likewise, the PrintStream is an OutputStream that has no corresponding InputStream class. It also does not have Output in its name.
51
Q

Low-Level vs. High-Level Streams

A
  • A low-level stream connects directly with the source of the data, such as a file, an array, or a String.
  • Low-level I/O streams process the raw data or resource and are accessed in a direct and unfiltered manner.
  • For example, a FileInputStream is a class that reads file data one byte at a time.
  • Alternatively, a high-level stream is built on top of another I/O stream using wrapping.
  • Wrapping is the process by which an instance is passed to the constructor of another class, and operations on the resulting instance are filtered and applied to the original instance.
  • For example, take a look at the FileReader and BufferedReader objects in the following sample code:
    ~~~
    try (var br = new BufferedReader(new FileReader(“zoo-data.txt”))) {
    System.out.println(br.readLine());
    }
    ~~~
  • In this example, FileReader is the low-level I/O stream, whereas BufferedReader is the high-level I/O stream that takes a FileReader as input.
  • Many operations on the high-level I/O stream pass through as operations to the underlying low-level I/O stream, such as read() or close().
  • Other operations override or add new functionality to the low-level I/O stream methods.
  • The high-level I/O stream may add new methods, such as readLine(), as well as performance enhancements for reading and filtering the low-level data.
  • High-level I/O streams can also take other high-level I/O streams as input. For example, although the following code might seem a little odd at first, the style of wrapping an I/O stream is quite common in practice:
    ~~~
    try (var ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(“zoo-data.txt”)))) {
    System.out.print(ois.readObject());
    }
    ~~~

In this example, the low-level FileInputStream interacts directly with the file, which is wrapped by a high-level BufferedInputStream to improve performance. Finally, the entire object is wrapped by another high-level ObjectInputStream, which allows us to interpret the data as a Java object.

For the exam, the only low-level stream classes you need to be familiar with are the ones that operate on files. The rest of the nonabstract stream classes are all high-level streams.

52
Q

Stream Base Classes

A

The java.io library defines four abstract classes that are the parents of all I/O stream classes defined within the API:

  1. InputStream,
  2. OutputStream,
  3. Reader,
  4. and Writer.

The constructors of high-level I/O streams often take a reference to the abstract class.
For example, BufferedWriter takes a Writer object as input, which allows it to take any subclass of Writer.

53
Q

One common area where the exam likes to play tricks on you is mixing and matching I/O stream classes that are not compatible with each other. For example, take a look at each of the following examples and see whether you can determine why they do not compile:

new BufferedInputStream(new FileReader("z.txt")); // DOES NOT COMPILE
new BufferedWriter(new FileOutputStream("z.txt")); // DOES NOT COMPILE
new ObjectInputStream(new FileOutputStream("z.txt")); // DOES NOT COMPILE
new BufferedInputStream(new InputStream()); // DOES NOT COMPILE
A
  • The first two examples do not compile because they mix Reader/Writer classes with InputStream/OutputStream classes, respectively.
  • The third example does not compile because we are mixing an OutputStream with an InputStream. Although it is possible to read data from an InputStream and write it to an OutputStream, wrapping the I/O stream is not the way to do so. As you see later in this chapter, the data must be copied over.
  • Finally, the last example does not compile because InputStream is an abstract class, and therefore you cannot create an instance of it.
54
Q

Reading and Writing Files

public abstract int read() throws IOException

public int .read(byte[] b, int off, int len) throws IOException

public abstract void write(int b) throws IOException

public void write(byte[] b, int off, int len) throws IOException

public void flush() throws IOException

A
  • Both InputStream and Reader declare a read() method to read byte data from an I/O stream.
  • Likewise, OutputStream and Writer both define a write() method to write a byte to the stream:

In both examples, -1 is used to indicate the end of the stream.

void copyStream(InputStream in, OutputStream out) throws IOException {
    int b;
    while ((b = in.read()) != -1) {
        out.write(b);
    }
}

void copyStream(Reader in, Writer out) throws IOException {
    int b;
    while ((b = in.read()) != -1) {
        out.write(b);
    }
}

Why do the methods use int instead of byte?
* the byte data type has a range of 256 characters.
* They needed an extra value to indicate the end of an I/O stream.
* The authors of Java decided to use a larger data type, int, so that special values like -1 would indicate the end of an I/O stream.
* The output stream classes use int as well, to be consistent with the input stream classes.
* Reading and writing one byte at a time isn’t a particularly efficient way of doing this.
* Luckily, there are overloaded methods for reading and writing multiple bytes at a time.
* The offset and length values are applied to the array itself.
* For example, an offset of 3 and length of 5 indicates that the stream should read up to five bytes/characters of data and put them into the array starting with position 3.

55
Q

Let’s look at an example:

10: void copyStream(InputStream in, OutputStream out) throws IOException {
11:     int batchSize = 1024;
12:     var buffer = new byte[batchSize];
13:     int lengthRead;
14:     while ((lengthRead = in.read(buffer, 0, batchSize))> 0) {
15:         out.write(buffer, 0, lengthRead);
16:         out.flush();
17:     }
A
  • Instead of reading the data one byte at a time, we read and write up to 1024 bytes at a time on line 14.
  • The return value lengthRead is critical for determining whether we are at the end of the stream and knowing how many bytes we should write into our output stream.
  • Unless our file happens to be a multiple of 1024 bytes, the last iteration of the while loop will write some value less than 1024 bytes.
  • For example, if the buffer size is 1,024 bytes and the file size is 1,054 bytes, the last read will be only 30 bytes.
  • If we ignored this return value and instead wrote 1,024 bytes, 994 bytes from the previous loop would be written to the end of the file.
  • We also added a flush() method on line 16 to reduce the amount of data lost if the application terminates unexpectedly.
  • When data is written to an output stream, the underlying operating system does not guarantee that the data will make it to the file system immediately.
  • The flush() method requests that all accumulated data be written immediately to disk.
  • It is not without cost, though. Each time it is used, it may cause a noticeable delay in the application, especially for large files. Unless the data that you are writing is extremely critical, the flush() method should be used only intermittently. For example, it should not necessarily be called after every write, as it is in this example.
56
Q

Let’s try again using high-level streams.

26: void copyTextFile(File src, File dest) throws IOException {
27:    try (var reader = new BufferedReader(new FileReader(src));
28:       var writer = new BufferedWriter(new FileWriter(dest))) {
29:       String line = null;
30:       while ((line = reader.readLine()) != null) {
31:          writer.write(line);
32:          writer.newLine();
33:       } } }
A
  • The key is to choose the most useful high-level classes.
  • In this case, we are dealing with a File, so we want to use a FileReader and FileWriter. Both classes have constructors that can take either a String representing the location or a File directly.
  • If the source file does not exist, a FileNotFoundException, which inherits IOException, will be thrown.
  • If the destination file already exists, this implementation will overwrite it. We can pass an optional boolean second parameter to FileWriter for an append flag if we want to change this behavior.
    public FileWriter(File file, boolean append) throws IOException
  • We also chose to use a BufferedReader and BufferedWriter so we can read a whole line at a time. This gives us the benefits of reading batches of characters on line 30 without having to write custom logic.
  • Line 31 writes out the whole line of data at once. Since reading a line strips the line breaks, we add those back on line 32.
  • Lines 27 and 28 demonstrate chaining constructors.
  • The try-with-resources constructor takes care of closing all the objects in the chain.
  • Now imagine that we wanted byte data instead of characters. We would need to choose different high-level classes:
    • BufferedInputStream,
    • BufferedOutputStream,
    • FileInputStream,
    • and FileOuputStream.
  • We would call readAllBytes() instead of readLine() and store the result in a byte[] instead of a String.
  • Finally, we wouldn’t need to handle new lines since the data is binary.
57
Q
  • We can do a little better than BufferedOutputStream and BufferedWriter by using a PrintStream and PrintWriter.
  • These classes contain four key methods.
    • The print() and println() methods print data with and without a new line, respectively.
    • There are also the format() and printf() methods, which we describe in the section on user interactions.
void copyTextFile(File src, File dest) throws IOException {
   try (var reader = new BufferedReader(new FileReader(src));
      var writer = new PrintWriter(new FileWriter(dest))) {
      String line = null;
      while ((line = reader.readLine()) != null)
          writer.println(line);
      }
}
A
  • While we used a String, there are numerous overloaded versions of println(), which take everything from primitives and String values to objects.
  • Under the covers, these methods often just perform String.valueOf().
    The print stream classes have the distinction of being the only I/O stream classes we cover that do not have corresponding input stream classes.
    And unlike other OutputStream classes, PrintStream does not have Output in its name.
58
Q

> [!NOTE]
It may surprise you that you’ve been regularly using a PrintStream throughout this book.
Both System.out and System.err are PrintStream objects. Likewise, System.in, often useful for reading user input, is an InputStream.
Unlike the majority of the other I/O streams we’ve covered, the methods in the print stream classes do not throw any checked exceptions. If they did, you would be required to catch a checked exception any time you called System.out.print()!
The line separator is \n or \r\n, depending on your operating system.
The println() method takes care of this for you. If you need to get the character directly, either of the following will return it for you:

System.getProperty("line.separator");
System.lineSeparator();
A

PrintStream

59
Q

Decoding I/O Class Names

A
60
Q

TABLE 14.7 The java.io abstract stream base classes

A

TABLE 14.7 The java.io abstract stream base classes

  • InputStream - Abstract class for all input byte streams
  • OutputStream - Abstract class for all output byte streams
  • Reader - Abstract class for all input character streams
  • Writer - Abstract class for all output character streams
61
Q

TABLE 14.8 The java.io concrete I/O stream classes

A
  • FileInputStream - Low - Reads file data as bytes
  • FileOutputStream - Low - Writes file data as bytes
  • FileReader - Low - Reads file data as characters
  • FileWriter - Low - Writes file data as characters
  • BufferedInputStream- High- Reads byte data from existing InputStream in buffered manner, which improves efficiency and performance
  • BufferedOutputStream- High- Writes byte data to existing OutputStream in buffered manner, which improves efficiency and performance
  • BufferedReader- High- Reads character data from existing Reader in buffered manner, which improves efficiency and performance
  • BufferedWriter- High- Writes character data to existing Writer in buffered manner, which improves efficiency and performance
  • ObjectInputStream- High- Deserializes primitive Java data types and graphs of Java objects from existing bInputStream
  • ObjectOutputStream- High- Serializes primitive Java data types and graphs of Java objects to existing OutputStream
  • PrintStream- High- Writes formatted representations of Java objects to binary stream
  • PrintWriter- High- Writes formatted representations of Java objects to character stream
62
Q

Enhancing with Files

  • public static String readString(Path path) throws IOException
  • public static byte[] readAllBytes(Path path) throws IOException
  • public static List<String> readAllLines(Path path) throws IOException
  • public static Stream<String> lines(Path path) throws IOException
  • public static Path writeString(Path path, CharSequence csq, OpenOption... options) throws IOException
  • public static Path write(Path path, byte[] bytes, OpenOption... options) throws IOException
  • public static Path write(Path path, Iterable<? extends CharSequence> lines, OpenOption... options) throws IOException
A

The NIO.2 APIs provide even easier ways to read and write a file using the Files class.
Let’s start by looking at three ways of copying a file by reading in the data and writing it back:

private void copyPathAsString(Path input, Path output) throws IOException {
   String string = Files.readString(input);
   Files.writeString(output, string);
}
private void copyPathAsBytes(Path input, Path output) throws IOException {
    byte[] bytes = Files.readAllBytes(input);
    Files.write(output, bytes);
}
private void copyPathAsLines(Path input, Path output) throws IOException {
   List lines = Files.readAllLines(input);
   Files.write(output, lines);
}

That’s pretty concise! You can read a Path as a String, a byte array, or a List. Be aware that the entire file is read at once for all three of these, thereby storing all of the contents of the file in memory at the same time. If the file is significantly large, you may trigger an OutOfMemoryError when trying to load all of it into memory. Luckily, there is an alternative. This time, we print out the file as we read it.

private void readLazily(Path path) throws IOException {
   try (Stream s = Files.lines(path)) {
      s.forEach(System.out::println);
   }
}

Now the contents of the file are read and processed lazily, which means that only a small portion of the file is stored in memory at any given time. Taking things one step further, we can leverage other stream methods for a more powerful example.

try (var s = Files.lines(path)) {
   s.filter(f -> f.startsWith("WARN:"))
      .map(f -> f.substring(5))
      .forEach(System.out::println);
}

This sample code searches a log for lines that start with WARN:, outputting the text that follows. Assuming that the input file sharks.log is as follows:

INFO:Server starting
DEBUG:Processes available = 10
WARN:No database could be detected
DEBUG:Processes available reset to 0
WARN:Performing manual recovery
INFO:Server successfully started

Then the sample output would be the following:

No database could be detected
Performing manual recovery

As you can see, we have the ability to manipulate files in complex ways, often with only a few short expressions.

63
Q

Files.readAllLines() vs. Files.lines()

  • public static List<String> readAllLines(Path path) throws IOException
  • public static Stream<String> lines(Path path) throws IOException
A

For the exam, you need to know the difference between readAllLines() and lines(). Both of these examples compile and run:

      Files.readAllLines(Paths.get("birds.txt")).forEach(System.out::println);
       Files.lines(Paths.get("birds.txt")).forEach(System.out::println);
  • The first line reads the entire file into memory and performs a print operation on the result,
  • while the second line lazily processes each line and prints it as it is read.
  • The advantage of the second code snippet is that it does not require the entire file to be stored in memory at any time.

You should also be aware of when they are mixing incompatible types on the exam. Do you see why the following does not compile?

      Files.readAllLines(Paths.get("birds.txt"))
          .filter(s -> s.length()> 2)
          .forEach(System.out::println);

The readAllLines() method returns a List, not a Stream, so the filter() method is not available.

64
Q

Combining with newBufferedReader() and newBufferedWriter()

public static BufferedReader newBufferedReader(Path path) throws IOException

public static BufferedWriter newBufferedWriter(Path path, OpenOption... options) throws IOException

A

Sometimes you need to mix I/O streams and NIO.2. Conveniently, Files includes two convenience methods for getting I/O streams.

private void copyPath(Path input, Path output) throws IOException {
   try (var reader = Files.newBufferedReader(input);
        var writer = Files.newBufferedWriter(output)) { 
      String line = null;
      while ((line = reader.readLine()) != null)
         writer.write(line);
         writer.newLine();
      } } }

You can wrap I/O stream constructors to produce the same effect, although it’s a lot easier to use the factory method. The first method, newBufferedReader(), reads the file specified at the Path location using a BufferedReader object.

65
Q

Reviewing Common Read and Write Methods

A

TABLE 14.9 Common I/O read and write methods
TABLE 14.10 Common Files NIO.2 read and write methods

66
Q

TABLE 14.9 Common I/O read and write methods

A
  • All input streams - public int read() - Reads single byte or returns -1 if no bytes available.
  • InputStream - public int read(byte[] b) - Reads values into buffer. Returns number of bytes or characters read.
  • Reader - public int read(char[] c) - Reads values into buffer. Returns number of bytes or characters read.
  • InputStream - public int read(byte[] b, int offset, int length) - Reads up to length values into buffer starting from position offset. Returns number of bytes or characters read.
  • Reader - public int read(char[] c, int offset, int length) - Reads up to length values into buffer starting from position offset. Returns number of bytes or characters read.
  • All output streams - public void write(int b) - Writes single byte.
  • OutputStream - public void write(byte[] b) - Writes array of values into stream.
  • Writer - public void write(char[] c) -
  • OutputStream - public void write(byte[] b, int offset, int length) - Writes length values from array into stream, starting with offset index.
  • Writer - public void write(char[] c, int offset, int length)
  • BufferedInputStream - public byte[] readAllBytes() - Reads data in bytes.
  • BufferedReader - public String readLine() - Reads line of data.
  • BufferedWriter - public void write(String line) - Writes line of data.
  • BufferedWriter - public void newLine() - Writes new line.
  • All output streams - public void flush() - Flushes buffered data through stream.
  • All streams - public void close() - Closes stream and releases resources.
67
Q

TABLE 14.10 Common Files NIO.2 read and write methods

A
  • public static byte[] readAllBytes() - Reads all data as bytes
  • public static String readString() - Reads all data into String
  • public static List<String> readAllLines() - Read all data into List</String>
  • public static Stream<String> lines() - Lazily reads data</String>
  • public static void write(Path path, byte[] bytes) - Writes array of bytes
  • public static void writeString(Path path, String string) - Writes String
  • public static void write(Path path, List<String> list) - Writes list of lines (technically, any Iterable of CharSequence, but you don't need to know that for the exam)</String>
68
Q

Serializing Data

A
  • Serialization is the process of converting an in-memory object to a byte stream.
  • deserialization is the process of converting from a byte stream into an object.
  • Serialization often involves writing an object to a stored or transmittable format, while deserialization is the reciprocal process.
  • Java provides built-in mechanisms for serializing and deserializing I/O streams of objects directly to and from disk, respectively.
69
Q

Applying the Serializable Interface

import java.io.Serializable;
public class Gorilla implements Serializable { 
   private static final long serialVersionUID = 1L;
   private String name;
   private int age;
   private Boolean friendly;
   private transient String favoriteFood;
 
   // Constructors/Getters/Setters/toString() omitted
}
A
  1. import java.io.Serializable;
  2. Implement Serializable Interface, (Serializable is marker interface)
    ex: public class Gorilla implements Serializable {
  3. Declare a static serialVersionUID variable
    ex: private static final long serialVersionUID = 1L;
  4. Marking Data transient
    ex: private transient String favoriteFood;
70
Q

Maintaining a serialVersionUID

private static final long serialVersionUID = 1L;

A
  • It’s a good practice to declare a static serialVersionUID variable in every class that implements Serializable.
  • The version is stored with each object as part of serialization.
  • Then, every time the class structure changes, this value is updated or incremented.
  • Perhaps our Gorilla class receives a new instance member Double banana, or maybe the age field is renamed.
  • The idea is a class could have been serialized with an older version of the class and deserialized with a newer version of the class.
  • The serialVersionUID helps inform the JVM that the stored data may not match the new class definition.
  • If an older version of the class is encountered during deserialization, a java.io.InvalidClassException may be thrown.
  • Alternatively, some APIs support converting data between versions.
71
Q

Marking Data transient

A
  • The transient modifier can be used for sensitive data of the class, like a password.
  • There are other objects it does not make sense to serialize, like the state of an in-memory Thread.
  • If the object is part of a serializable object, we just mark it transient to ignore these select instance members.
  • What happens to data marked transient on deserialization?
  • It reverts to its default Java values, such as 0.0 for double, or null for an object.
  • You see examples of this shortly when we present the object stream classes.
72
Q

Ensuring That a Class Is Serializable

A

Since Serializable is a marker interface, you might think there are no rules to using it. Not quite!

Any process attempting to serialize an object will throw a NotSerializableException if the class does not implement the Serializable interface properly.

2 Rules :
* The class must be marked Serializable.
* Every instance member of the class must be
* serializable,
* marked transient,
* or have a null value at the time of serialization.
* Be careful with the second rule. For a class to be serializable, we must apply the second rule recursively.

73
Q

How to Make a Class Serializable

Do you see why the following Cat class is not serializable?

public class Cat implements Serializable {
   private Tail tail = new Tail();
} 
 
public class Tail implements Serializable {
   private Fur fur = new Fur();
}
 
public class Fur {}
A

Cat contains an instance of Tail, and both of those classes are marked Serializable, so no problems there. Unfortunately, Tail contains an instance of Fur that is not marked Serializable.

Either of the following changes fixes the problem and allows Cat to be serialized:

public class Tail implements Serializable {
   private transient Fur fur = new Fur();
}
 
public class Fur implements Serializable {}

We could also make our tail or fur instance members null, although this would make Cat serializable only for particular instances, rather than all instances.

74
Q

Serializing Records

Do you think this record is serializable?
record Record(String name) {}

A
  • It is not serializable because it does not implement Serializable.
  • A record follows the same rules as other types of classes with respect to whether it can be serialized.
  • Therefore, this one can be:
    ` record Record(String name) implements Serializable {}`
75
Q

Storing Data with ObjectOutputStream and ObjectInputStream

// ObjectInputStream - deserialize
public Object readObject() throws IOException, ClassNotFoundException
 
// ObjectOutputStream - serialize
public void writeObject(Object obj) throws IOException
A
  • The ObjectInputStream class is used to deserialize an object,
  • while the ObjectOutputStream is used to serialize an object.
  • They are high-level streams that operate on existing I/O streams.
  • While both of these classes contain a number of methods for built-in data types like primitives, the two methods you need to know for the exam are the ones related to working with objects.
// ObjectInputStream
public Object readObject() throws IOException, ClassNotFoundException
 
// ObjectOutputStream
public void writeObject(Object obj) throws IOException

Note the parameters, return types, and exceptions thrown.

76
Q

A sample method that serializes a List of Gorilla objects to a file:

void saveToFile(List<Gorilla> gorillas, File dataFile) throws IOException {
   try (var out = new ObjectOutputStream(
           new BufferedOutputStream(
              new FileOutputStream(dataFile)))) {
      for (Gorilla gorilla : gorillas)
         out.writeObject(gorilla);
   }
}
A

Pretty easy, right?
Notice that we
* start with a file stream,
* wrap it in a buffered I/O stream to improve performance,
* and then wrap that with an object stream.
* Serializing the data is as simple as passing it to writeObject().

77
Q

Once the data is stored in a file, we can deserialize it by using the following method:

List<Gorilla> readFromFile(File dataFile) throws IOException, ClassNotFoundException {
   var gorillas = new ArrayList<Gorilla>();
   try (var in = new ObjectInputStream(
           new BufferedInputStream(
              new FileInputStream(dataFile)))) {
      while (true) {
         var object = in.readObject();
         if (object instanceof Gorilla g)
            gorillas.add(g);
      }
   } catch (EOFException e) {
      // File end reached
   }
   return gorillas;
}
A

Ah, not as simple as our save method, was it?
* When calling readObject(), null and -1 do not have any special meaning, as someone might have serialized objects with those values.
* Unlike our earlier techniques for reading methods from an input stream,
* we need to use an infinite loop to process the data,
* which throws an EOFException when the end of the I/O stream is reached.

  • If your program happens to know the number of objects in the I/O stream, you can call readObject() a fixed number of times, rather than using an infinite loop.
  • Since the return type of readObject() is Object, we need to check the type before obtaining access to our Gorilla properties.
  • Notice that readObject() declares a checked ClassNotFoundException since the class might not be available on deserialization.
78
Q

The following code snippet shows how to call the serialization methods:

var gorillas = new ArrayList<Gorilla>();
gorillas.add(new Gorilla("Grodd", 5, false));
gorillas.add(new Gorilla("Ishmael", 8, true));
File dataFile = new File("gorilla.data");
 
saveToFile(gorillas, dataFile);
var gorillasFromDisk = readFromFile(dataFile);
System.out.print(gorillasFromDisk);
A

Assuming that the toString() method was properly overridden in the Gorilla class, this prints the following at runtime:

[[name=Grodd, age=5, friendly=false],
 [name=Ishmael, age=8, friendly=true]]
79
Q

> [!NOTE]
ObjectInputStream inherits an available() method from InputStream that you might think can be used to check for the end of the I/O stream rather than throwing an EOFException.
Unfortunately, this only tells you the number of blocks that can be read without blocking another thread. In other words, it can return 0 even if there are more bytes to be read.

A
80
Q

Understanding the Deserialization Creation Process

A
  • For the exam, you need to understand how a deserialized object is created.
  • When you deserialize an object, the constructor of the serialized class, along with any instance initializers, is not called when the object is created.
  • Java will call the no-arg constructor of the first non-serializable parent class it can find in the class hierarchy.
  • In our Gorilla example, this would just be the no-arg constructor of Object.
  • As we stated earlier, any static or transient fields are ignored.
  • Values that are not provided will be given their default Java value, such as null for String, or 0 for int values.
81
Q

Let’s take a look at a new Chimpanzee class. This time we do list the constructors to illustrate that none of them is used on deserialization.

import java.io.Serializable;
public class Chimpanzee implements Serializable {
   private static final long serialVersionUID = 2L;
   private transient String name;
   private transient int age = 10;
   private static char type = 'C';
   { this.age = 14; }
 
   public Chimpanzee() {
      this.name = "Unknown";
      this.age = 12;
      this.type = 'Q';
   }
 
   public Chimpanzee(String name, int age, char type) {
      this.name = name;
      this.age = age;
      this.type = type;
   }
 
   // Getters/Setters/toString() omitted
}

Assuming we rewrite our previous serialization and deserialization methods to process a Chimpanzee object instead of a Gorilla object, what do you think the following prints?

var chimpanzees = new ArrayList<Chimpanzee>();
chimpanzees.add(new Chimpanzee("Ham", 2, 'A'));
chimpanzees.add(new Chimpanzee("Enos", 4, 'B'));
File dataFile = new File("chimpanzee.data");
 
saveToFile(chimpanzees, dataFile);
var chimpanzeesFromDisk = readFromFile(dataFile);
System.out.println(chimpanzeesFromDisk);
A

Think about it. Go on, we’ll wait.
Ready for the answer?
* Well, for starters, none of the instance members are serialized to a file.
* The name and age variables are both marked transient, while the type variable is static.
* We purposely accessed the type variable using this to see whether you were paying attention.
* Upon deserialization, none of the constructors in Chimpanzee is called.
* Even the no-arg constructor that sets the values [name=Unknown,age=12,type=Q] is ignored.
* The instance initializer that sets age to 14 is also not executed.
* In this case, the name variable is initialized to null since that’s the default value for String in Java.
* Likewise, the age variable is initialized to 0.
* The program prints the following, assuming the toString() method is implemented:

[[name=null,age=0,type=B],
 [name=null,age=0,type=B]]
  • What about the type variable?
  • Since it’s static, it will display whatever value was set last.
  • If the data is serialized and deserialized within the same execution, it will display B, since that was the last Chimpanzee we created.
  • On the other hand, if the program performs the deserialization and print on startup, it will print C, since that is the value the class is initialized with.
82
Q

For the exam, make sure you understand that the constructor and any instance initializations defined in the serialized class are ignored during the deserialization process.

Java only calls the constructor of the first non-serializable parent class in the class hierarchy.
Finally, let’s add a subclass:

public class BabyChimpanzee extends Chimpanzee {
   private static final long serialVersionUID = 3L;
 
   private String mother = "Mom";
 
   public BabyChimpanzee() { super(); }
 
   public BabyChimpanzee(String name, char type) {
      super(name, 0, type);
   }
   // Getters/Setters/toString() omitted
}
A
  • Notice that this subclass is serializable because the superclass has implemented Serializable.
  • We now have an additional instance variable.
  • The code to serialize and deserialize remains the same.
  • We can even still cast to Chimpanzee because this is a subclass.
83
Q

Interacting with Users

A

ans

84
Q

Printing Data to the User

// PrintStream
try (var in = new FileInputStream("zoo.txt")) {
   System.out.println("Found file!");
} catch (FileNotFoundException e) {
   System.err.println("File not found!");
}
A
  • Java includes two PrintStream instances for providing information to the user:
    • System.out
    • and System.err.
  • While System.out should be old hat to you, System.err might be new to you.
  • The syntax for calling and using System.err is the same as System.out but is used to report errors to the user in a separate I/O stream from the regular output information.
try (var in = new FileInputStream("zoo.txt")) {
   System.out.println("Found file!");
} catch (FileNotFoundException e) {
   System.err.println("File not found!");
}
  • How do they differ in practice?
  • In part, that depends on what is executing the program.
  • For example, if you are running from a command prompt, they will likely print text in the same format.
  • On the other hand, if you are working in an integrated development environment (IDE), they might print the System.err text in a different color.
  • Finally, if the code is being run on a server, the System.err stream might write to a different log file.
85
Q

Real World Scenario
Using Logging APIs

A

While System.out and System.err are incredibly useful for debugging stand-alone or simple applications, they are rarely used in professional software development. Most applications rely on a logging service or API.
While many logging APIs are available, they tend to share a number of similar attributes. First you create a static logging object in each class. Then you log a message with an appropriate logging level: debug(), info(), warn(), or error(). The debug() and info() methods are useful as they allow developers to log things that aren’t errors but may be useful.

86
Q

Reading Input as an I/O Stream

var reader = new BufferedReader(new InputStreamReader(System.in));
String userInput = reader.readLine();
System.out.println("You entered: " + userInput);
A
  • The System.in returns an InputStream and is used to retrieve text input from the user.
  • It is commonly wrapped with a BufferedReader via an InputStreamReader to use the readLine()method.
var reader = new BufferedReader(new InputStreamReader(System.in));
String userInput = reader.readLine();
System.out.println("You entered: " + userInput);

When executed, this application first fetches text from the user until the user presses the Enter key.
It then outputs the text the user entered to the screen.

87
Q

Closing System Streams

A
  • You might have noticed that we never created or closed System.out, System.err, and System.in when we used them.
  • In fact, these are the only I/O streams in the entire chapter that we did not use a try-with-resources block on!
  • Because these are static objects, the System streams are shared by the entire application.
  • The JVM creates and opens them for us. They can be used in a try-with-resources statement or by calling close(), although closing them is not recommended.
  • Closing the System streams makes them permanently unavailable for all threads in the remainder of the program.

What do you think the following code snippet prints?

try (var out = System.out) {}
System.out.println("Hello");  

Nothing. It prints nothing. The methods of PrintStream do not throw any checked exceptions and rely on the checkError() to report errors, so they fail silently.

What about this example?

try (var err = System.err) {}
System.err.println("Hello");  

This one also prints nothing. Like System.out, System.err is a PrintStream. Even if it did throw an exception, we’d have a hard time seeing it since our I/O stream for reporting errors is closed! Closing System.err is a particularly bad idea, since the stack traces from all exceptions will be hidden.

Finally, what do you think this code snippet does?

var reader = new BufferedReader(new InputStreamReader(System.in));
try (reader) {}
String data = reader.readLine(); // IOException

It prints an exception at runtime. Unlike the PrintStream class, most InputStream implementations will throw an exception if you try to operate on a closed I/O stream.

88
Q

java.io.Console class

  • Create Console instance
  • Obtaining Underlying I/O Streams
    • public Reader reader()
    • public PrintWriter writer()
  • Formatting Console Data
    ~~~
    // PrintStream
    public PrintStream format(String format, Object… args)
    public PrintStream format(Locale loc, String format, Object… args)

// PrintWriter
public PrintWriter format(String format, Object… args)
public PrintWriter format(Locale loc, String format, Object… args)
~~~
* Reading Console Data
* public String readLine()
* public String readLine(String fmt, Object… args)
* public char[] readPassword()
* public char[] readPassword(String fmt, Object… args)

  • The java.io.Console class is specifically designed to handle user interactions.
  • The Console class is a singleton because it is accessible only from a factory method and only one instance of it is created by the JVM.
  • Retrive Console instance
    Console console = System.console();
  • Check if Cosole instance available
    if (console != null) {
  • retrieves a line of input from the user
    String userInput = console.readLine();
  • prints the result
    console.writer().println("You entered: " + userInput);
A
  • The java.io.Console class is specifically designed to handle user interactions.
  • After all, System.in and System.out are just raw streams,
  • whereas Console is a class with numerous methods centered around user input.
  • The Console class is a singleton because it is accessible only from a factory method and only one instance of it is created by the JVM.
    Console console = System.console();
  • For example, if you come across code on the exam such as the following, it does not compile, since the constructors are all private:
Console c = new Console(); // DOES NOT COMPILE

The following snippet shows how to obtain a Console and use it to retrieve user input:

Console console = System.console();
if (console != null) {
    String userInput = console.readLine();
    console.writer().println("You entered: " + userInput);
} else {
    System.err.println("Console not available");
}
  • This program first retrieves an instance of the Console and verifies that it is available, outputting a message to System.err if it is not.
  • If it is available, the program retrieves a line of input from the user and prints the result. As you might have noticed, this example is equivalent to our earlier example of reading user input with System.in and System.out.
89
Q

Acquiring Input with Console
* The java.io.Console class is specifically designed to handle user interactions.
* The Console class is a singleton because it is accessible only from a factory method and only one instance of it is created by the JVM.
* Retrive Console instance
Console console = System.console();
* Check if Cosole instance available
if (console != null) {
* retrieves a line of input from the user
String userInput = console.readLine();
* prints the result
console.writer().println("You entered: " + userInput);

A
  • The java.io.Console class is specifically designed to handle user interactions.
  • After all, System.in and System.out are just raw streams,
  • whereas Console is a class with numerous methods centered around user input.
  • The Console class is a singleton because it is accessible only from a factory method and only one instance of it is created by the JVM.
    Console console = System.console();
  • For example, if you come across code on the exam such as the following, it does not compile, since the constructors are all private:
Console c = new Console(); // DOES NOT COMPILE

The following snippet shows how to obtain a Console and use it to retrieve user input:

Console console = System.console();
if (console != null) {
    String userInput = console.readLine();
    console.writer().println("You entered: " + userInput);
} else {
    System.err.println("Console not available");
}
  • This program first retrieves an instance of the Console and verifies that it is available, outputting a message to System.err if it is not.
  • If it is available, the program retrieves a line of input from the user and prints the result. As you might have noticed, this example is equivalent to our earlier example of reading user input with System.in and System.out.
90
Q

> [!NOTE]
The Console object may not be available, depending on where the code is being called.
If it is not available, System.console() returns null.
It is imperative that you check for a null value before attempting to use a Console object!

A
Console console = System.console();
if (console != null) {
    String userInput = console.readLine();
    console.writer().println("You entered: " + userInput);
} else {
    System.err.println("Console not available");
}
91
Q

Obtaining Underlying I/O Streams

public Reader reader()
public PrintWriter writer()

A

The Console class includes access to two streams for reading and writing data.

public Reader reader()
public PrintWriter writer()

Accessing these classes is analogous to calling System.in and System.out directly, although they use character streams rather than byte streams. In this manner, they are more appropriate for handling text data.

92
Q

Formatting Console Data

// PrintStream
public PrintStream format(String format, Object… args)
public PrintStream format(Locale loc, String format, Object… args)

// PrintWriter
public PrintWriter format(String format, Object… args)
public PrintWriter format(Locale loc, String format, Object… args)
A

In Chapter 4, you learned about the format() method on String; and in Chapter 11, “Exceptions and Localization,” you worked with formatting using locales. Conveniently, each print stream class includes a format() method, which includes an overloaded version that takes a Locale to combine both of these:

// PrintStream
public PrintStream format(String format, Object… args)
public PrintStream format(Locale loc, String format, Object… args)

// PrintWriter
public PrintWriter format(String format, Object… args)
public PrintWriter format(Locale loc, String format, Object… args)
93
Q

> [!NOTE]
For convenience (as well as to make C developers feel more at home), Java includes printf() methods, which function identically to the format() methods.
The only thing you need to know about these methods is that they are interchangeable with format().

A
94
Q

Let’s take a look at using multiple methods to print information for the user:

Console console = System.console();
if (console == null) {
    throw new RuntimeException("Console not available");
} else {
    console.writer().println("Welcome to Our Zoo!");
    console.format("It has %d animals and employs %d people", 391, 25);
    console.writer().println();
    console.printf("The zoo spans %5.1f acres", 128.91);
}
A

Assuming the Console is available at runtime, it prints the following:

Welcome to Our Zoo!
It has 391 animals and employs 25 people
The zoo spans 128.9 acres.
95
Q

Using Console with a Locale

A

Unlike the print stream classes, Console does not include an overloaded format() method that takes a Locale instance. Instead, Console relies on the system locale. Of course, you could always use a specific Locale by retrieving the Writer object and passing your own Locale instance, such as in the following example:

Console console = System.console();
console.writer().format(new Locale("fr", "CA"), "Hello World");
96
Q

Reading Console Data

public String readLine()
public String readLine(String fmt, Object… args)
public char[] readPassword()
public char[] readPassword(String fmt, Object… args)
A

The Console class includes four methods for retrieving regular text data from the user.

public String readLine()
public String readLine(String fmt, Object… args)
public char[] readPassword()
public char[] readPassword(String fmt, Object… args)
  • Like using System.in with a BufferedReader, the Console readLine() method reads input until the user presses the Enter key.
  • The overloaded version of readLine() displays a formatted message prompt prior to requesting input.
  • The readPassword() methods are similar to the readLine() method, with two important differences:
    • The text the user types is not echoed back and displayed on the screen as they are typing.
    • The data is returned as a char[] instead of a String.
  • The first feature improves security by not showing the password on the screen if someone happens to be sitting next to you.
  • The second feature involves preventing passwords from entering the String pool.
97
Q

Reviewing Console Methods

The last code sample we present asks the user a series of questions and prints results based on this information using many of various methods we learned in this section:

Console console = System.console();
if (console == null) {
    throw new RuntimeException("Console not available");
} else {
    String name = console.readLine("Please enter your name: ");
    console.writer().format("Hi %s", name);
    console.writer().println();
    console.format("What is your address? ");
    String address = console.readLine();
    char[] password = console.readPassword("Enter a password "
    \+ "between %d and %d characters: ", 5, 10);
    char[] verify = console.readPassword("Enter the password again: ");
    console.printf("Passwords "
    \+ (Arrays.equals(password, verify) ? "match" : "do not match"));
}
A

Assuming the Console is available, the output should resemble the following:

Please enter your name: Max
Hi Max
What is your address? Spoonerville
Enter a password between 5 and 10 characters:
Enter the password again:
Passwords match
98
Q

Working with Advanced APIs

A

Files, paths, I/O streams: you’ve worked with a lot this chapter! In this final section, we cover some advanced features of I/O streams and NIO.2 that can be quite useful in practice—and have been known to appear on the exam from time to time!

99
Q

Manipulating Input Streams

  • public boolean markSupported()
  • public void mark(int readLimit)
  • public void reset() throws IOException
  • public long skip(long n) throws IOException
A

All input stream classes include the following methods to manipulate the order in which data is read from an I/O stream:

// InputStream and Reader
public boolean markSupported()
public void mark(int readLimit)
public void reset() throws IOException
public long skip(long n) throws IOException
  • The mark() and reset() methods return an I/O stream to an earlier position.
  • Before calling either of these methods, you should call the markSupported() method, which returns true only if mark() is supported.
  • The skip() method is pretty simple; it basically reads data from the I/O stream and discards the contents.
100
Q

> [!TIP]
Not all input stream classes support mark() and reset(). Make sure to call markSupported() on the I/O stream before calling these methods, or an exception will be thrown at runtime.

A
101
Q

Marking Data

A

Assume that we have an InputStream instance whose next values are LION. Consider the following code snippet:

public void readData(InputStream is) throws IOException {
    System.out.print((char) is.read()); // L
    if (is.markSupported()) {
        is.mark(100); // Marks up to 100 bytes
        System.out.print((char) is.read()); // I
        System.out.print((char) is.read()); // O
        is.reset(); // Resets stream to position before I
    }
    System.out.print((char) is.read()); // I
    System.out.print((char) is.read()); // O
    System.out.print((char) is.read()); // N
}

The code snippet will output LIOION if mark() is supported and LION otherwise. It’s a good practice to organize your read() operations so that the I/O stream ends up at the same position regardless of whether mark() is supported.

What about the value of 100 that we passed to the mark() method?
This value is called the readLimit. It instructs the I/O stream that we expect to call reset() after at most 100 bytes. If our program calls reset() after reading more than 100 bytes from calling mark(100), it may throw an exception, depending on the I/O stream class.

102
Q

> [!TIP]
In actuality, mark() and reset() are not putting the data back into the I/O stream but are storing the data in a temporary buffer in memory to be read again. Therefore, you should not call the mark() operation with too large a value, as this could take up a lot of memory.

A
103
Q

Skipping Data

A

Assume that we have an InputStream instance whose next values are TIGERS. Consider the following code snippet:

System.out.print ((char)is.read()); // T
is.skip(2); // Skips I and G
is.read(); // Reads E but doesn't output it
System.out.print((char)is.read()); // R
System.out.print((char)is.read()); // S

This code prints TRS at runtime. We skipped two characters, I and G. We also read E but didn’t use it anywhere, so it behaved like calling skip(1).
The return parameter of skip() tells us how many values were skipped. For example, if we are near the end of the I/O stream and call skip(1000), the return value might be 20, indicating that the end of the I/O stream was reached after 20 values were skipped. Using the return value of skip() is important if you need to keep track of where you are in an I/O stream and how many bytes have been processed.

104
Q

Reviewing Manipulation APIs

A

TABLE 14.11 Common I/O stream methods

105
Q

Discovering File Attributes

A

We begin our discussion by presenting the basic methods for reading file attributes. These methods are usable within any file system, although they may have limited meaning in some file systems.

106
Q

Checking for Symbolic Links
java.nio.file.Files

  • public static boolean isDirectory(Path path, LinkOption… options)
  • public static boolean isRegularFile(Path path, LinkOption… options)
  • public static boolean isSymbolicLink(Path path)
A

Earlier, we saw that the Files class has methods called isDirectory() and isRegularFile(), which are similar to the isDirectory() and isFile() methods on File. While the File object can’t tell you if a reference is a symbolic link, the isSymbolicLink() method on Files can.

It is possible for isDirectory() or isRegularFile() to return true for a symbolic link, as long as the link resolves to a directory or regular file, respectively. Let’s take a look at some sample code:

System.out.print(Files.isDirectory(Paths.get("/canine/fur.jpg")));
System.out.print(Files.isSymbolicLink(Paths.get("/canine/coyote")));
System.out.print(Files.isRegularFile(Paths.get("/canine/types.txt")));
  • The first example prints true if fur.jpg is a directory or a symbolic link to a directory and false otherwise.
  • The second example prints true if /canine/coyote is a symbolic link, regardless of whether the file or directory it points to exists.
  • The third example prints true if types.txt points to a regular file or a symbolic link that points to a regular file.
107
Q

Checking File Accessibility
java.nio.file.Files

  • public static boolean isHidden(Path path) throws IOException
  • public static boolean isReadable(Path path)
  • public static boolean isWritable(Path path)
  • public static boolean isExecutable(Path path)
A

In many file systems, it is possible to set a boolean attribute to a file that marks it hidden, readable, or executable. The Files class includes methods that expose this information: isHidden(), isReadable(), isWriteable(), and isExecutable().
A hidden file can’t normally be viewed when listing the contents of a directory. The readable, writable, and executable flags are important in file systems where the filename can be viewed, but the user may not have permission to open the file’s contents, modify the file, or run the file as a program, respectively.
Here we present an example of each method:

System.out.print(Files.isHidden(Paths.get("/walrus.txt")));
System.out.print(Files.isReadable(Paths.get("/seal/baby.png")));
System.out.print(Files.isWritable(Paths.get("dolphin.txt")));
System.out.print(Files.isExecutable(Paths.get("whale.png")));
  • If the walrus.txt file exists and is hidden within the file system, the first example prints true.
  • The second example prints true if the baby.png file exists and its contents are readable.
  • The third example prints true if the dolphin.txt file can be modified.
  • Finally, the last example prints true if the file can be executed within the operating system.
  • Note that the file extension does not necessarily determine whether a file is executable. For example, an image file that ends in .png could be marked executable in some file systems.
    With the exception of the isHidden() method, these methods do not declare any checked exceptions and return false if the file does not exist.
108
Q

Improving Attribute Access

A

Up until now, we have been accessing individual file attributes with multiple method calls. While this is functionally correct, there is often a cost each time one of these methods is called. Put simply, it is far more efficient to ask the file system for all of the attributes at once rather than performing multiple round trips to the file system. Furthermore, some attributes are file system–specific and cannot be easily generalized for all file systems.
* NIO.2 addresses both of these concerns by allowing you to construct views for various file systems with a single method call.
* A view is a group of related attributes for a particular file system type.
* That’s not to say that the earlier attribute methods that we just finished discussing do not have their uses.
* If you need to read only one attribute of a file or directory, requesting a view is unnecessary.

109
Q

Understanding Attribute and View Types

A

NIO.2 includes two methods for working with attributes in a single method call:

  • a read-only attributes method
  • and an updatable view method.
  • For each method, you need to provide a file system type object, which tells the NIO.2 method which type of view you are requesting.
  • By updatable view, we mean that we can both read and write attributes with the same object.
  • Table 14.12 lists the commonly used attributes and view types.
  • For the exam, you only need to know about the basic file attribute types.
  • The other views are for managing operating system–specific information.
110
Q

Retrieving Attributes

public static <A extends BasicFileAttributes> A readAttributes( Path path, Class<A> type, LinkOption… options) throws IOException
A

The Files class includes the following method to read attributes of a class in a read-only capacity:

public static <A extends BasicFileAttributes> A readAttributes( Path path, Class<A> type, LinkOption… options) throws IOException

Applying it requires specifying the Path and BasicFileAttributes.class parameters.

var path = Paths.get("/turtles/sea.txt");
BasicFileAttributes data = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("Is a directory? " + data.isDirectory());
System.out.println("Is a regular file? " + data.isRegularFile());
System.out.println("Is a symbolic link? " + data.isSymbolicLink());
System.out.println("Size (in bytes): " + data.size());
System.out.println("Last modified: " + data.lastModifiedTime());

The BasicFileAttributes class includes many values with the same name as the attribute methods in the Files class. The advantage of using this method, though, is that all of the attributes are retrieved at once for some operating systems.

111
Q

Modifying Attributes

public static <V extends FileAttributeView> V getFileAttributeView( Path path, Class<V> type, LinkOption… options)
A

The following Files method returns an updatable view:

public static <V extends FileAttributeView> V getFileAttributeView( Path path, Class<V> type, LinkOption… options)

We can use the updatable view to increment a file’s last modified date/time value by 10,000 milliseconds, or 10 seconds.

// Read file attributes
var path = Paths.get("/turtles/sea.txt");
BasicFileAttributeView view = Files.getFileAttributeView(path, BasicFileAttributeView.class);
BasicFileAttributes attributes = view.readAttributes();

// Modify file last modified time
FileTime lastModifiedTime = FileTime.fromMillis(attributes.lastModifiedTime().toMillis() + 10_000);
view.setTimes(lastModifiedTime, null, null);

After the updatable view is retrieved, we need to call readAttributes() on the view to obtain the file metadata. From there, we create a new FileTime value and set it using the setTimes() method:

// BasicFileAttributeView instance method
public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime)

This method allows us to pass null for any date/time value that we do not want to modify. In our sample code, only the last modified date/time is changed.

112
Q

> [!NOTE]
Not all file attributes can be modified with a view.
For example, you cannot set a property that changes a file into a directory.
Likewise, you cannot change the size of the object without modifying its contents.

A
113
Q

Traversing a Directory Tree

A

While the Files.list() method is useful, it traverses the contents of only a single directory. What if we want to visit all of the paths within a directory tree? Before we proceed, we need to review some basic concepts about file systems. Remember that a directory is organized in a hierarchical manner. For example, a directory can contain files and other directories, which can in turn contain other files and directories. Every record in a file system has exactly one parent, with the exception of the root directory, which sits atop everything.

A file system is commonly visualized as a tree with a single root node and many branches and leaves. In this model, a directory is a branch or internal node, and a file is a leaf node. A common task in a file system is to iterate over the descendants of a path, either recording information about them or, more commonly, filtering them for a specific set of files. For example, you may want to search a folder and print a list of all of the .java files. Furthermore, file systems store file records in a hierarchical manner. Generally speaking, if you want to search for a file, you have to start with a parent directory, read its child elements, then read their children, and so on.

Traversing a directory, also referred to as walking a directory tree, is the process by which you start with a parent directory and iterate over all of its descendants until some condition is met or there are no more elements over which to iterate. For example, if we’re searching for a single file, we can end the search when the file is found or we’ve checked all files and come up empty. The starting path is usually a specific directory; after all, it would be time-consuming to search the entire file system on every request!

114
Q

Don’t Use DirectoryStream and FileVisitor

A

While browsing the NIO.2 Javadocs, you may come across methods that use the DirectoryStream and FileVisitor classes to traverse a directory. These methods predate the existence of the Stream API and were even required knowledge for older Java certification exams.
The best advice we can give you is to not use them. The newer Stream API–based methods are superior and accomplish the same thing, often with much less code.

115
Q

Selecting a Search Strategy

A

Two common strategies are associated with walking a directory tree: a depth-first search and a breadth-first search.
depth-first search
* A depth-first search traverses the structure from the root to an arbitrary leaf and then navigates back up toward the root, traversing fully any paths it skipped along the way.
* The search depth is the distance from the root to current node.
* To prevent endless searching, Java includes a search depth that is used to limit how many levels (or hops) from the root the search is allowed to go.

breadth-first search
* Alternatively, a breadth-first search starts at the root and processes all elements of each particular depth before proceeding to the next depth level.
* The results are ordered by depth, with all nodes at depth 1 read before all nodes at depth 2, and so on.
* While a breadth-first search tends to be balanced and predictable, it also requires more memory since a list of visited nodes must be maintained.

For the exam, you don’t have to understand the details of each search strategy that Java employs; you just need to be aware that the NIO.2 Stream API methods use depth-first searching with a depth limit, which can be optionally changed.

116
Q

Walking a Directory

  • public static Stream<Path> walk(Path start, FileVisitOption… options) throws IOException
  • public static Stream<Path> walk(Path start, int maxDepth, FileVisitOption… options) throws IOException
A

The Files class includes two methods for walking the directory tree using a depth-first search.

public static Stream<Path> walk(Path start, FileVisitOption… options) throws IOException
public static Stream<Path> walk(Path start, int maxDepth, FileVisitOption… options) throws IOException
  • Like our other stream methods, walk() uses lazy evaluation
  • and evaluates a Path only as it gets to it.
  • This means that even if the directory tree includes hundreds or thousands of files, the memory required to process a directory tree is low.
  • The first walk() method relies on a default maximum depth of Integer.MAX_VALUE,
  • while the overloaded version allows the user to set a maximum depth.
  • This is useful in cases where the file system might be large and we know the information we are looking for is near the root.

Rather than just printing the contents of a directory tree, we can again do something more interesting. The following getPathSize() method walks a directory tree and returns the total size of all the files in the directory:

private long getSize(Path p) {
    try {
        return Files.size(p);
    } catch (IOException e) {
        throw new UncheckedIOException(e);
    }
}
public long getPathSize(Path source) throws IOException {
    try (var s = Files.walk(source)) {
        return s.parallel()
            .filter(p -> !Files.isDirectory(p))
            .mapToLong(this::getSize)
            .sum();
    }
}

The getSize() helper method is needed because Files.size() declares IOException, and we’d rather not put a try/catch block inside a lambda expression. Instead, we wrap it in the unchecked exception class UncheckedIOException. We can print the data using the format() method:

var size = getPathSize(Path.of("/fox/data"));
System.out.format("Total Size: %.2f megabytes", (size/1000000.0));

Depending on the directory you run this on, it will print something like this:

Total Size: 15.30 megabytes

117
Q

Applying a Depth Limit

A

Let’s say our directory tree is quite deep, so we apply a depth limit by changing one line of code in our getPathSize() method.

try (var s = Files.walk(source, 5)) {

This new version checks for files only within 5 steps of the starting node. A depth value of 0 indicates the current path itself. Since the method calculates values only on files, you’d have to set a depth limit of at least 1 to get a nonzero result when this method is applied to a directory tree.

118
Q

Avoiding Circular Paths

A

Many of our earlier NIO.2 methods traverse symbolic links by default, with a NOFOLLOW_LINKS used to disable this behavior. The walk() method is different in that it does not follow symbolic links by default and requires the FOLLOW_LINKS option to be enabled. We can alter our getPathSize() method to enable following symbolic links by adding the FileVisitOption:

try (var s = Files.walk(source, FileVisitOption.FOLLOW_LINKS)) {

When traversing a directory tree, your program needs to be careful of symbolic links, if enabled.

For example, if our process comes across a symbolic link that points to the root directory of the file system, every file in the system will be searched!

Worse yet, a symbolic link could lead to a cycle in which a path is visited repeatedly. A cycle is an infinite circular dependency in which an entry in a directory tree points to one of its ancestor directories.

119
Q

Searching a Directory

public static Stream<Path> find(Path start, int maxDepth, 
BiPredicate<Path BasicFileAttributes> matcher, FileVisitOption… options) throws IOException
A

In the previous example, we applied a filter to the Stream<Path> object to filter the results, although there is a more convenient method.

public static Stream<Path> find(Path start, int maxDepth, 
BiPredicate<Path BasicFileAttributes> matcher, FileVisitOption… options) throws IOException

The find() method behaves in a similar manner as the walk() method, except that it takes a BiPredicate to filter the data. It also requires a depth limit to be set. Like walk(), find() also supports the FOLLOW_LINK option.
The two parameters of the BiPredicate are a Path object and a BasicFileAttributes object, which you saw earlier in the chapter. In this manner, Java automatically retrieves the basic file information for you, allowing you to write complex lambda expressions that have direct access to this object. We illustrate this with the following example:

Path path = Paths.get("/bigcats");
long minSize = 1_000;
try (var s = Files.find(path, 10, 
    (p, a) -> a.isRegularFile() 
        && p.toString().endsWith(".java")
        && a.size() > minSize)) {
s.forEach(System.out::println);
}

This example searches a directory tree and prints all .java files with a size of at least 1,000 bytes, using a depth limit of 10. While we could have accomplished this using the walk() method along with a call to readAttributes(), this implementation is a lot shorter and more convenient than those would have been. We also don’t have to worry about any methods within the lambda expression declaring a checked exception, as we saw in the getPathSize() example.

120
Q

Review of Key APIs

A

TABLE 14.14 Key APIs

FIGURE 14.8 Diagram of I/O stream classes

121
Q

Summary

A

ans

122
Q

Exam Essentials

A

ans

123
Q

Review Questions

A

ans