Skip to content

Java IO & NIO

I/O vs NIO Comparison

Java I/O vs NIO


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

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
}

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

  1. InputStream vs Reader?
  2. InputStream: Bytes, binary data
  3. Reader: Characters, text data with encoding

  4. Why use buffered streams?

  5. Reduces I/O operations by buffering data
  6. Significant performance improvement

  7. Difference between NIO and traditional I/O?

  8. NIO: Buffer-oriented, non-blocking, channels
  9. I/O: Stream-oriented, blocking

  10. When to use memory-mapped files?

  11. Large files, random access, sharing between processes

  12. What is Path vs File?

  13. Path (NIO.2): Modern, more features
  14. File: Legacy, still works but prefer Path

  • *