Опубликован: 04.12.2009 | Доступ: свободный | Студентов: 8416 / 657 | Оценка: 4.30 / 3.87 | Длительность: 27:27:00
Лекция 7:

Важнейшие объектные типы

Работа с потоками ввода-вывода

Стандартные классы Java, инкапсулирующие работу с данными (оболочечные классы для числовых данных, классы String и StringBuffer, массивы и т.д.), не обеспечивают поддержку чтения этих данных из файлов или запись их в файл. Вместо этого используется весьма гибкая и современная, но не очень простая технология использования потоков (Streams).

Поток представляет накапливающуюся последовательность данных, поступающих из какого-то источника. Порция данных может быть считана из потока, при этом она из потока изымается. В потоке действует принцип очереди – "первым вошел, первым вышел".

В качестве источника данных потока может быть использован как стационарный источник данных (файл, массив, строка), так и динамический – другой поток. При этом в ряде случаев выход одного потока может служить входом другого. Поток можно представить себе как трубу, через которую перекачиваются данные, причем часто в таких "трубах" происходит обработка данных. Например, поток шифрования шифрует данные, полученные на входе, и при считывании из потока передает их в таком виде на выход. А поток архивации сжимает по соответствующему алгоритму входные данные и передает их на выход. "Трубы" потоков можно соединять друг с другом – выход одного со входом другого. Для этого в качестве параметра конструктора потока задается имя переменной, связанной с потоком - источником данных для создаваемого потока.

Буферизуемые потоки имеют хранящийся в памяти промежуточный буфер, из которого считываются выходные данные потока. Наличие такого буфера позволяет повысить производительность операций ввода-вывода, а также осуществлять дополнительные операции – устанавливать метки (маркеры) для какого-либо элемента, находящегося в буфере потока и даже делать возврат считанных элементов в поток (в пределах буфера).

Абстрактный класс InputStream ("входной поток") инкапсулирует модель входных потоков, позволяющих считывать из них данные. Абстрактный класс OutputStream ("выходной поток") - модель выходных потоков, позволяющих записывать в них данные. Абстрактные методы этих классов реализованы в классах-потомках.

Таблица 7.4. Методы класса InputStream
Метод Что делает
int available() Текущее количество байт, доступных для чтения из потока.
int read() Читает один байт из потока и возвращает его значение в виде целого, лежащего в диапазоне от 0 до 255. При достижении конца потока возвращает -1.
int read(byte[] b)

int read(byte[] b, int offset, int count)

Пытается прочесть b.length байт из потока в массив b. Возвращает число реально прочитанных байт. После достижения конца потока последующие считывания возвращают -1.
long skip(long count) Попытка пропускать (игнорировать) count байт из потока. count \le 0, пропуска байт нет. Возвращается реально пропущенное число байт. Если это значение \le 0, пропуска байт не было.
boolean markSupported() Возвращает true в случае, когда поток поддерживает операции mark и reset, иначе – false.
void mark(int limit) Ставит метку в текущей позиции начала потока. Используется для последующего вызова метода reset, с помощью которого считанные после установки метки данные возвращаются обратно в поток. Эту операцию поддерживают не все потоки. См. метод markSupported.
void reset() Восстанавливает предшествующее состояние данных в начале потока, возвращая указатель начала потока на помеченный до того меткой элемент. То есть считанные после установки метки данные возвращаются обратно в поток. Попытка вызова при отсутствии метки или выходе ее за пределы лимита приводит к возбуждению исключения IOException. Эту операцию поддерживают не все потоки. См. методы markSupported и mark.
void close() Закрытие потока. Последующие попытки чтения из этого потока приводят к возбуждению исключения IOException.

Все методы класса InputStream, кроме markSupported и mark, возбуждают исключение IOException – оно возникает при ошибке чтения данных.

Таблица 7.5. Методы класса OutputStream
Метод Что делает
void write(int b) Записывает один байт в поток. Благодаря использованию типа int можно использовать в качестве параметра целочисленное выражение без приведения его к типу byte. Напомним, что в выражениях "короткие" целые типы автоматически преобразуются к типу int.
void write(byte[] b)

void write(byte[] b, int offset, int count)

Записывает в поток массив байт. Если заданы параметры offset и count, записывается не весь массив, а count байт начиная с индекса offset.
void flush() Форсирует вывод данных из выходного буфера и его очистку. Необходимость этой операции связана с тем, что в большинстве случаев данные уходят "во внешний мир" на запись не каждый раз после вызова write, а только после заполнения выходного буфера. Таким образом, если операции записи разделены паузой (большим интервалом времени), и требуется, чтобы уход данных из выходного буфера совершался своевременно, после последнего вызова оператора write, предшествующего паузе, надо вызывать оператор flush.
void close() Закрытие потока. Последующие попытки записи в этот поток приводят к возбуждению исключения IOException.

Все методы этого класса возбуждают IOException в случае ошибки записи.

Не все классы потоков являются потомками InputStream/OutputStream. Для чтения строк (в виде массива символов) используются потомки абстрактного класса java.io.Reader ("читатель"). В частности, для чтения из файла – класс FileReader. Аналогично, для записи строк используются классы - потомки абстрактного класса java.io.Writer ("писатель"). В частности, для записи массива символов в файл – класс FileWriter.

Имеется еще один важный класс для работы с файлами, не входящий в иерархии InputStream/OutputStream и Reader/ Writer. Это класс RandomAccessFile ("файл с произвольным доступом"), предназначенный для чтения и записи данных в произвольном месте файла. Такой файл с точки зрения класса RandomAccessFile представляет массив байт, сохраненных на внешнем носителе. Класс RandomAccessFile не является абстрактным, поэтому можно создавать его экземпляры.

Все операции чтения и записи начинаются с позиции, задаваемой файловым указателем перед началом операции. После каждой такой операции файловый указатель сдвигается к концу файла на число позиций (байт), соответствующее типу считанных данных. В случае, когда во время записи файловый указатель выходит за конец файла, размер файла автоматически увеличивается.

Таблица 7.6. Важнейшие методы класса RandomAccessFile
Метод Что делает
Общие параметры файла
long getFilePointer() Возвращает значение файлового указателя - текущую позицию в файле считывающей/записывающей головки. Индексация начинается с нуля и дается в числе байт.
long length() Длина файла (в байтах).
void setLength(long newLength) Изменение длины файла – она устанавливается равной newLength. Если newLength меньше текущей длины, файл укорачивается путем отбрасывания его конца. При этом если файловый указатель находился в отбрасываемой части, он устанавливается в конец файла, иначе его позиция не меняется. Если newLength больше текущей длины, файл увеличивает размер путем добавления в конец нового пространства, содержимое которого не гарантируется.
void close() Закрытие потока. Последующие попытки доступа к этому потоку приводят к возбуждению исключения IOException.
Побайтные операции установки позиции
void seek(long pos) Установка файлового указателя в позицию pos.
int skipBytes(int n) Пропуск n байт – передвижение считывающей/записывающей головки на n байт. Если n>0, то вперед, к концу файла. Если n\le 0, то позиция головки не меняется. Возвращается число реально пропущенных байт. Оно может быть меньше n – например, из-за достижения конца файла.
Побайтные операции чтения
int read() Читает один байт из потока и возвращает его значение в виде целого, лежащего в диапазоне от 0 до 255. При достижении конца потока возвращает -1.
int read(byte[] b) Пытается прочесть b.length байт из потока в массив b. Возвращает число реально прочитанных байт. После достижения конца потока последующие считывания возвращают -1.
int read(byte[] b, int offset, int count) Пытается прочесть count байт из потока в массив b, записывая их начиная с позиции offset. Возвращает число реально прочитанных байт. После достижения конца потока последующие считывания возвращают -1.
void readFully(byte[] b) Считывает из файла b.length байт, начиная с текущей позиции файлового указателя, и заполняет весь буферный массив b.
void readFully(byte[] b, int offset, int count) Считывает из файла в массив count байт, записывая их начиная с байта, имеющего позицию offset.
Побайтные операции записи
void write(int b) Запись в файл одного байта b. То же, что writeByte(b).
void write(byte[] b) Запись в файл всего массива байт b, т.е. b.length байт.
void write(byte[] b, int offset, int count) Запись в файл count байт массива b начиная с байта массива, имеющего индекс offset.
Чтение одиночных значений примитивного типа
boolean readBoolean() Считывает значение boolean
byte readByte() Считывает значение byte
short readShort() Считывает значение short
int readInt() Считывает значение int
long readLong() Считывает значение long
int readUnsignedByte() Считывает значение типа "беззнаковый байт"(тип отсутствует в Java) и преобразует в значение типа int
int readUnsignedShort() Считывает значение типа "беззнаковое короткое целое"(тип отсутствует в Java) и преобразует в значение типа int
char readChar() Считывает значение char
float readFloat() Считывает значение float
double readDouble() Считывает значение double
Запись одиночных значений примитивного типа
void writeBoolean(boolean v) Запись в файл значение boolean
void writeByte(int v) Запись в файл значение byte. Благодаря использованию типа int можно использовать в качестве параметра целочисленное выражение без приведения его к типу byte.
void writeShort(int v) Запись в файл значение short. Благодаря использованию типа int можно использовать в качестве параметра целочисленное выражение без приведения его к типу short.
void writeInt(int v) Запись в файл значение int
void writeLong(long v) Запись в файл значение long
void writeChar(int v) Запись в файл значение char. Благодаря использованию типа int можно использовать в качестве параметра целочисленное выражение без приведения его к типу char.
void writeFloat(float v) Запись в файл значение float
void writeDouble(double v) Запись в файл значение double
Чтение отдельных строк
String readLine() Считывает из файла строку символов от текущей позиции файлового указателя до места переноса на новую строку (символ возврата каретки или последовательность "\n" ) или конца файла. При этом старшие байты получившихся в строке символов UNICODE оказываются равными нулю, т.е. поддерживается только часть кодовой таблицы. Метод полезен для считывания текста, записанного в кодировке ANSI.
String readUTF Считывает из файла строку символов в кодировке UTF-8 от текущей позиции файлового указателя. Число последующих считываемых байт строки в кодировке UTF-8 задается первыми двумя считанными байтами.
Запись отдельных строк
void writeBytes(String s) Запись в файл строки s как последовательности байт, соответствующих символам строки. При этом каждому символу соответствует один записываемый байт, так как у каждого символа UNICODE отбрасывается старший из двух байт ( записывается младший байт). Используется для записи символов в кодировке ANSI. См. также метод writeChars, в котором записываются оба байта.
void writeChars(String s) Запись в файл строки s как последовательности байт, соответствующих символам строки. При этом каждому символу UNICODE соответствует два записываемых байта.
void writeUTF(String s) Запись в файл строки s как последовательности байт, соответствующих символам строки в кодировке UTF-8.

Все методы класса RandomAccessFile, кроме getChannel(), возбуждают исключение IOException – оно возникает при ошибке записи или ошибке доступа к данным.

В конструкторе класса требуется указать два параметра. Первый – имя файла или файловую переменную, второй – строку с модой доступа к файлу. Имеются следующие варианты:

  • "r"- от read,- "только читать"
  • "rw"- от read and write,- "читать и писать"
  • "rws"- от read and write synchronously,- "читать и писать с поддержкой синхронизации" (см. далее раздел про потоки, Threads)
  • "rwd"- от read and write to device,- "читать и писать с поддержкой синхронизации устройства" (см. далее раздел про потоки, Threads)

Например:

  • java.io.RandomAccessFile rf1=new java.io.RandomAccessFile("q.txt","r");
  • java.io.RandomAccessFile rf2=new java.io.RandomAccessFile(file,"rw");

Рассмотрим примеры, иллюстрирующие работу с потоками.

Пример чтения текста из файла:

File file;
javax.swing.JFileChooser fileChooser=new javax.swing.JFileChooser();
javax.swing.filechooser.FileFilter fileFilter=new SimpleFileFilter(".txt");

private void openMenuItemActionPerformed(java.awt.event.ActionEvent evt) {

    fileChooser.addChoosableFileFilter(fileFilter);
    if(fileChooser.showOpenDialog(null)!=fileChooser.APPROVE_OPTION){
        return;//Нажали Cancel
    };
    file = fileChooser.getSelectedFile();
    try{
        InputStream fileInpStream=new FileInputStream(file);
        int size=fileInpStream.available();
        fileInpStream.close();
        char[] buff=new char[size];
        Reader fileReadStream=new FileReader(file);
        int count=fileReadStream.read(buff);
        jTextArea1.setText(String.copyValueOf(buff));
        javax.swing.JOptionPane.showMessageDialog(null,
                "Прочитано "+ count+" байт");
        
        fileReadStream.close();
    } catch(Exception e){
        javax.swing.JOptionPane.showMessageDialog(null,
                "Ошибка чтения из файла \n"+file.getAbsolutePath());
    }
}

Переменной fileInpStream, которая будет использована для работы потока FileInputStream, мы задаем тип InputStream. Это очень характерный прием при работе с потоками, позволяющий при необходимости заменять в дальнейшем входной поток с FileInputStream на любой другой без изменения последующего кода. После выбора имени текстового файла с помощью файлового диалога мы создаем файловый поток:

fileInpStream=new FileInputStream(file);

Функция fileInpStream.available() возвращает число байт, которые можно считать из файла. После ее использования поток fileInpStream нам больше не нужен, и мы его закрываем. Поток FileReader не поддерживает метод available(), поэтому нам пришлось использовать поток типа FileInputStream.

Массив buff используется в качестве буфера, куда мы считываем весь файл.

Если требуется читать файлы большого размера, используют буфер фиксированной длины и в цикле считывают данные из файла блоками, равными длине буфера, до тех пор, пока число считанных байт не окажется меньше размера буфера. Это означает, что мы дошли до конца файла. В таком случае выполнение цикла следует прекратить. При такой организации программы нет необходимости вызывать метод available() и использовать поток типа FileInputStream.

В операторе

Reader fileReadStream=new FileReader(file);

используется тип Reader, а не FileReader, по той же причине, что до этого мы использовали InputStream, а не FileInputStream.

Построчное чтение из файла осуществляется аналогично, но содержимое блока try следует заменить на следующий код:

FileReader filReadStream=new FileReader(file);
BufferedReader bufferedIn=new BufferedReader(filReadStream);
String s="",tmpS="";
while((tmpS=bufferedIn.readLine())!=null)
    s+=tmpS+"\n";
jTextArea1.setText(s);
bufferedIn.close();

Пример записи текста в файл очень похож на пример чтения, но гораздо проще, так как не требуется вводить промежуточные буферы и потоки:

private void saveAsMenuItemActionPerformed(java.awt.event.ActionEvent evt) {
    fileChooser.addChoosableFileFilter(fileFilter);
    if(fileChooser.showSaveDialog(null)!=fileChooser.APPROVE_OPTION){
        return;//Нажали Cancel
    };
    file = fileChooser.getSelectedFile();
    try{
        Writer filWriteStream=new FileWriter(file);
        filWriteStream.write(jTextArea1.getText() );
        filWriteStream.close();
    } catch(Exception e){
        javax.swing.JOptionPane.showMessageDialog(null,
                   "Ошибка записи в файл \n"+file.getAbsolutePath());
    }
}
Полетаев Дмитрий
Полетаев Дмитрий
Не очень понятно про оболочечные Данные,ячейки памяти могут наверно размер менять,какое это значение те же операции только ячейки больше,по скорости тоже самое
Максим Старостин
Максим Старостин

Код с перемещением фигур не стирает старую фигуру, а просто рисует новую в новом месте. Точку, круг.