使用 Java 的文件部分中最后一个换行符的位置



如何有效地确定文件中特定部分最后一个换行符的位置?

例如,我试过这个

BufferedReader br = new BufferedReader(new FileReader(file));
long length = file.length();
String line = null;
int tailLength = 0;
while ((line = br.readLine()) != null) {
    System.out.println(line);
    tailLength = line.getBytes().length;
}
int returnValue = length - tailLength;

但这只会返回整个文件中最后一个换行符的位置,而不是文件某个部分中的最后一个换行符。本节将由int start;int end;表示

我认为最有效的方法是从文件的末尾开始并分块读取。 然后,向后搜索第一行。

import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class FileUtils {
    static final int CHUNK_SIZE = 8 * 1024;
    public static long getLastLinePosition(Path path) throws IOException {
        try (FileChannel inChannel = FileChannel.open(path, StandardOpenOption.READ);
             @SuppressWarnings("unused")
             FileLock lock = inChannel.tryLock(0, Long.MAX_VALUE, true)) {
            long fileSize = inChannel.size();
            long mark = fileSize;
            long position;
            boolean ignoreCR = false;
            while (mark > 0) {
                position = Math.max(0, mark - CHUNK_SIZE);
                MappedByteBuffer mbb = inChannel.map(FileChannel.MapMode.READ_ONLY, position, Math.min(mark, CHUNK_SIZE));
                byte[] bytes = new byte[mbb.remaining()];
                mbb.get(bytes);
                for (int i = bytes.length - 1; i >= 0; i--, mark--) {
                    switch (bytes[i]) {
                        case 'n':
                            if (mark < fileSize) {
                                return mark;
                            }
                            ignoreCR = true;
                            break;
                        case 'r':
                            if (ignoreCR) {
                                ignoreCR = false;
                            } else if (mark < fileSize) {
                                return mark;
                            }
                            break;
                    }
                }
                mark = position;
            }
        }
        return 0;
    }
}

测试文件 :

abcrn
1234rn
defrn

输出 : 11

了解有关java.nio.channels.FileChanneljava.nio.MappedByteBuffer的更多信息:

  • http://tutorials.jenkov.com/java-nio/file-channel.html
  • https://examples.javacodegeeks.com/core-java/nio/filechannel/java-nio-channels-filechannel-example/
  • https://examples.javacodegeeks.com/core-java/nio/mappedbytebuffer/java-mappedbytebuffer-example/
  • http://tutorials.techmytalk.com/2014/11/05/java-nio-memory-mapped-files/
  • http://javarevisited.blogspot.nl/2012/01/memorymapped-file-and-io-in-java.html

编辑

如果您使用的是 Java 6,请将这些更改应用于上述代码:

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
public class FileUtils {
    static final int CHUNK_SIZE = 8 * 1024;
    public static long getLastLinePosition(String name) throws IOException {
        FileChannel inChannel = null;
        FileLock lock = null;
        try {
            inChannel = new RandomAccessFile(name, "r").getChannel();
            lock = inChannel.tryLock(0, Long.MAX_VALUE, true);
            // ...
        } finally {
            if (lock != null) {
                lock.release();
            }
            if (inChannel != null) {
                inChannel.close();
            }
        }
        return 0;
    }
}

选择理想缓冲区大小的提示:

  • https://stackoverflow.com/a/237495/3767784
  • https://stackoverflow.com/a/4638989/3767784
  • https://stackoverflow.com/a/19007819/3767784

不幸的是你不能,我不得不使用RandomAccessFile它有getFilePointer()方法,你可以在readLine()后调用,但它非常慢,不能识别UTF-8。

我最终实现了自己的字节计数行读取器。

当面对包含 unicode、格式错误或二进制内容的文件时,您的天真解决方案将严重失败。

最新更新