Java IO & NIO¶
I/O vs NIO Comparison¶
Traditional I/O (java.io)¶
Reading Files¶
// Reading text file - BufferedReader
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// Read all lines into list
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
List<String> lines = reader.lines().collect(Collectors.toList());
}
// Reading binary file - BufferedInputStream
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("file.bin"))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// Process buffer[0..bytesRead-1]
}
}
// Read entire file into byte array
try (FileInputStream fis = new FileInputStream("file.bin")) {
byte[] data = fis.readAllBytes(); // Java 9+
}
Writing Files¶
// Writing text file - BufferedWriter
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write("Line 1");
writer.newLine();
writer.write("Line 2");
}
// Append mode
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt", true))) {
writer.write("Appended line");
}
// Writing binary file - BufferedOutputStream
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.bin"))) {
byte[] data = "Hello".getBytes();
bos.write(data);
}
// PrintWriter for convenient writing
try (PrintWriter pw = new PrintWriter(new FileWriter("output.txt"))) {
pw.println("Line 1");
pw.printf("Formatted: %d%n", 42);
}
I/O Stream Hierarchy¶
Character Encoding¶
// Specify encoding explicitly
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8))) {
// Read with UTF-8 encoding
}
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("file.txt"), StandardCharsets.UTF_8))) {
// Write with UTF-8 encoding
}
NIO.2 (java.nio.file) - Recommended¶
Path Operations¶
// Creating Paths
Path path1 = Path.of("file.txt"); // Java 11+
Path path2 = Path.of("/home", "user", "file.txt"); // Multiple parts
Path path3 = Paths.get("file.txt"); // Pre-Java 11
// Path operations
Path path = Path.of("/home/user/docs/file.txt");
path.getFileName(); // file.txt
path.getParent(); // /home/user/docs
path.getRoot(); // /
path.getNameCount(); // 4
path.getName(0); // home
path.subpath(1, 3); // user/docs
path.toAbsolutePath(); // /home/user/docs/file.txt
path.normalize(); // Removes . and ..
path.toRealPath(); // Resolves symlinks
// Combining paths
Path base = Path.of("/home/user");
Path resolved = base.resolve("docs/file.txt"); // /home/user/docs/file.txt
Path sibling = path.resolveSibling("other.txt"); // /home/user/docs/other.txt
// Relative path between two paths
Path from = Path.of("/home/user");
Path to = Path.of("/home/user/docs/file.txt");
Path relative = from.relativize(to); // docs/file.txt
Reading Files (NIO.2)¶
// Read all lines
List<String> lines = Files.readAllLines(Path.of("file.txt"));
List<String> lines2 = Files.readAllLines(Path.of("file.txt"), StandardCharsets.UTF_8);
// Read all bytes
byte[] bytes = Files.readAllBytes(Path.of("file.bin"));
// Read as string (Java 11+)
String content = Files.readString(Path.of("file.txt"));
// Stream lines (memory efficient for large files)
try (Stream<String> stream = Files.lines(Path.of("file.txt"))) {
stream.filter(line -> line.contains("error"))
.forEach(System.out::println);
}
// BufferedReader from Path
try (BufferedReader reader = Files.newBufferedReader(Path.of("file.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// InputStream from Path
try (InputStream is = Files.newInputStream(Path.of("file.bin"))) {
// Read from input stream
}
Writing Files (NIO.2)¶
// Write lines
List<String> lines = List.of("Line 1", "Line 2", "Line 3");
Files.write(Path.of("output.txt"), lines);
// Write bytes
byte[] data = "Hello World".getBytes();
Files.write(Path.of("output.bin"), data);
// Write string (Java 11+)
Files.writeString(Path.of("output.txt"), "Hello World");
// With options
Files.write(Path.of("output.txt"), lines,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND);
// BufferedWriter from Path
try (BufferedWriter writer = Files.newBufferedWriter(Path.of("output.txt"))) {
writer.write("Hello");
writer.newLine();
}
// OutputStream from Path
try (OutputStream os = Files.newOutputStream(Path.of("output.bin"))) {
os.write(data);
}
File Operations¶
// Check existence
boolean exists = Files.exists(path);
boolean notExists = Files.notExists(path);
boolean isRegularFile = Files.isRegularFile(path);
boolean isDirectory = Files.isDirectory(path);
boolean isSymbolicLink = Files.isSymbolicLink(path);
boolean isReadable = Files.isReadable(path);
boolean isWritable = Files.isWritable(path);
boolean isExecutable = Files.isExecutable(path);
boolean isHidden = Files.isHidden(path);
// Create files and directories
Files.createFile(Path.of("newfile.txt"));
Files.createDirectory(Path.of("newdir"));
Files.createDirectories(Path.of("path/to/newdir")); // Creates parents
Path tempFile = Files.createTempFile("prefix", ".suffix");
Path tempDir = Files.createTempDirectory("prefix");
// Copy and move
Files.copy(source, target);
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
Files.copy(inputStream, target);
Files.copy(source, outputStream);
Files.move(source, target);
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
// Delete
Files.delete(path); // Throws if not exists
Files.deleteIfExists(path); // Returns boolean
// File size and timestamps
long size = Files.size(path);
FileTime lastModified = Files.getLastModifiedTime(path);
Files.setLastModifiedTime(path, FileTime.from(Instant.now()));
Walking Directory Tree¶
// Walk directory tree
try (Stream<Path> paths = Files.walk(Path.of("/home/user"))) {
paths.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(System.out::println);
}
// Walk with depth limit
try (Stream<Path> paths = Files.walk(Path.of("/home/user"), 2)) {
paths.forEach(System.out::println);
}
// Find files matching pattern
try (Stream<Path> paths = Files.find(Path.of("/home"), 10,
(path, attrs) -> path.toString().endsWith(".txt") && attrs.isRegularFile())) {
paths.forEach(System.out::println);
}
// List directory contents (non-recursive)
try (Stream<Path> paths = Files.list(Path.of("/home/user"))) {
paths.forEach(System.out::println);
}
// Glob pattern matching
try (DirectoryStream<Path> stream = Files.newDirectoryStream(
Path.of("/home/user"), "*.{txt,java}")) {
for (Path entry : stream) {
System.out.println(entry);
}
}
File Attributes¶
// Basic attributes
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
attrs.size();
attrs.creationTime();
attrs.lastModifiedTime();
attrs.lastAccessTime();
attrs.isDirectory();
attrs.isRegularFile();
attrs.isSymbolicLink();
// POSIX attributes (Unix-like systems)
PosixFileAttributes posixAttrs = Files.readAttributes(path, PosixFileAttributes.class);
posixAttrs.owner();
posixAttrs.group();
posixAttrs.permissions();
// Set POSIX permissions
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-xr-x");
Files.setPosixFilePermissions(path, perms);
// Owner
UserPrincipal owner = Files.getOwner(path);
Files.setOwner(path, owner);
NIO Channels and Buffers¶
ByteBuffer¶
// Create buffer
ByteBuffer buffer = ByteBuffer.allocate(1024); // Heap buffer
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024); // Direct buffer
ByteBuffer wrapped = ByteBuffer.wrap(byteArray);
// Buffer operations
buffer.put((byte) 1); // Write byte
buffer.putInt(42); // Write int
buffer.put(byteArray); // Write byte array
buffer.flip(); // Switch to read mode (limit=position, position=0)
byte b = buffer.get(); // Read byte
int i = buffer.getInt(); // Read int
buffer.clear(); // Reset for writing (position=0, limit=capacity)
buffer.rewind(); // Reset position to 0 (keep limit)
buffer.compact(); // Move unread data to beginning
buffer.position(); // Current position
buffer.limit(); // Current limit
buffer.capacity(); // Total capacity
buffer.remaining(); // limit - position
buffer.hasRemaining(); // position < limit
FileChannel¶
// Read from file
try (FileChannel channel = FileChannel.open(Path.of("file.txt"), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
buffer.flip();
// Process buffer
}
// Write to file
try (FileChannel channel = FileChannel.open(Path.of("file.txt"),
StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes());
channel.write(buffer);
}
// Memory-mapped file (efficient for large files)
try (FileChannel channel = FileChannel.open(Path.of("largefile.bin"), StandardOpenOption.READ)) {
MappedByteBuffer mappedBuffer = channel.map(
FileChannel.MapMode.READ_ONLY,
0,
channel.size()
);
// Access file through mappedBuffer
byte firstByte = mappedBuffer.get(0);
}
// Transfer between channels (efficient)
try (FileChannel source = FileChannel.open(Path.of("source.txt"), StandardOpenOption.READ);
FileChannel dest = FileChannel.open(Path.of("dest.txt"),
StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
source.transferTo(0, source.size(), dest);
}
// File locking
try (FileChannel channel = FileChannel.open(Path.of("file.txt"),
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
FileLock lock = channel.lock(); // Exclusive lock
try {
// Work with file
} finally {
lock.release();
}
}
Watching for File Changes¶
// Watch for file system changes
WatchService watchService = FileSystems.getDefault().newWatchService();
Path dir = Path.of("/home/user/watched");
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
// Process events
while (true) {
WatchKey key = watchService.take(); // Blocking
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
@SuppressWarnings("unchecked")
WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
Path filename = pathEvent.context();
System.out.println(kind.name() + ": " + filename);
}
boolean valid = key.reset();
if (!valid) {
break; // Directory no longer accessible
}
}
Serialization¶
// Implement Serializable
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password; // Not serialized
}
// Serialize object
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.ser"))) {
oos.writeObject(person);
}
// Deserialize object
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject();
}
// Custom serialization
public class CustomPerson implements Serializable {
private transient String data;
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(encrypt(data));
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
data = decrypt((String) in.readObject());
}
}
Practical Examples¶
Copy Directory Recursively¶
public static void copyDirectory(Path source, Path target) throws IOException {
Files.walkFileTree(source, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
Path targetDir = target.resolve(source.relativize(dir));
Files.createDirectories(targetDir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.copy(file, target.resolve(source.relativize(file)),
StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
}
Delete Directory Recursively¶
public static void deleteDirectory(Path dir) throws IOException {
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
Read Large File Efficiently¶
// Using memory-mapped file
public static void processLargeFile(Path path) throws IOException {
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
long size = channel.size();
long position = 0;
long chunkSize = 1024 * 1024; // 1MB chunks
while (position < size) {
long remaining = size - position;
long mapSize = Math.min(chunkSize, remaining);
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, position, mapSize);
// Process buffer
while (buffer.hasRemaining()) {
byte b = buffer.get();
// Process byte
}
position += mapSize;
}
}
}
Common Interview Questions¶
- InputStream vs Reader?
- InputStream: Bytes, binary data
-
Reader: Characters, text data with encoding
-
Why use buffered streams?
- Reduces I/O operations by buffering data
-
Significant performance improvement
-
Difference between NIO and traditional I/O?
- NIO: Buffer-oriented, non-blocking, channels
-
I/O: Stream-oriented, blocking
-
When to use memory-mapped files?
-
Large files, random access, sharing between processes
-
What is Path vs File?
- Path (NIO.2): Modern, more features
- File: Legacy, still works but prefer Path
- *