Сегодня был приятно удивлен тем, насколько можно увеличить производительность чтения файлов в Java, всего лишь написав небольшую обертку вокруг FileChannel.
Задача была поставлена такая: нужно в очень большом файле заменить строку с ошибочными данными на пробелы, не меняя размера файла. То есть найти строку с нужным номером, пропустив все остальные, и заменить ее на пустую.
Написал сначала с использованием RandomAccessFile в режиме "rw". Читаем строку, инкрементим номер, если наш, удаляем и выходим. На файле в 2гб удаление в середине файла занимало минут 5.
Начал копать, заменил readLine на свой skipLine, который не создает строку, а только пропускает байты. Результат примерно тот же.
Решил заменить RandomAccessFile на FileChannel. Открываем буфер и читаем. Никакой разницы. Почесал репу ещё немного, увеличил буфер до 10мб и файл прочитался за 2-3 секунды. Вот как выглядит это чудо:
public class MappedFile { long pointer; long size; private final FileChannel channel; public MappedFile(FileChannel channel) throws IOException { size = channel.size(); this.channel = channel; channel.position(0); createBuffer(); } private void createBuffer() throws IOException { long bufferSize = bufferSize(); buffer = channel.map(MapMode.READ_ONLY, pointer, bufferSize); buffer.load(); } public void close() throws IOException { channel.close(); } public long getFilePointer() { return pointer + buffer.position(); } private long bufferSize() { long maximum = 10000000; if (maximum > size - pointer) { return size - pointer; } else { return maximum; } } public byte read() throws IOException { if (!buffer.hasRemaining()) { pointer += buffer.limit(); createBuffer(); } return buffer.get(); } public void seek(long cur) throws IOException { if (cur < pointer + buffer.capacity()) { long newPosition = pointer - cur; buffer.position((int) newPosition); } else { pointer = cur; createBuffer(); } } public boolean isAnythingLeftToRead() { return pointer < size; } MappedByteBuffer buffer; }
Понятно, что писать в него нельзя, но нам ведь достаточно только указателя, дальше можно открыть RandomAccessFile и записать все, что нужно.