Image

Чтение и запись ByteBuffer в файл: неужели все настолько неудобно?

Я работаю с файлами произвольного доступа фиксированного размера. Мне нужно читать из них, в произвольных позициях, блоки фиксированного размера в ByteBuffer и записывать их обратно, тоже в произвольных позициях. Я пишу и читаю блоки, заведомо попадающие внутрь файла - примерно как в БД.

Естественно, применяется RandomAccessFile и его FileChannel.

Я использую две техники (переключаемые).

Первая простейшая - мапирование: FileChannel.map. Удобно, эффективно и пр. Однако, увы, в 1.6 авторы до сих пор не исправили ошибку (исправленную в early-release 1.7): при более чем одном мапировании сборщик мусора может глюкануть, и приложение "вылетит". Да и другие проблемы есть: скажем, файл нельзя удалить, пока последний MappedByteBuffer не будет утилизован сборщиком мусора.

Вторая техника - FileChannel.read/write. Вначале я особо не задумывался: просто вызывал read(ByteBuffer dst, long position) и write(ByteBuffer src, long position). Ведь у меня - дисковый файл плюс уверенность, что блок не выходит за пределы файла. И только сейчас коллега обратил мое внимание, что эти методы возвращают результат, и нигде не написано, что они обязаны прочитать/записать полный заказанный объем. Разве что в тоне "вероятно, может быть". Например: http://java.sun.com/javase/6/docs/api/java/nio/channels/ReadableByteChannel.html#read(java.nio.ByteBuffer)

Что ж, я написал следующие процедуры:

    public static void readAllBuffer(FileChannel fileChannel, long position, ByteBuffer dest) 
        throws IOException 
    {
        if (position < 0)
            throw new IllegalArgumentException("Negative position");
        long savePosition = fileChannel.position();
        try {
            fileChannel.position(position);
            ByteBuffer dup = dest.duplicate();
            dup.rewind();
            for (int ofs = 0, k = 0, n = dup.limit(); ofs < n; k++) {
                if (k > 2 * n) { // too many attempts
                    throw new EOFException("Cannot read " + n + " bytes from the file at the position " + position);
                }
                int res = fileChannel.read(dup);
                if (res < 0)
                    throw new EOFException("Cannot read " + n + " bytes from the file at the position " + position
                        + ": file is exhausted");
                ofs += res;
            }
        } finally {
            fileChannel.position(savePosition);
        }
    }

    public static void writeAllBuffer(FileChannel fileChannel, long position, ByteBuffer src) 
        throws IOException 
    {
        if (position < 0)
            throw new IllegalArgumentException("Negative position");
        long savePosition = fileChannel.position();
        try {
            fileChannel.position(position);
            ByteBuffer dup = src.duplicate();
            dup.rewind();
            for (int ofs = 0, k = 0, n = src.limit(); ofs < n; k++) {
                if (k > 2 * n) { // too many attempts
                    throw new EOFException("Cannot write " + n + " bytes to the file at the position " + position);
                }
                int res = fileChannel.write(dup);
                if (res < 0)
                    throw new EOFException("Cannot write " + n + " bytes to the file at the position " + position
                        + ": the disk if probably full");
                ofs += res;
            }
        } finally {
            fileChannel.position(savePosition);
        }
    }

(Комментарии пропускаю - JavaDoc нарушает форматирование LJ.)


Но неужели и правда я обязан писать таких "монстриков"? Ведь вроде самая что ни на есть стандартная задача. Может быть, есть готовое решение? Или где-то явно сказано, что FileChannel.read/write в случае RandomAccessFile вполне надежны?