Сегодня был приятно удивлен тем, насколько можно увеличить производительность чтения файлов в 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 и записать все, что нужно.
