Шрифт:
До сих пор нам приходилось иметь дело с последовательными файлами, содержимое которых вводилось и выводилось побайтно, т.е. строго по порядку. Но в Java предоставляется также возможность обращаться к хранящимся в файле данным в произвольном порядке. Для этой цели предусмотрен класс RandomAccessFile, инкапсулирующий файл с произвольным доступом. Класс RandomAccessFile не является производным от класса InputStream или OutputStream. Вместо этого он реализует интерфейсы Datalnput и DataOutput, в которых объявлены основные методы ввода-вывода. В нем поддерживаются также запросы позиционирования, т.е. возможность задавать положение указателя файла произвольным образом. Ниже приведен конструктор класса RandomAccessFile, которым мы будем пользоваться далее. RandomAccessFile(String имя_файла, String доступ) throws FileNotFoundException
Здесь конкретный файл указывается с помощью параметра имя_файла, а параметр доступ определяет, какой именно тип доступа будет использоваться для обращения к файлу. Если параметр доступ принимает значение "г", то данные могут читаться из файла, но не записываться в него. Если же указан тип доступа "rw", то файл открывается как для чтения, так и для записи.
Метод seek , общая форма объявления которого приведена ниже, служит для установки текущего положения указателя файла, void seek(long newPos) throws IOException
Здесь параметр newPos определяет новое положение указателя файла в байтах относительно начала файла. Операция чтения или записи, следующая после вызова метода seek , будет выполняться относительно нового положения указателя файла.
В классе RandomAccessFile определены методы read и write . Этот класс также реализует интерфейсы Datalnput и DataOuput, т.е. в нем доступны методы чтения и записи простых типов, например readlnt и writeDouble .
Ниже приведен пример программы, демонстрирующий ввод-вывод с произвольным доступом. В этой программе шесть значений типа double сначала записываются в файл, а затем читаются из него, причем порядок чтения их отличается от порядка записи. // Демонстрация произвольного доступа к файлам. // Для компиляции этого кода требуется JDK 7 // или более поздняя версия данного комплекта. import java.io.*; class RandomAccessDemo { public static void main(String args[]) { double data[] = { 19.4, 10.1, 123.54, 33.0, 87.9, 74.25 }; double d; // открыть и использовать файл с произвольным доступом // Файл с произвольным доступом открывается для записи и чтения. try (RandomAccessFile raf = new RandomAccessFile("random.dat", "rw")) { // записать значения в Файл for(int i=0; i < data.length; i++) { raf.writeDouble(data[i]); } //а теперь прочитать отдельные значения из файла // Для установки указателя файла служит метод seek. raf.seek(0); // найти первое значение типа double d = raf.readDouble; System.out.println("First value is " + d) ; raf.seek(8); // найти второе значение типа double d = raf.readDouble; System.out.println("Second value is " + d) ; raf.seek(8 * 3); // найти четвертое значение типа double d = raf.readDouble; System.out.println("Fourth value is " + d); System.out.println; // а теперь прочитать значения через одно System.out.println("Here is every other value: "); for(int i=0; i < data.length; i+=2) { raf.seek(8 * i); // найти i-e значение типа double d = raf.readDouble; System.out.print(d + " ") ; } } catch(IOException exc) { System.out.println("I/O Error: " + exc) ; } } }
Результат выполнения данной программы выглядит следующим образом: First value is 19.4 Second value is 10.1 Fourth value is 33.0 Here is every other value: 19.4 123.54 87.9
Обратите внимание на расположение каждого числового значения. Ведь значение типа double занимает 8 байтов, и поэтому каждое последующее число начинается на 8-байтовой границе предыдущего числа. Иными словами, первое числовое значение начинается на позиции нулевого байта, второе — на позиции 8-го байта, третье — на позиции 16-го байта и т.д. Поэтому для чтения четвертого числового значения нужно установить указатель файла на позиции 24-го байта при вызове метода seek . Применение символьных потоков в Java
Как следует из предыдущих разделов этой главы, байтовые потоки в Java довольно эффективны и удобны в употреблении. Но что касается ввода-вывода символов, то байтовые потоки далеки от идеала. Поэтому для этих целей в Java определены классы символьных потоков. На вершине иерархии классов, поддерживающих символьные потоки, находятся абстрактные классы Reader и Writer. Методы класса Reader приведены в табл. 10.7, а методы класса Writer — в табл. 10.8. В большинстве этих методов может быть сгенерировано исключение IOException. Методы, определенные в указанных абстрактных классах Reader и Writer, доступны во всех их подклассах. Таким образом, они образуют минимальный набор функций ввода-вывода, необходимых для всех символьных потоков.
Таблица 10.7. Методы, определенные в классе Reader Метод Описание abstract void close Закрывает поток ввода данных. При последующей попытке чтения генерируется исключение IOException void mark (int numChars) Ставит отметку на текущей позиции в потоке. Отметка доступна до тех пор, пока на будет прочитано количество символов, определяемое параметром numChars boolean markSupported Возвращает логическое значение true, если поток поддерживает методы mark и reset int read Возвращает целочисленное представление очередного символа из потока ввода. Если достигнут конец потока, возвращается значение -1 int read(char buffer[]) Предпринимает попытку прочитать количество байтов, определяемое выражением buffer, length, в массив buffer и возвращает фактическое количество успешно прочитанных символов. Если достигнут конец потока, возвращается значение -1 abstract int read(char buffer[], int offset, int numChars) Предпринимает попытку прочитать количество символов, определяемое параметром numChars, в массив buffer, начиная с элемента buffer [ offset]. Если достигнут конец потока, возвращается значение -1 int read(CharBuffer buffer) Предпринимает попытку заполнить буфер, определяемый параметром buffer, символами, прочитанными из входного потока. Если достигнут конец потока, возвращается значение -1. CharBuffer — это класс, представляющий последовательность символов, например строку boolean ready Возвращает логическое значение true, если следующий запрос на получение символа может быть выполнен немедленно. В противном случае возвращается логическое значение false void reset Устанавливает указатель ввода на помеченной ранее позиции long skip(long numChars) Пропускает количество символов, определяемое параметром numChars, в потоке ввода. Возвращает фактическое количество пропущенных символов
Таблица 10.8. Методы, определенные в классе Writer Метод Описание Writer append(char ch) Записывает символ ch в конец текущего потока. Возвращает ссылку на поток Writer append(CharSequence chars) Записывает символы chars в конец текущего потока. Возвращает ссылку на поток. CharSequence — это интерфейс, в котором описаны только операции чтения последовательности символов Writer append(CharSequence chars, int begin, int end) Записывает символы chars в конец текущего потока, начинаяс позиции, определяемой параметром begin, и кончая позицией, определяемой параметром end. Возвращает ссылку на поток. CharSequence — это интерфейс, в котором описаны только операции чтения последовательности символов abstract void close Закрывает поток вывода. При последующей попытке записи в поток генерируется исключение IOException abstract void flush Выводит текущее содержимое буфера на устройство. В результате выполнения данной операции буфер очищается void write(int ch) Записывает в вызывающий поток вывода один символ. Параметр ch относится к типу int, что позволяет вызывать данный метод в выражениях, не приводя результат их вычисления к типу char void write(char buffer[]) Записывает в вызывающий поток вывода массив символов buffer abstract void write(char buffer[], int offset, int numChars) Записывает в вызывающий поток вывода количество символов, определяемое параметром numChars, из массива buffer, начиная с элемента buffer[ offset ] void write(String str) Записывает в вызывающий поток вывода символьную строку str void write(String str, int offset, int numChars) Записывает в вызывающий поток вывода часть numChars символов из строки str, начиная с позиции, обозначаемой параметром offset Консольный ввод из символьных потоков
Если программа подлежит локализации, то при организации ввода с консоли символьным потокам следует отдать предпочтение перед байтовыми. А поскольку System.in — это байтовый поток, то для него придется построить оболочку в виде класса, производного от класса Reader. Наиболее подходящим для ввода с консоли является класс Buf feredReader, поддерживающий буферизованный поток ввода. Но объект типа Buf feredReader нельзя построить непосредственно из потока стандартного ввода System, in. Сначала нужно преобразовать байтовый поток в символьный. И для этой цели служит класс InputStreamReader, преобразующий байты в символы. Для того чтобы получить объект типа InputStreamReader, связанный с потоком стандартного ввода System, in, нужно воспользоваться следующим конструктором: InputStreamReader(InputStream inputStream)