Home/Learn/Operating Systems/Directory Structure

Directory Structure

Beginner
File Systems

Directory structures define how files are organised and named within a filesystem, evolving from flat single-level designs to hierarchical trees with sharing via links.

Overview

The simplest directory structure is single-level — all files in one directory, which causes naming conflicts across users. Two-level structures give each user their own directory. Tree-structured directories (used by all modern OSes) allow arbitrary nesting with absolute paths (/home/user/docs/file.txt) and relative paths (../sibling/file.txt). Acyclic-graph directories extend trees with hard and symbolic links for file sharing without duplication. General-graph directories allow cycles but require garbage collection to reclaim unreachable files. Unix uses a tree structure with acyclic-graph extensions via links. Java's NIO Path API and Files utility class map directly onto these concepts.

Path Operations with Java NIO

Java's Path API provides clean abstractions for navigating the directory hierarchy. Paths.get() parses path strings, resolve() combines paths, relativize() computes relative paths between two locations, and normalize() removes redundant . and .. elements.

Java — Path operations with NIO
import java.nio.file.*;

// Absolute path: starts from filesystem root
Path absolute = Path.of("/home/user/projects/app/src/Main.java");
System.out.println("Root:     " + absolute.getRoot());       // /
System.out.println("Parent:   " + absolute.getParent());     // /home/user/projects/app/src
System.out.println("Filename: " + absolute.getFileName());   // Main.java
System.out.println("Parts:    " + absolute.getNameCount());  // 6

// Relative path: relative to current working directory
Path relative = Path.of("src/Main.java");
System.out.println("Is absolute: " + relative.isAbsolute()); // false

// resolve: combine a base path with another path
Path base = Path.of("/home/user/projects");
Path child = base.resolve("app/src/Main.java");
System.out.println("Resolved: " + child); // /home/user/projects/app/src/Main.java

// relativize: compute relative path from one location to another
Path from = Path.of("/home/user/projects/app");
Path to   = Path.of("/home/user/projects/lib/utils.jar");
System.out.println("Relative: " + from.relativize(to)); // ../lib/utils.jar

// normalize: remove . and .. components
Path messy = Path.of("/home/user/../user/./projects//app");
System.out.println("Normalised: " + messy.normalize()); // /home/user/projects/app

// Create directories (including missing parents)
Path newDir = Path.of("/tmp/deep/nested/dir");
Files.createDirectories(newDir); // equivalent to mkdir -p

Walking a Directory Tree

Files.walk() performs a depth-first traversal of a directory tree, returning a stream of Path objects. Files.walkFileTree() offers finer control via a FileVisitor with pre/post directory callbacks. Both are useful for recursive file operations like searching, copying, or computing directory sizes.

Java — Directory tree traversal with Files.walk()
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.stream.Stream;

Path root = Path.of("/tmp/sample");
Files.createDirectories(root.resolve("a/b"));
Files.createDirectories(root.resolve("a/c"));
Files.writeString(root.resolve("a/b/file1.txt"), "hello");
Files.writeString(root.resolve("a/c/file2.txt"), "world");

// Walk: depth-first stream of all paths
try (Stream<Path> stream = Files.walk(root)) {
    stream.forEach(p -> {
        int depth = root.relativize(p).getNameCount();
        System.out.println("  ".repeat(depth) + p.getFileName());
    });
}
// Output:
//   sample
//     a
//       b
//         file1.txt
//       c
//         file2.txt

// Find only .txt files up to depth 5
try (Stream<Path> txtFiles = Files.walk(root, 5)
        .filter(p -> p.toString().endsWith(".txt"))) {
    txtFiles.forEach(System.out::println);
}

// Compute total directory size
long totalBytes;
try (Stream<Path> all = Files.walk(root)) {
    totalBytes = all.filter(Files::isRegularFile)
                    .mapToLong(p -> { try { return Files.size(p); }
                                     catch (Exception e) { return 0L; } })
                    .sum();
}
System.out.println("Total size: " + totalBytes + " bytes");

Key Points to Remember

  • 1Single-level directories cause naming conflicts; tree-structured directories solve this with namespacing.
  • 2Absolute paths start from the root (/); relative paths are resolved from the current working directory.
  • 3A directory is a file containing name-to-inode mappings — every directory has at least two entries: . (self) and .. (parent).
  • 4Hard links create an acyclic-graph structure; cycles require garbage collection to detect unreachable files.
  • 5Files.walk() performs a lazy depth-first traversal and must be closed (use try-with-resources) to release file handles.
  • 6Path.normalize() is essential before security checks — /etc/passwd and /etc/../etc/passwd are the same file.

Interview Questions

Sign in to ask Aria
1

What is the difference between an absolute path and a relative path?

EasyAmazon
2

Why does every directory contain . and .. entries?

EasyFlipkart
3

How would you recursively calculate the total size of a directory tree in Java?

MediumGoogle
4

A web server receives a path like /files/../../etc/passwd. How do you prevent path traversal attacks using Java NIO?

HardAtlassian

Ask Aria about Directory Structure

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…