How do I read last n characters from a file?

In the following post you will learn how to read last n characters from a file. The JDK 7 introduces a new SeekableByteChannel interface which allows its implementation to change the position and the size of the byte channel. One of its implementation is the FileChannel class (java.nio.channels.FileChannel).

The FileChannel class make it possible to get hold the current position of where we are going to read from or write to a file. The code snippet below shows you how you can read the last 1000 characters from a log file.

package org.kodejava.examples.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FileReadLastCharacters {
    public static void main(String[] args) {
        //
        // Defines the path to the log file and creates a ByteBuffer.
        //
        Path logPath = 
                Paths.get("D:\Appservers\apache-tomcat-7.0.37\logs\catalina.out");
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        try {
            //
            // Creates FileChannel and open the file channel for read access.
            //
            FileChannel channel = FileChannel.open(logPath, StandardOpenOption.READ);

            //
            // Read a sequence of bytes from the channel into the buffer starting
            // at given file position, which is the channel size - 1000. Because
            // we are going to read the last 1000 characters from the file.
            //
            channel.read(buffer, channel.size() - 1000);
            System.out.println("Characters = " + new String(buffer.array()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The steps in the code snippet above are:

  1. Get the path to the log file.
  2. Create a ByteBuffer, a buffer where the read bytes to be transfered.
  3. Using the FileChannel.open() method we open  a file to be read and return a FileChannel object.
  4. The read() method of the FileChannel reads a sequence of bytes from the channel and transfer them to the given buffer starting and the position defined by channel.size() - 1000. This method returns the number of bytes read, possible zero, or -1 if the given position is greater than or equal to the file’s current size.
  5. Print out the buffered string.

How to get the file name when using WatchService?

package org.kodejava.example.nio;

import java.io.IOException;
import java.nio.file.*;

import static java.nio.file.StandardWatchEventKinds.*;

public class WatchServiceGetFilename {
    public static void main(String[] args) {
        try {
            //
            // Create a WatchService and register the logDir path with the
            // WatchService for ENTRY_CREATE.
            //
            WatchService watcher = FileSystems.getDefault().newWatchService();
            Path logDir = Paths.get("D:\Temp\logs");
            logDir.register(watcher, ENTRY_CREATE);

            while (true) {
                WatchKey key = watcher.take();
                for (WatchEvent event : key.pollEvents()) {
                    if (event.kind() == ENTRY_CREATE) {
                        //
                        // Get the name of created file.
                        //
                        WatchEvent ev = (WatchEvent) event;
                        Path filename = ev.context();

                        System.out.printf("A new file %s was created.%n",
                                filename.getFileName());
                    }
                }
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

How to monitor file or directory changes?

package org.kodejava.example.nio;

import java.io.IOException;
import java.nio.file.*;

import static java.nio.file.StandardWatchEventKinds.*;

public class FileWatchDemo {
    public static void main(String[] args) {
        try {
            //
            // Creates a instance of WatchService.
            //
            WatchService watcher = FileSystems.getDefault().newWatchService();

            //
            // Registers the logDir below with a watch service.
            //
            Path logDir = Paths.get("D:\Temp\logs");
            logDir.register(watcher,
                    ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);

            //
            // Monitor the logDir at listen for change notification.
            //
            while (true) {
                WatchKey key = watcher.take();
                for (WatchEvent event : key.pollEvents()) {
                    WatchEvent.Kind kind = event.kind();

                    if (kind == ENTRY_CREATE) {
                        System.out.println("Entry was created on log dir.");
                    } else if (kind == ENTRY_MODIFY) {
                        System.out.println("Entry was modified on log dir.");
                    } else if (kind == ENTRY_DELETE) {
                        System.out.println("Entry was deleted from log dir.");
                    }
                }
                key.reset();
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

How to read file using Files.newBufferedReader?

In the snippet below you’ll learn to open file for reading using Files.newBufferedReader() method in JDK 7. This method returns a java.io.BufferedReader which makes a backward compatibility with the old I/O system in Java.

To read a file you’ll need to provide a Path and the Charset to the newBufferedReader() method arguments.

package org.kodejava.example.nio;

import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FilesNewBufferedReader {
    public static void main(String[] args) {
        Path logFile = Paths.get("D:\Temp\logs\app.log");
        try (BufferedReader reader = Files.newBufferedReader(logFile,
                StandardCharsets.UTF_8)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

How to write file using Files.newBufferedWriter?

To open a file for writing in JDK 7 you can use the Files.newBufferedWriter() method. This method takes three arguments. We need to pass the Path, the Charset and a varargs of OpenOption.

For example, in the snippet below we pass the path of our log file, we use the StandardCharsets.UTF_8 charset and we use the StandardOpenOption.WRITE to open a file for writing. If you want to open a file and append its contents instead of rewriting it you can use the StandardOpenOption.APPEND.

package org.kodejava.example.nio;

import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class FilesNewBufferedWriter {
    public static void main(String[] args) {
        Path logFile = Paths.get("app.log");
        try (BufferedWriter writer = Files.newBufferedWriter(logFile,
                StandardCharsets.UTF_8, StandardOpenOption.WRITE)) {

            for (int i = 0; i < 10; i++) {
                writer.write(String.format("Message %s%n", i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Because we use the StandardOpenOption.WRITE we have to make sure that the file to be written is exists. If the file is not available we will get error like java.nio.file.NoSuchFileException.

How do I move a file in JDK 7?

In the following code snippet you will learn how to move a file using the java.nio.file.Files helper class of JDK 7. This class simplify how you can move file. To move file you need to define the Path of the source and the target file.

We use the Files.move() method to move the file by passing the source and target path. We can also define the CopyOptions of the move process. For example to tell the move operation to replace the target file if the file already exist we can use the StandardCopyOption.REPLACE_EXISTING option. This option is a varargs, that means we can pass multiple options.

package org.kodejava.example.nio;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.*;

public class FileMoveDemo {
    public static void main(String[] args) {
        //
        // Define the source and target of the file to be moved.
        //
        Path source = Paths.get("D:\Source\data.txt");
        Path target = Paths.get("D:\Backup\data.txt");

        try {
            //
            // Move file from source to target using the defined
            // configuration (REPLACE_EXISTING)
            //
            Files.move(source, target, REPLACE_EXISTING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

How do I find files in a directory using DirectoryStream?

In this example we are going to learn to use the DirectoryStream, which part of java.nio.file package, to find files in a directory. We are begin by creating a Path, the directory where the search will be conducted.

After that we create a DirectoryStream using Files.newDirectoryStream(). To create a directory stream we passed two arguments, the starting path and a glob expression. Below we use *.txt glob expression to filter all text files in the D:Workbench.

To get all the entries of the directory stream we can use a foreach loop as can be seen below. We iterate each entries, which is a Path object. And we print the entries file name using the getFileName() method.

package org.kodejava.example.nio;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FindFilesInDirectory {
    public static void main(String[] args) {
        Path dir = Paths.get("D:\Workbench");

        try (DirectoryStream stream = Files.newDirectoryStream(dir, "*.txt")) {
            for (Path entry : stream) {
                System.out.println(entry.getFileName());
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

You can also notice that in this example we are using the try-with-resource statement. This mean that the DirectoryStream will be closed appropriately when the process is done. This type of try statement is an improvement introduced in JDK 7.

How do I create and delete a file in JDK 7?

In this example you’ll learn how to create and delete a file. Using the new Files class helper from the JDK 7 you can create a file using the Files.createFile(Path) method. To delete a file you can use the Files.delete(Path) method.

Before create a file and delete a file we can check to see if the file is exist or not using the Files.exists(Path) method. In the code snippet below we’ll create a file when the file is not exist. And we’ll delete the file if the file is exist.

package org.kodejava.example.nio;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class CreateDeleteFile {
    public static void main(String[] args) {
        try {
            //
            // Create a config.cfg file under D:Temp directory.
            //
            Path path = Paths.get("D:\Temp\config.cfg");
            if (!Files.exists(path)) {
                Files.createFile(path);
            }

            //
            // Delete the path.cfg file specified by the Path.
            //
            if (Files.exists(path)) {
                Files.delete(path);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

How to recursively list all text files in a directory?

In this example you’ll learn how to use the Files.walkFileTree() to walk through file tree. This method requires two parameters. The first parameter is the starting file, in this example we’ll start from drive D:. And the second parameter is the file visitor to invoke for each file. Here we’ll create a file visitor call FindTextFilesVisitor which extend the java.nio.file.SimpleFileVisitor.

To get all the text files (files end with .txt) we override the visitFile() defined by the SimpleFileVisitor. In this method we check if the file ends with .txt extension and print the file name when the extension match. And we continue to walk the file tree by returning FileVisitResult.CONTINUE.

package org.kodejava.example.nio;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class WalkFileTree {
    public static void main(String[] args) {
        try {
            Path startDir = Paths.get("D:\");
            Files.walkFileTree(startDir, new FindTextFilesVisitor());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * FindTextFilesVisitor.
     */
    static class FindTextFilesVisitor extends SimpleFileVisitor {
        @Override
        public FileVisitResult visitFile(Path file,
                                         BasicFileAttributes attrs)
                throws IOException {
            if (file.toString().endsWith(".txt")) {
                System.out.println(file.getFileName());
            }
            return FileVisitResult.CONTINUE;
        }
    }
}

Instead of listing files, you can modify the code snippet above for instance use it to delete all the files that ends with .bak. Simply change the extension and replace the print out statement with a file delete statement in the visitFile() method.

How do I remove redundant elements from a Path?

To eliminate redundant elements from a Path we can use the Path.normalize() method. For example in the following code snippet. When try accessing the README file in the current directory the . symbol in the Path elements considered to be redundant, we don’t need it. That’s why we normalize the Path.

package org.kodejava.example.nio;

import java.nio.file.Path;
import java.nio.file.Paths;

public class PathNormalize {
    public static void main(String[] args) {
        //
        // The following Path contains a redundant element. The "." 
        // which basically point to the current directory can simply
        // removed when we are working on the current directory.
        //
        Path path = Paths.get("./README");
        System.out.println("Path = " + path);

        //
        // Removes redundant name elements from the path.
        //
        path = path.normalize();
        System.out.println("Path = " + path);
    }
}