How to work with Files in Java

Beknazar
9 min readAug 26, 2021

--

In this article, I want to discuss in great detail how to work with files by using Java.

  1. File
  2. Reading and writing content

File

Well, let’s start first with understanding what is it file and file system.

File System

Let’s imagine our machine memory as a big canvas of cells that is able to store some data. Everything we have in our machine is stored there. In order to keep it organized, we have a file system. The file system controls how data is stored. Without it, data would be one large body of data with no way to tell where one piece of data stops and the next starts. So by separating the data and giving them a name(single file) helps us quickly find and work with the data. The file system consists of files and directories(folders). They are organized in a tree structure. There is one root directory from where the whole structure is started. Each file and directory has a unique file path, by this path we can find and work with them.

File

The file is isolated data with a name and unique file path in the file system. The file has a header and content. Inside the header, it will have metadata — data about this file like size, type, how to read, permissions, last modified, etc. The content part will actually have the content of that file.

In Java, files and directories are represented with one File object

Almost any programming language can manipulate(read, write, create, delete) files and directories by using the ready build-in frameworks and classes. However, it’s important to keep in mind that files are just a bunch of data in the memory and they got virtually structured as a file system.

java.io.File class

java.io package(io — input/output) will have most of the classes we will need to know to work with files in java. The first class we will take a look at is the File class. It represents files and directories in Java. While creating it we have to pass the file path(basically we need to tell which file or directory it should represent as a java object). We cannot read or write data into this file by using only the File object.

Mainly, we can use this object to:

  1. Get metadata(data about data) about our file. For example, the size, last modified, and other useful data.
  2. We can create this file/directory if doesn’t exist(or override if it exists)
  3. We can delete it.

Let’s see the java code. Assume, I have a test.txtfile on my desktop with Hello, World! content.

  • The file class represents files and directories.
  • In order to create a File object, we need to path File Path for a file we want to represent as a java object.
  • It has many useful methods to gather information about files/directories.

There are two types of file paths:
1. Absolute path that starts from the root of the file system.
For example, /Users/beknazarsuranchiyev/Desktop/test.txt

/ is root for Mac and for Windows it can be C driver

2. Relative path that ‘compiles’ from the place the code is running.
For example, src/test/myFile.txt

— — — — — — — — — — — — — — — — — — — — — — — — — —
Sometimes you will see \\ and / delimiters in the file path.

\ — is for Windows operation system. Since \ is a special character in Java, we need to escape it with another \ so that why we have \\

/ — is for the Linux-based operating system(Mac as well uses it).
I recomend always use / linx-based delimeter(it will work if you run code in wndows machines as well).

If a file or directory does not exist in the specified path, we can create them.

  • we can create an empty file by using createNewFile() method.
  • it does throw an unchecked exception so we need to handle it by declaring or catching(in this example just declaring).
  • In this example, we are creating our file here: /Users/beknazarsuranchiyev/Desktop/apple.txt
    if Desktop directory wouldn’t exist, the code would fail to create our apple.txt file. So createNewFile() is not able to create underline directories if they don’t exist. We will need to create them separately.

Let’s create directories

  • mkdir() is a method to create a directory(taking name from Linux)
  • if a directory is not created it will not throw an exception but will return false(one reason folder cannot be created is permissions)
  • There is mkdirs() that can create non-existing parent directories.

We can delete files and folders by using delete() command.

I want to discuss more on permissions

  • We can find out read access by carRead() method.
  • We can find out write access by canWrite() method.
  • We can find out the execute access by canExecute() method.

Ok, few more useful pieces of information:

  1. Find out which ID is running the code: System.out.println(System.getProperty("user.name"));
  2. If you need more info about files/directories permissions, you might try to run Linux commands from Java code. This is the last option to go.

Reading and writing content

In general, there are two ways of working with reading and writing files in Java.

  1. InputStream and OutputStream.
    The Stream classes are used for inputting and outputting all types of binary or byte data.
  2. Reader and Writer.
    The Reader and Writer classes are used for inputting and outputting characters and strings. Basically, text files.

Let’s talk about Streams

From OCP Book

Both InputStream/OutoutStream and Reader/Writer use streams in the background(Even Reader/Writer does not have a stream in their name). The main idea of the stream is to read/write piece by piece. For example, we want to read the content of a huge(100 GB let’s say) text file and let’s say count the number of letters. Reading and loading all content into memory(RAM memory) wouldn’t be the best option, memory can simply get overflowed. However, reading data piece by piece will not overflow the memory of course if we are not storing everything in the memory as well.

Let’s see how we can read file content using InputStream

  • It will print Hello, World! if my file under /Users/beknazarsuranchiyev/Desktop/test.txt will have Hello, World! content.
  • while((b = in.read()) != -1) {content.append((char)b);} this is the most important part of the code. in.read() will read the piece of the content and return it as int. When it is used next time, it will read the next piece and so on. When it will return -1 it means it reached the end of the file content. First, we are assigning the value of in.read() method to our b variable then we are checking if it’s -1 or not. If it’s -1 , we are exiting from the loop and if it’s not we are putting value into our SpringBuilder.
  • We always need to close our resources(files, database connections). In this case, we are closing it in finally block because it always runs(does not matter if an exception occurs or not). in.close() also throws checked exception so we are handling it there as well.
  • In this example, we are reading each byte separately, but there is an option to read multiple bytes at the same time. Reading multiple bytes at the same time of text files requires some extra code to convert bytes into String representation(better to use reader/writer classes).
  • The above code is a little old version of using exceptions and closing. We will see a better way of doing it.

Let’s see another example:

This is code does a lot more, but it looks shorter and nicer. It will make a copy of sourceFile to the place with a name specified in targetFile.

  • try() {} try with resources. The main idea is that it will close the resources automatically in the end so we don’t have to use finally block and close them manually. You can see, we opened two resources there and we don’t have to worry about closing them. The try with resources will handle it. If you want to create your custom class that will work with it, you will need to implement Closeable interface.
  • byte[] buffer = new byte[1024]; we are passing it into read() method. We are specifying the number of bytes to read as one piece. It will improve the efficiency of overall reading and writing.
  • The last thing to note is the way we write into the file. new FileOutputStream(targetFile) is responsible to write content into the file(if the file does not exist it will create one). out.write(buffer, 0, b); is used to write content into the file.
  • 'Inputs' is for reading and 'Outputs' is for writing.

Now, let’s talk about Readers and Writers

They also use streams only difference is they are made specifically to work with text files (characters, strings).

  • As you can see it has a similar concept with Streams.

Buffers(high-level classes)

The buffer classes exist for Streams and Reader/Writers as well. They are wrapper classes that provide more abstract methods to work with files in an easier way. There are two types of IO classes that can actually read and write.

High-level and low-level. So far we have been working only with low-level classes. As we already said high-level classes provide additional methods to work with file data in higher-level for example to read the whole lines.

  • You can see as new BufferedReader(new FileReader(file)) is wrapping low-level reader. This is the way we use high-level IO classes by wrapping the low-level IO classes.
  • in this example, we have a method that will read text file content and return it as String.
  • Notice, we have used stream (not IO streams) of java here to make code shorter.

The same idea of buffer classes we have ‘Stream’ classes. It’s always recommended to use buffer high-level classes.

Summary

We use File class to represent files and directories in java. We can get information about this file. We can create new files/directories or we can delete them. However, we cannot use it to read or write content.

In order to read and write files Java utilizes Streams. The main idea is to read/write the content of the file piece by piece.

There are two types of IO classes to read and write the content of the class.

  1. Input/Output streams. They read and write all types of data. It works at the binary level.
  2. Readers/Writers. They work for text content files.

Buffer or high-level are classes that are wrapper classes of low-level IO classes. Buffer classes will prove additional methods to work with file content at a higher level. It is always good practice to use buffered classes unless you are doing something specific and need low-level access to content.

That’s all for today, have a good one!

--

--

Beknazar
Beknazar

No responses yet