14.4 Advanced File System Patterns
Master advanced file system operations including locking, memory-mapped files, custom file systems, and specialized patterns.
File Locking
Advisory File Locking:
import java.nio.channels.*;
import java.nio.file.*;
import java.io.IOException;
/**
* File locking for coordinating access
*/
public class FileLockExample {
/**
* Exclusive lock (write lock)
*/
public static void exclusiveLock(Path file) throws IOException {
try (FileChannel channel = FileChannel.open(file,
StandardOpenOption.READ,
StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
// Acquire exclusive lock (blocks until available)
FileLock lock = channel.lock();
try {
System.out.println("Exclusive lock acquired");
// Perform exclusive operations
// Write to file, etc.
Thread.sleep(5000); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.release();
System.out.println("Lock released");
}
}
}
/**
* Shared lock (read lock)
*/
public static void sharedLock(Path file) throws IOException {
try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {
// Acquire shared lock
FileLock lock = channel.lock(0, Long.MAX_VALUE, true); // shared=true
try {
System.out.println("Shared lock acquired");
// Read file
String content = Files.readString(file);
System.out.println("Content: " + content);
} finally {
lock.release();
}
}
}
/**
* Try lock (non-blocking)
*/
public static void tryLock(Path file) throws IOException {
try (FileChannel channel = FileChannel.open(file,
StandardOpenOption.READ,
StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
// Try to acquire lock (returns immediately)
FileLock lock = channel.tryLock();
if (lock == null) {
System.out.println("Could not acquire lock - file locked by another process");
return;
}
try {
System.out.println("Lock acquired");
// Do work
} finally {
lock.release();
}
}
}
/**
* Partial file locking
*/
public static void partialLock(Path file) throws IOException {
try (FileChannel channel = FileChannel.open(file,
StandardOpenOption.READ,
StandardOpenOption.WRITE)) {
// Lock only first 1024 bytes
FileLock lock = channel.lock(0, 1024, false);
try {
System.out.println("Locked bytes 0-1024");
// Modify locked region
} finally {
lock.release();
}
}
}
}
Lock-Based Safe Write:
public class SafeFileWriter {
/**
* Write with exclusive lock
*/
public static void safeWrite(Path file, String content) throws IOException {
try (FileChannel channel = FileChannel.open(file,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING)) {
// Acquire exclusive lock
try (FileLock lock = channel.lock()) {
// Write content
channel.write(java.nio.ByteBuffer.wrap(content.getBytes()));
// Force to disk
channel.force(true);
}
}
}
/**
* Read with shared lock
*/
public static String safeRead(Path file) throws IOException {
try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {
try (FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {
return Files.readString(file);
}
}
}
}
Memory-Mapped Files
Basic Memory Mapping:
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* Memory-mapped file operations
*/
public class MemoryMappedFile {
/**
* Read using memory mapping
*/
public static void readMapped(Path file) throws IOException {
try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {
long size = channel.size();
// Map entire file to memory
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY,
0,
size
);
// Read from buffer
byte[] data = new byte[(int) size];
buffer.get(data);
System.out.println("Read " + data.length + " bytes");
}
}
/**
* Write using memory mapping
*/
public static void writeMapped(Path file, byte[] data) throws IOException {
try (FileChannel channel = FileChannel.open(file,
StandardOpenOption.CREATE,
StandardOpenOption.READ,
StandardOpenOption.WRITE)) {
// Map file to memory
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE,
0,
data.length
);
// Write to buffer
buffer.put(data);
// Force to disk
buffer.force();
}
}
/**
* Large file processing with memory mapping
*/
public static long processLargeFile(Path file) throws IOException {
try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {
long size = channel.size();
long chunkSize = 1024 * 1024 * 100; // 100 MB chunks
long position = 0;
long checksum = 0;
while (position < size) {
long remaining = size - position;
long mapSize = Math.min(remaining, chunkSize);
// Map chunk
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY,
position,
mapSize
);
// Process chunk
for (int i = 0; i < mapSize; i++) {
checksum += buffer.get() & 0xFF;
}
position += mapSize;
}
return checksum;
}
}
}
Shared Memory Between Processes:
/**
* Shared memory region for IPC
*/
public class SharedMemoryRegion {
private final Path file;
private final int size;
public SharedMemoryRegion(Path file, int size) throws IOException {
this.file = file;
this.size = size;
// Create file if doesn't exist
if (!Files.exists(file)) {
try (FileChannel channel = FileChannel.open(file,
StandardOpenOption.CREATE,
StandardOpenOption.READ,
StandardOpenOption.WRITE)) {
// Allocate space
channel.write(java.nio.ByteBuffer.allocate(size));
}
}
}
/**
* Writer process
*/
public void write(String message) throws IOException {
try (FileChannel channel = FileChannel.open(file,
StandardOpenOption.READ,
StandardOpenOption.WRITE)) {
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE,
0,
size
);
byte[] bytes = message.getBytes();
buffer.putInt(bytes.length);
buffer.put(bytes);
buffer.force();
}
}
/**
* Reader process
*/
public String read() throws IOException {
try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY,
0,
size
);
int length = buffer.getInt();
byte[] bytes = new byte[length];
buffer.get(bytes);
return new String(bytes);
}
}
}
Temporary Files and Directories
Creating Temporary Files:
// Create temp file in system temp directory
Path tempFile = Files.createTempFile("upload-", ".tmp");
System.out.println("Temp file: " + tempFile);
// Create temp file in specific directory
Path tempDir = Path.of("/var/tmp");
Path tempFile2 = Files.createTempFile(tempDir, "data-", ".bin");
// With file attributes
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-------");
FileAttribute<Set<PosixFilePermission>> attr =
PosixFilePermissions.asFileAttribute(perms);
Path secureTempFile = Files.createTempFile("secret-", ".dat", attr);
// Delete on exit (not recommended for long-running apps)
tempFile.toFile().deleteOnExit();
// Better: use try-with-resources pattern
try (var ignored = tempFile) {
// Use temp file
Files.writeString(tempFile, "temporary data");
// Process file
} finally {
// Clean up
Files.deleteIfExists(tempFile);
}
Creating Temporary Directories:
// Create temp directory
Path tempDir = Files.createTempDirectory("work-");
System.out.println("Temp directory: " + tempDir);
// Create in specific location
Path tempDir2 = Files.createTempDirectory(
Path.of("/var/tmp"),
"cache-"
);
// With attributes
Path secureTempDir = Files.createTempDirectory("secure-", attr);
// Use and clean up
try {
// Work in temp directory
Path workFile = tempDir.resolve("data.txt");
Files.writeString(workFile, "content");
// Process files
} finally {
// Delete entire directory tree
deleteDirectory(tempDir);
}
private static void deleteDirectory(Path directory) throws IOException {
if (!Files.exists(directory)) return;
Files.walk(directory)
.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
System.err.println("Failed to delete: " + path);
}
});
}
Temporary File Manager:
/**
* Manage temporary files with automatic cleanup
*/
public class TempFileManager implements AutoCloseable {
private final Set<Path> tempFiles = ConcurrentHashMap.newKeySet();
private final Path baseDir;
public TempFileManager() throws IOException {
this.baseDir = Files.createTempDirectory("app-");
System.out.println("Temp directory: " + baseDir);
}
/**
* Create tracked temp file
*/
public Path createTempFile(String prefix, String suffix) throws IOException {
Path tempFile = Files.createTempFile(baseDir, prefix, suffix);
tempFiles.add(tempFile);
return tempFile;
}
/**
* Create tracked temp directory
*/
public Path createTempDirectory(String prefix) throws IOException {
Path tempDir = Files.createTempDirectory(baseDir, prefix);
tempFiles.add(tempDir);
return tempDir;
}
/**
* Clean up specific file
*/
public void cleanup(Path path) {
try {
if (Files.isDirectory(path)) {
deleteDirectory(path);
} else {
Files.deleteIfExists(path);
}
tempFiles.remove(path);
} catch (IOException e) {
System.err.println("Cleanup failed: " + path);
}
}
@Override
public void close() {
// Clean up all tracked files
tempFiles.forEach(this::cleanup);
// Remove base directory
try {
Files.deleteIfExists(baseDir);
} catch (IOException e) {
System.err.println("Failed to delete base dir: " + baseDir);
}
}
}
// Usage
try (TempFileManager manager = new TempFileManager()) {
Path temp1 = manager.createTempFile("data-", ".txt");
Path temp2 = manager.createTempDirectory("work-");
// Use temp files
Files.writeString(temp1, "data");
// Automatic cleanup on close
}
Symbolic Links
Creating and Managing Symlinks:
// Create symbolic link
Path target = Path.of("/var/data/original.txt");
Path link = Path.of("/tmp/link.txt");
Files.createSymbolicLink(link, target);
// Read link target
Path linkTarget = Files.readSymbolicLink(link);
System.out.println("Link points to: " + linkTarget);
// Check if path is symbolic link
boolean isSymlink = Files.isSymbolicLink(link);
// Follow vs don't follow links
BasicFileAttributes attrs1 = Files.readAttributes(link,
BasicFileAttributes.class,
LinkOption.NOFOLLOW_LINKS); // Read link itself
BasicFileAttributes attrs2 = Files.readAttributes(link,
BasicFileAttributes.class); // Read target
// Copy symlink (not target)
Path linkCopy = Path.of("/tmp/link-copy.txt");
Files.copy(link, linkCopy, LinkOption.NOFOLLOW_LINKS);
// Resolve real path (follows symlinks)
Path realPath = link.toRealPath();
System.out.println("Real path: " + realPath);
Hard Links:
// Create hard link
Path original = Path.of("/data/file.txt");
Path hardLink = Path.of("/backup/file-link.txt");
Files.createLink(hardLink, original);
// Both paths refer to same inode
// Deleting one doesn't affect the other
Custom File Systems
ZIP File System:
import java.net.URI;
import java.nio.file.*;
import java.util.Map;
/**
* Work with ZIP files as file systems
*/
public class ZipFileSystemExample {
/**
* Create new ZIP file
*/
public static void createZip(Path zipPath, Map<String, String> files)
throws IOException {
Map<String, String> env = Map.of("create", "true");
URI uri = URI.create("jar:" + zipPath.toUri());
try (FileSystem zipFs = FileSystems.newFileSystem(uri, env)) {
// Add files to ZIP
for (Map.Entry<String, String> entry : files.entrySet()) {
Path pathInZip = zipFs.getPath(entry.getKey());
// Create parent directories
Path parent = pathInZip.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
// Write file
Files.writeString(pathInZip, entry.getValue());
}
}
}
/**
* Read from ZIP file
*/
public static void readZip(Path zipPath) throws IOException {
URI uri = URI.create("jar:" + zipPath.toUri());
try (FileSystem zipFs = FileSystems.newFileSystem(uri, Map.of())) {
Path root = zipFs.getPath("/");
// Walk ZIP contents
Files.walk(root).forEach(path -> {
try {
if (Files.isRegularFile(path)) {
String content = Files.readString(path);
System.out.println("File: " + path);
System.out.println("Content: " + content);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
/**
* Update file in ZIP
*/
public static void updateZip(Path zipPath, String fileName, String newContent)
throws IOException {
URI uri = URI.create("jar:" + zipPath.toUri());
try (FileSystem zipFs = FileSystems.newFileSystem(uri, Map.of())) {
Path fileInZip = zipFs.getPath(fileName);
Files.writeString(fileInZip, newContent);
}
}
/**
* Extract ZIP contents
*/
public static void extractZip(Path zipPath, Path destDir) throws IOException {
URI uri = URI.create("jar:" + zipPath.toUri());
try (FileSystem zipFs = FileSystems.newFileSystem(uri, Map.of())) {
Path root = zipFs.getPath("/");
Files.walk(root).forEach(source -> {
try {
if (Files.isRegularFile(source)) {
Path dest = destDir.resolve(root.relativize(source).toString());
// Create parent directories
Files.createDirectories(dest.getParent());
// Copy file
Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
// Example usage
public static void main(String[] args) throws IOException {
Path zipPath = Path.of("archive.zip");
// Create ZIP
Map<String, String> files = Map.of(
"readme.txt", "This is a readme",
"data/config.json", "{\"version\": \"1.0\"}",
"data/users.csv", "id,name\n1,Alice\n2,Bob"
);
createZip(zipPath, files);
// Read ZIP
readZip(zipPath);
// Update file in ZIP
updateZip(zipPath, "readme.txt", "Updated readme content");
// Extract ZIP
extractZip(zipPath, Path.of("extracted"));
}
}
JAR File System:
/**
* Access JAR files as file systems
*/
public class JarFileSystemExample {
public static void listJarContents(Path jarPath) throws IOException {
URI uri = URI.create("jar:" + jarPath.toUri());
try (FileSystem jarFs = FileSystems.newFileSystem(uri, Map.of())) {
Path root = jarFs.getPath("/");
Files.walk(root)
.filter(Files::isRegularFile)
.forEach(System.out::println);
}
}
public static void readClassFile(Path jarPath, String className)
throws IOException {
URI uri = URI.create("jar:" + jarPath.toUri());
try (FileSystem jarFs = FileSystems.newFileSystem(uri, Map.of())) {
String classPath = className.replace('.', '/') + ".class";
Path classFile = jarFs.getPath(classPath);
if (Files.exists(classFile)) {
byte[] bytes = Files.readAllBytes(classFile);
System.out.println("Class size: " + bytes.length + " bytes");
}
}
}
}
File Stores and Space Information
File Store Information:
/**
* Get file system space information
*/
public class FileStoreInfo {
public static void printFileStoreInfo(Path path) throws IOException {
FileStore store = Files.getFileStore(path);
System.out.println("Name: " + store.name());
System.out.println("Type: " + store.type());
System.out.println("Read-only: " + store.isReadOnly());
long total = store.getTotalSpace();
long usable = store.getUsableSpace();
long unallocated = store.getUnallocatedSpace();
System.out.println("Total space: " + formatSize(total));
System.out.println("Usable space: " + formatSize(usable));
System.out.println("Unallocated space: " + formatSize(unallocated));
System.out.println("Used: " + formatSize(total - unallocated));
// Usage percentage
double usedPercent = ((total - unallocated) * 100.0) / total;
System.out.printf("Usage: %.2f%%%n", usedPercent);
}
public static void printAllFileStores() {
for (FileStore store : FileSystems.getDefault().getFileStores()) {
try {
System.out.println("\n" + store.name());
System.out.println(" Type: " + store.type());
System.out.println(" Total: " + formatSize(store.getTotalSpace()));
System.out.println(" Available: " + formatSize(store.getUsableSpace()));
} catch (IOException e) {
System.err.println(" Error: " + e.getMessage());
}
}
}
private static String formatSize(long bytes) {
if (bytes < 1024) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(1024));
String pre = "KMGTPE".charAt(exp - 1) + "";
return String.format("%.2f %sB", bytes / Math.pow(1024, exp), pre);
}
/**
* Check if enough space available
*/
public static boolean hasEnoughSpace(Path path, long requiredBytes)
throws IOException {
FileStore store = Files.getFileStore(path);
return store.getUsableSpace() >= requiredBytes;
}
}
Real-World Example: Document Archive System
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.time.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.zip.*;
/**
* Complete document archive system with compression,
* indexing, and retrieval
*/
public class DocumentArchiveSystem implements AutoCloseable {
private final Path archiveDir;
private final Path indexFile;
private final Map<String, DocumentMetadata> index = new ConcurrentHashMap<>();
public static class DocumentMetadata {
public final String id;
public final String originalName;
public final Path archivePath;
public final long size;
public final Instant archivedAt;
public final Map<String, String> tags;
public DocumentMetadata(String id, String originalName, Path archivePath,
long size, Instant archivedAt, Map<String, String> tags) {
this.id = id;
this.originalName = originalName;
this.archivePath = archivePath;
this.size = size;
this.archivedAt = archivedAt;
this.tags = tags;
}
}
public DocumentArchiveSystem(Path archiveDir) throws IOException {
this.archiveDir = archiveDir;
this.indexFile = archiveDir.resolve("index.dat");
Files.createDirectories(archiveDir);
loadIndex();
}
/**
* Archive a document
*/
public String archiveDocument(Path document, Map<String, String> tags)
throws IOException {
// Generate unique ID
String id = UUID.randomUUID().toString();
// Create archive path
String yearMonth = LocalDate.now().toString().substring(0, 7);
Path archiveSubdir = archiveDir.resolve(yearMonth);
Files.createDirectories(archiveSubdir);
Path archivePath = archiveSubdir.resolve(id + ".gz");
// Compress and archive
compressFile(document, archivePath);
// Create metadata
DocumentMetadata metadata = new DocumentMetadata(
id,
document.getFileName().toString(),
archivePath,
Files.size(archivePath),
Instant.now(),
new HashMap<>(tags)
);
// Update index
index.put(id, metadata);
saveIndex();
System.out.println("Archived: " + document.getFileName() + " -> " + id);
return id;
}
/**
* Retrieve document
*/
public void retrieveDocument(String id, Path destination) throws IOException {
DocumentMetadata metadata = index.get(id);
if (metadata == null) {
throw new IllegalArgumentException("Document not found: " + id);
}
decompressFile(metadata.archivePath, destination);
System.out.println("Retrieved: " + id + " -> " + destination);
}
/**
* Search by tags
*/
public List<DocumentMetadata> search(Map<String, String> searchTags) {
return index.values().stream()
.filter(doc -> matchesTags(doc.tags, searchTags))
.collect(Collectors.toList());
}
private boolean matchesTags(Map<String, String> docTags,
Map<String, String> searchTags) {
return searchTags.entrySet().stream()
.allMatch(entry -> entry.getValue().equals(docTags.get(entry.getKey())));
}
/**
* List all documents
*/
public List<DocumentMetadata> listAll() {
return new ArrayList<>(index.values());
}
/**
* Delete archived document
*/
public boolean deleteDocument(String id) throws IOException {
DocumentMetadata metadata = index.remove(id);
if (metadata == null) {
return false;
}
Files.deleteIfExists(metadata.archivePath);
saveIndex();
System.out.println("Deleted: " + id);
return true;
}
/**
* Get statistics
*/
public void printStatistics() throws IOException {
long totalSize = index.values().stream()
.mapToLong(doc -> doc.size)
.sum();
System.out.println("Total documents: " + index.size());
System.out.println("Total size: " + formatSize(totalSize));
// Space usage
FileStore store = Files.getFileStore(archiveDir);
System.out.println("Archive location: " + store.name());
System.out.println("Available space: " + formatSize(store.getUsableSpace()));
}
/**
* Compress file
*/
private void compressFile(Path source, Path destination) throws IOException {
try (InputStream in = Files.newInputStream(source);
OutputStream out = Files.newOutputStream(destination,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
GZIPOutputStream gzip = new GZIPOutputStream(out)) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
gzip.write(buffer, 0, len);
}
}
}
/**
* Decompress file
*/
private void decompressFile(Path source, Path destination) throws IOException {
try (InputStream in = Files.newInputStream(source);
GZIPInputStream gzip = new GZIPInputStream(in);
OutputStream out = Files.newOutputStream(destination,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
byte[] buffer = new byte[8192];
int len;
while ((len = gzip.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
}
/**
* Load index from disk
*/
private void loadIndex() throws IOException {
if (!Files.exists(indexFile)) {
return;
}
try (ObjectInputStream ois = new ObjectInputStream(
Files.newInputStream(indexFile))) {
@SuppressWarnings("unchecked")
Map<String, DocumentMetadata> loaded =
(Map<String, DocumentMetadata>) ois.readObject();
index.putAll(loaded);
} catch (ClassNotFoundException e) {
throw new IOException("Failed to load index", e);
}
}
/**
* Save index to disk
*/
private void saveIndex() throws IOException {
Path tempFile = Files.createTempFile(archiveDir, "index-", ".tmp");
try (ObjectOutputStream oos = new ObjectOutputStream(
Files.newOutputStream(tempFile))) {
oos.writeObject(new HashMap<>(index));
}
Files.move(tempFile, indexFile,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);
}
private static String formatSize(long bytes) {
if (bytes < 1024) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(1024));
return String.format("%.2f %sB",
bytes / Math.pow(1024, exp),
"KMGTPE".charAt(exp - 1));
}
@Override
public void close() throws IOException {
saveIndex();
}
// Example usage
public static void main(String[] args) throws IOException {
try (DocumentArchiveSystem archive =
new DocumentArchiveSystem(Path.of("/var/archive"))) {
// Archive documents
String id1 = archive.archiveDocument(
Path.of("invoice.pdf"),
Map.of("type", "invoice", "year", "2024", "customer", "ACME")
);
String id2 = archive.archiveDocument(
Path.of("contract.pdf"),
Map.of("type", "contract", "year", "2024", "customer", "ACME")
);
// Search
List<DocumentMetadata> results = archive.search(
Map.of("customer", "ACME", "year", "2024")
);
System.out.println("Found " + results.size() + " documents");
// Retrieve
archive.retrieveDocument(id1, Path.of("retrieved-invoice.pdf"));
// Statistics
archive.printStatistics();
}
}
}
Best Practices
1. Use Appropriate Tools:
// Memory-mapped for large file processing
// File locking for concurrent access
// Temp files for intermediate results
2. Clean Up Resources:
// Always clean up temp files
try (TempFileManager manager = new TempFileManager()) {
// Use temp files
} // Automatic cleanup
3. Handle Platform Differences:
// Check file system capabilities
boolean supportsPosix = FileSystems.getDefault()
.supportedFileAttributeViews()
.contains("posix");
4. Use Atomic Operations:
// Atomic file replacement
Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE);
5. Monitor Resource Usage:
// Check available space before operations
if (!hasEnoughSpace(path, requiredSize)) {
throw new IOException("Insufficient disk space");
}
These advanced patterns enable sophisticated file system operations for production Java applications.