Java NIO (New I/O) is a collection of APIs under the java.nio
package that provide more scalable, flexible, and high-performance I/O operations. The primary benefit of NIO is its ability to handle file I/O in a non-blocking manner, as opposed to the traditional I/O APIs, which block the execution while reading or writing data.
- Channels: Represent an open connection to a data source (file, socket, etc.).
- Buffers: Containers for data that are read from or written to channels.
- Selectors: Allow a single thread to handle multiple channels in a non-blocking manner.
- Path and Files: Classes to manage file paths and perform operations like reading and writing.
Path Class and Working with File Paths
In Java NIO, the Path
class is a crucial component for handling file paths. It’s part of the java.nio.file
package and replaces the File
class in traditional I/O. The Path
class provides methods for manipulating file paths in a platform-independent manner, making our code more flexible across operating systems.
Creating a Path object
1 2 3 4 5 6 7 8 9 |
import java.nio.file.Path; import java.nio.file.Paths; public class NIOExample { public static void main(String[] args) { Path path = Paths.get("C:/Users/John/Documents/example.txt"); System.out.println("File path: " + path); } } |
The Paths.get()
method creates a Path
object from a string or URI. We can use the Path
object to perform various file operations like checking existence, reading, or writing.
Common Path Methods
path.getFileName()
: Returns the file name.path.getParent()
: Returns the parent directory of the path.path.toAbsolutePath()
: Returns the absolute path of the file.
Reading and Writing Files with Files Class
The Files
class, also part of java.nio.file
, simplifies file operations like reading and writing. It provides various static methods to interact with files in a way that’s more efficient than the traditional I/O.
Reading a File
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.nio.file.*; import java.io.IOException; import java.util.List; public class ReadFileExample { public static void main(String[] args) { Path path = Paths.get("example.txt"); try { List<String> lines = Files.readAllLines(path); for (String line : lines) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } |
Writing to a File
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import java.nio.file.*; import java.io.IOException; import java.util.List; public class WriteFileExample { public static void main(String[] args) { Path path = Paths.get("output.txt"); List<String> lines = List.of("Hello", "World"); try { Files.write(path, lines); } catch (IOException e) { e.printStackTrace(); } } } |
BufferedWriter vs Files.write() in NIO
Both BufferedWriter
and Files.write()
can be used to write content to a file, but there are key differences in how they function.
- BufferedWriter: This class is used when we need to write characters to a file with buffering. It is best suited for writing text data line by line. We can use it in combination with
FileWriter
for performance benefits in scenarios where writing to a file is done in multiple small chunks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class BufferedWriterExample { public static void main(String[] args) { try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) { writer.write("Hello"); writer.newLine(); writer.write("World"); } catch (IOException e) { e.printStackTrace(); } } } |
- Files.write(): This method is more straightforward and is used for writing byte or character data to files. It’s useful for scenarios where we have the entire content ready to write.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import java.nio.file.*; import java.io.IOException; import java.util.List; public class FilesWriteExample { public static void main(String[] args) { Path path = Paths.get("output.txt"); List<String> lines = List.of("Hello", "World"); try { Files.write(path, lines); } catch (IOException e) { e.printStackTrace(); } } } |
BufferedWriter offers more control over buffering and writing large chunks of data efficiently. Files.write() is simpler and better suited for small, direct file writing tasks, such as writing entire collections.
Directory Traversal Using NIO
Java NIO provides the Files.walk()
method for traversing directories in a recursive manner. This method returns a stream of paths, which allows for easy iteration over the directory contents.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import java.nio.file.*; import java.io.IOException; public class DirectoryTraversal { public static void main(String[] args) { Path dirPath = Paths.get("C:/Users/John/Documents"); try { Files.walk(dirPath) .filter(Files::isRegularFile) .forEach(path -> System.out.println(path)); } catch (IOException e) { e.printStackTrace(); } } } |
Files.walk()
is used to iterate through the directory and subdirectories, filtering out only regular files and printing their paths.
Working with FileChannel for High-Performance File Operations
The FileChannel
class provides a more advanced and high-performance way to handle file I/O. It’s part of the NIO package and can perform non-blocking I/O operations, which is particularly useful when dealing with large files.
Reading Data Using FileChannel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import java.nio.file.*; import java.nio.channels.FileChannel; import java.io.IOException; import java.nio.ByteBuffer; public class FileChannelExample { public static void main(String[] args) { Path path = Paths.get("example.txt"); try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) { ByteBuffer buffer = ByteBuffer.allocate(1024); while (fileChannel.read(buffer) > 0) { buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } buffer.clear(); } } catch (IOException e) { e.printStackTrace(); } } } |
In this example, FileChannel
is used to read data in chunks using a ByteBuffer
. This provides more control over the data and is ideal for large files or when we want to manipulate the file data in memory.
Writing Data Using FileChannel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.nio.file.*; import java.nio.channels.FileChannel; import java.io.IOException; import java.nio.ByteBuffer; public class FileChannelWriteExample { public static void main(String[] args) { Path path = Paths.get("output.txt"); try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { String data = "Hello FileChannel!"; ByteBuffer buffer = ByteBuffer.wrap(data.getBytes()); fileChannel.write(buffer); } catch (IOException e) { e.printStackTrace(); } } } |
FileChannel
is particularly beneficial for tasks that require random access to file data, memory-mapped file access, and non-blocking I/O operations.