Hi! Today we'll talk about working with files and directories. You already know how to manage file contents: we've dedicated a lot of lessons to this :) I think you find it easy to remember a few classes used for these purposes.
In today's lesson, we'll talk specifically about file management: creating, renaming, etc. Before Java 7, all such operations were performed using the
Not the most complex class, right? :) Well, we've also got this
Here we first create a file (
Note: like the methods of the
Console output:
Now you know how to copy files programmatically! :) Of course, the
Console output:
Super convenient! :) This ability appeared in Java 7. The Stream API appeared in Java 8. It adds some elements of functional programming to Java. Including richer file handling capabilities. Imagine that we have the following task: find all the lines that begin with the word "As", convert them to UPPERCASE, and display them on the console. What would a solution using the
Console output:
Mission accomplished, but don't you think that for such a simple task our code turned out to be a little... verbose? Using Java 8's Stream API, the solution looks much more elegant:
We achieved the same result, but with much less code! What's more, no one can say that we've lost "readability". I think you can easily comment on what this code does, even without being familiar with the Stream API. In short, a Stream is a sequence of elements, over which you can perform various operations. We get a Stream object from the
Now let's return to our bread and butter, that is, files :) The last capability that we will consider today is walking through a file tree. In modern operating systems, the file structure most often looks like a tree: it has a root and there are branches, which can have other branches, etc. The root and branches are directories. For example, the directory "С://" may be the root. It includes two branches: "C://Downloads" and "C://Users". Each of these branches has two branches: "C://Downloads/Pictures", "C://Downloads/Video", "C://Users/JohnSmith", "C://Users/Pudge2005". And these branches in turn have other branches, etc. and this is why we call it a tree. On Linux, the structure is similar, but the /home directory is the root. Now imagine that we need to start at the root directory, walk through all of its folders and subfolders, and find files that have some particular content. We will search for files that begin with the line "This is the file we need!" We'll take the "testFolder" folder, which is on the desktop, as the root directory. Here are its contents: The level1-a and level1-b folders also contain folders: There are no folders in these "second level folders", only individual files: The 3 files with the contents we need are deliberately given explanatory names: FileWeNeed1.txt, FileWeNeed2.txt, FileWeNeed3.txt. These are precisely the files we need to find using Java. How do we do this? A very powerful method for traversing a file tree comes to our aid:
In this case, our class inherits
Actually, this is very simple. Here we are simply describing what the program should do after the file is visited and all the necessary operations have been performed. In our case, we want to continue traversing the tree, so we choose the
Well, let's run our code and see if it works.
Console output:
Excellent! It worked! :) You could also accept this small challenge: replace
File
class. You can read about it here.
But in Java 7, the language's creators decided to change how we work with files and directories. This happened because the File
class had several drawbacks. For example, it did not have the copy()
method, which would let you copy a file from one location to another (a seemingly essential ability).
In addition, the File
class had quite a few methods that returned boolean
values. When there is an error, such a method returns false. It does not throw an exception, making it very difficult to identify errors and diagnose their causes.
In the place of the single File
class, 3 classes appeared: Paths
, Path
, and Files
. Well, to be precise, Path
is an interface, not a class.
Let's figure out how they differ from each other and why we need each of them.
Let's start with the simplest: Paths
.
Paths
Paths
is a very simple class with a single static method: get()
. It was created solely to get a Path
object from the passed string or URI.
It has no other functionality.
Here is an example of it at work:
Not the most complex class, right? :) Well, we've also got this
Path
type. Let's figure out what Path
is and why it is needed :)
Path
Path
, by and large, is a redesigned analogue of the File
class. It is much easier to work with than File
.
First, many utility (static) methods were taken out and moved to the Files
class.
Second, order was imposed on the return values of the methods of the Path
interface. In the File
class, methods returned either a String
, or a boolean
, or a File
.
It wasn't easy to figure it out. For example, there was a getParent()
method that returned a string representing the parent path of the current file. But there was also a getParentFile()
method, which returned the same thing but in the form of a File
object! This is clearly redundant.
Accordingly, in the Path
interface, the getParent()
method and other methods for working with files simply return a Path
object. No pile of options — everything is easy and simple.
What are some of the useful methods that Path
has?
Here are some of them and examples of how they work:
getFileName()
: returns the file name from the path;getParent()
: returns the "parent" directory of the current path (in other words, the directory located immediately above in the directory tree);getRoot()
: returns the "root" directory, i.e. the directory at the top of the directory tree;startsWith()
,endsWith()
: check whether the path starts/ends with the passed path:Console output:Pay attention to how theendsWith()
method works. It checks whether the current path ends with the passed path. Specifically, whether it is in the path, not in the passed string.Compare the results of these two calls:Console output:TheendsWith()
method must be passed a genuine path, not just a set of characters: otherwise, the result will always be false, even if the current path really ends with that sequence of characters (as is the case with "estFile.txt" in the example above).In addition,Path
has a group of methods that simplifies working with absolute (full) and relative paths.
boolean isAbsolute()
returns true if the current path is absolute:Console output:Path normalize()
: "normalizes" the current path, removing unnecessary elements from it. You may know that in popular operating systems the symbols "." (current directory) and ".." (parent directory) are often used to designate paths. For example, "./Pictures/dog.jpg" means that the the current directory has a "Pictures" folder, which in turn contains a "dog.jpg" file.Look here. If a path using "." or ".." appears in your program, thenormalize()
method will remove them and produce a path that does not contain them:Console output:Path relativize()
: computes the relative path between the current and the passed path.For example:Console output:
Path
methods is quite long. You can find them all in the Oracle documentation.
Now we'll move on to consider Files
.
Files
Files
is a utility class that holds the static methods taken out of the File
class. Files
is comparable to Arrays
or Collections
. The difference is that it works with files, not arrays or collections :)
It focuses on managing files and directories. Using the static methods of the Files
class, we can create, delete, and move files and directories.
These operations are performed using the createFile()
(for directories, createDirectory()
), move()
, and delete()
methods.
Here's how to use them:
Here we first create a file (
Files.createFile()
method) on the desktop. Then we create a folder in the same location (Files.createDirectory()
method). After that, we move the file (Files.move()
method) from the desktop to this new folder, and finally we delete the file (Files.delete()
method).
Console output:
Note: like the methods of the
Path
interface, many methods of the Files
class return a Path
object.
Most of the methods of the Files
class also take Path
objects as inputs. Here the Paths.get()
method will be your faithful assistant — make good use of it.
What else is interesting in Files
? What the old File
class really lacked is a copy()
method! We talked about it at the beginning of this lesson. Now it's time to meet it!
Console output:
Now you know how to copy files programmatically! :) Of course, the
Files
class lets you not only manage a file itself, but also work with its contents.
It has the write()
method for writing data to a file, and all of 3 methods for reading data: read()
, readAllBytes()
, and readAllLines()
We will dwell in detail on the last one. Why that one?
Because it has a very interesting return type: List<String>
! That is, it returns us a list of all the lines in the file. Of course, this makes it very convenient to work with the file contents, because the entire file, line by line, can, for example, be displayed on the console using an ordinary for
loop:
Console output:
Super convenient! :) This ability appeared in Java 7. The Stream API appeared in Java 8. It adds some elements of functional programming to Java. Including richer file handling capabilities. Imagine that we have the following task: find all the lines that begin with the word "As", convert them to UPPERCASE, and display them on the console. What would a solution using the
Files
class look like in Java 7?
Something like this:
Console output:
Mission accomplished, but don't you think that for such a simple task our code turned out to be a little... verbose? Using Java 8's Stream API, the solution looks much more elegant:
We achieved the same result, but with much less code! What's more, no one can say that we've lost "readability". I think you can easily comment on what this code does, even without being familiar with the Stream API. In short, a Stream is a sequence of elements, over which you can perform various operations. We get a Stream object from the
Files.lines()
method, and then apply 3 functions to it:
- We use the
filter()
method to select only those lines from the file that begin with "As". - We walk through all the selected lines using the
map()
method and convert each of them to UPPERCASE. - We use the
collect()
method to gather all of the received lines into aList
.
Now let's return to our bread and butter, that is, files :) The last capability that we will consider today is walking through a file tree. In modern operating systems, the file structure most often looks like a tree: it has a root and there are branches, which can have other branches, etc. The root and branches are directories. For example, the directory "С://" may be the root. It includes two branches: "C://Downloads" and "C://Users". Each of these branches has two branches: "C://Downloads/Pictures", "C://Downloads/Video", "C://Users/JohnSmith", "C://Users/Pudge2005". And these branches in turn have other branches, etc. and this is why we call it a tree. On Linux, the structure is similar, but the /home directory is the root. Now imagine that we need to start at the root directory, walk through all of its folders and subfolders, and find files that have some particular content. We will search for files that begin with the line "This is the file we need!" We'll take the "testFolder" folder, which is on the desktop, as the root directory. Here are its contents: The level1-a and level1-b folders also contain folders: There are no folders in these "second level folders", only individual files: The 3 files with the contents we need are deliberately given explanatory names: FileWeNeed1.txt, FileWeNeed2.txt, FileWeNeed3.txt. These are precisely the files we need to find using Java. How do we do this? A very powerful method for traversing a file tree comes to our aid:
Files.walkFileTree ()
.
Here's what we need to do.
First, we need a FileVisitor
.
FileVisitor
is a special class that encapsulates the entire logic for traversing a file tree. In particular, that's where we will put the logic for reading the contents of a file and checking whether it contains the text we need. Here's what our FileVisitor
looks like:
In this case, our class inherits
SampleFileVisitor
. This is a simplified version of FileVisitor
, in which we need to override just one method: visitFile()
. Here we define what needs to be done with each file in each directory.
If you need more complex logic for traversing the file structure, you should inherit FileVisitor
. You would need to implement 3 more methods in that class:
preVisitDirectory()
: the logic to execute before entering a folder;visitFileFailed()
: the logic to execute if a file cannot be visited (no access, or for other reasons);postVisitDirectory()
: the logic to execute after entering a folder.
visitFile()
method is quite simple: read all the lines in the file, check if they contain the content we need, and if so, print the absolute path on the console.
The only line that might cause you difficulty is this one:
Actually, this is very simple. Here we are simply describing what the program should do after the file is visited and all the necessary operations have been performed. In our case, we want to continue traversing the tree, so we choose the
CONTINUE
option.
But, alternatively, we might have a different objective: instead of finding all files that contain "This is the file we need", find only one such file. After that, the program should terminate. In this case, our code would look exactly the same, but the return value would be:
Well, let's run our code and see if it works.
Console output:
Excellent! It worked! :) You could also accept this small challenge: replace
SimpleFileVisitor
with an ordinary FileVisitor
, override all 4 methods, and come up with your own purpose for the program. For example, you could write a program that logs all its actions: display the name of the file or folder before or after entering them
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.