— Привет, Амиго!

— Привет, Билаабо! Как жизнь?

— Отлично. Вчера выводил паразитов, но пока не очень-то получается. А потом опять пришлось ночевать в мусорном баке.

— Т.е. все по-прежнему отлично?

— Можно и так сказать.

— Гуд. А что у нас будет сегодня?

— Сегодня я тебе расскажу про класс RandomAccessFile.

RandomAccessFile и т.д. - 1

Дело в том, что FileInputStream и FileOutputStream представляют файлы в виде потоков: читать из них и писать в них можно только последовательно.

Это не всегда очень-то удобно. Иногда тебе нужно записать пару строк в середину файла, или прочитать пару страниц текста в конце многомегабайтного файла. Читать для этого весь файл не очень эффективно.

Для решения этой проблемы был создан класс RandomAccessFile. С его помощью можно писать в любое место файла, читать из него, а также писать и читать файл одновременно.

— Как интересно.

— Ага. Очень удобно на самом деле.

— А как читать из произвольного места?

— Все довольно просто. Представь, что у тебя открыт текстовый редактор «блокнот». В нем есть курсор. Когда ты что-то печатаешь, текст добавляется в том месте, где он стоит. С чтением файла то же самое. Чтение происходит в том месте, где стоит «курсор». При чтении/записи он сам автоматически сдвигается.

Давай, я лучше покажу тебе пример:

Чтение файла:
//r- read, файл открыт только для чтения
RandomAccessFile raf = new RandomAccessFile("input.txt", "r"); 

//перемещаем «курсор» на 100-й символ.
raf.seek(100);

//читаем строку, начиная с текущего положения курсора и до конца строки
String text = raf.readLine();

//закрываем файл
raf.close();

В этом примере я хотел бы обратить твое внимание на две вещи:

Во-первых, создание объекта RandomAccessFile. Вторым параметром идет буква r. Это означает, что файл открыт для чтения (r- read). Если ты хочешь открыть файл для чтения и записи, в конструктор надо передать “rw” вместо “r”.

Во-вторых, обрати внимание на метод seek. С помощью этого метода можно прыгать по файлу и менять позицию курсора для текущей операции чтения/записи. Сразу при открытии файла «курсор» устанавливается на 0-й байт. Или точнее – перед нулевым байтом.

— Правильно ли я понял, что мы открыли файл, и курсор был в его самом начале – на позиции 0. Затем мы вызвали seek и он переместился на 100-й байт. А когда вызвали readLine, то чтение уже было начиная с сотого байта. Так?

— Да. Только хочу обратить твое внимание на то, что метод seek позволяет произвольно прыгать по файлу. Пример:

Чтение файла:
//r- read, файл открыт только для чтения
RandomAccessFile raf = new RandomAccessFile("input.txt", "r");
// «курсор» стоит на 0-м символе.
String text1 = raf.readLine();

//перемещаем «курсор» на 100-й символ.
raf.seek(100);
String text2 = raf.readLine();

//перемещаем «курсор» на 0-й символ.
raf.seek(0);
String text3 = raf.readLine();

//закрываем файл
raf.close();

В данном примере мы вначале прочитали строку, начиная с 0-го байта. Затем прыгнули на сотый байт и прочитали строку там. Затем снова прыгнули на 0-й байт и прочитали строку. Т.е. text1 и text3 – это идентичные строки.

— Ага. Ситуация начинает проясняться.

— Отлично. Тогда вот тебе еще один пример:

Чтение файла:
//rw- read/write, файл открыт и для чтения и для записи
RandomAccessFile raf = new RandomAccessFile("seek.txt", "rw");

//пишем в файл строку, начиная с 0-го байта
raf.writeBytes("It is a string");

//ставим курсор на 8-й символ
raf.seek(8);
//печатаем в файл строку surprise!
raf.writeBytes("surprise!");

//закрываем файл
raf.close();

Тут мы открываем файл для чтения и записи – в конструктор передаем «rw» (read/write).

Затем пишем в файл строку «It is a string».

Затем переставляем курсор на 8-й байт (как раз на начало слова string)

Затем пишем в файл строку «surprise

В результате файл будет содержать «It is a surprise

— Т.е. байты не вставляются в середину файла, а заменяют те, которые там были?

— Ага.

— А если мы установим курсор в самый конец файла?

— Тогда байты будут писаться в конец, а файл – удлиняться. Т.е. практически то же самое, когда ты пишешь текст в текстовом редакторе.

— Гм. Вроде все понятно. А можно полный список методов класса RandomAccessFile?

— Конечно. Держи:

Метод Описание
int read() Читает один байт и возвращает его
int read(byte b[], int off, int len) Читает массив байт
int read(byte b[]) Читает массив байт
void readFully(byte b[]) Читает массив байт, ждет, пока добавятся новые байты, если их не хватает для заполнения массива
int skipBytes(int n) Пропускает n байт. Т.е. перемещает курсор на n байт вперед
void write(int b) Пишет один байт в то место, где стоит курсор
void write(byte b[]) Пишет массив байт в то место, где стоит курсор
void write(byte b[], int off, int len) Пишет массив байт в то место, где стоит курсор
long getFilePointer() Возвращает номер байта, на который указывает «курсор». Может быть от 0 до «длины файла»
void seek(long pos) Перемещает «курсор», используемый для чтения/записи, в указанное место
long length() Возвращает длину файла
void setLength(long newLength) Устанавливает новую длину файла. Если файл был больше – он обрезается, если меньше – расширяется и новое место заполняется нулями
void close() Закрывает файл
boolean readBoolean() Читает boolean с текущей позиции курсора в файле
byte readByte() Читает byte с текущей позиции курсора в файле
char readChar() Читает char с текущей позиции курсора в файле
int readInt() Читает int с текущей позиции курсора в файле
long readLong() Читает long с текущей позиции курсора в файле
float readFloat() Читает float с текущей позиции курсора в файле
double readDouble() Читает double с текущей позиции курсора в файле
String readLine() Читает строку из файла и возвращает ее
void writeBoolean(boolean v) Пишет boolean в файл (начиная с позиции курсора)
void writeByte(int v) Пишет byte в файл (начиная с позиции курсора)
void writeChar(int v) Пишет char в файл (начиная с позиции курсора)
void writeInt(int v) Пишет int в файл (начиная с позиции курсора)
void writeLong(long v) Пишет long в файл (начиная с позиции курсора)
void writeFloat(float v) Пишет float в файл (начиная с позиции курсора)
void writeDouble(double v) Пишет double в файл (начиная с позиции курсора)
void writeBytes(String s) Пишет строку в файл (начиная с позиции курсора)
void writeChars(String s) Пишет строку в файл (начиная с позиции курсора)

— Гм. Ничего принципиально нового. Разве что пара методов seek()/getFilePointer() и length()/setLength().

— Да, Амиго. Все примерно то же самое. Но ведь удобно?

— Удобно. Спасибо тебе, Билаабо, за интересную лекцию и за те примеры, что ты мне дал.

— Рад помочь, друг Амиго!