Работа с файлами в Java

Содержание

Слайд 2

Классы байтовых потоков Фрагмент иерархии классов байтовых потоков

Классы байтовых потоков

Фрагмент иерархии классов байтовых потоков

Слайд 3

Классы байтовых потоков В Java так же как и в С++

Классы байтовых потоков

В Java так же как и в С++

для описания работы по вводу/выводу используется специальное понятие поток данных (stream). Поток данных связан с некоторым источником, или приемником, данных, способным получать или предоставлять информацию. Соответственно, потоки делятся на входящие – читающие данные и выходящие – передающие (записывающие) данные. Введение концепции stream позволяет отделить основную логику программы, обменивающейся информацией с любыми устройствами одинаковым образом, от низкоуровневых операций с такими устройствами ввода/вывода.
В Java потоки естественным образом представляются объектами. Описывающие их классы как раз и составляют основную часть пакета java.io. Все классы разделены на две части – одни осуществляют ввод данных, другие – вывод.
На рисунке слайда представлен фрагмент иерархии классов байтовых потоков. Классы байтовых потоков используются для работы с бинарными файлами.
Классы входных байтовых потоков наследуются от InputStream, а выходных – от OutputStream. Базовые, наиболее универсальные, классы позволяют считывать и записывать информацию в виде набора байт. Чтобы их было удобно применять в различных задачах, java.io содержит также классы, преобразующие любые данные в набор байт.
Например, если нужно сохранить результаты вычислений – набор значений типа double – в файл, то их можно сначала превратить в набор байт, а затем эти байты записать в файл. Аналогичные действия совершаются и в ситуации, когда требуется сохранить объект (т.е. его состояние) – преобразование в набор байт и последующая их запись в файл. Понятно, что при восстановлении данных в обоих рассмотренных случаях проделываются обратные действия – сначала считывается последовательность байт, а затем она преобразуется в нужный формат.
Слайд 4

Классы InputStream и OutputStream Методы класса InputStream: int read() int read(byte[]

Классы InputStream и OutputStream
Методы класса InputStream:
int read()
int read(byte[] buf)
int read(byte[]

buf, int offset, int len)
available()
close()
Методы класса OutputStream:
void write(int)
void write(byte[] buf)
void write(byte[] buf, int offset, int len)
flush()
close()
Слайд 5

Классы InputStream и OutputStream InputStream – это базовый класс для байтовых

Классы InputStream и OutputStream

InputStream – это базовый класс для байтовых потоков ввода. Методы

этого класса необходимы всем классам, которые наследуются от InputStream.
Метод read() является абстрактным и, поэтому определяется в классах-наследниках. Этот метод предназначен для считывания ровно одного байта из потока, однако возвращает при этом значение типа int.. Если достигнут конец потока, то есть в нем больше нет информации для чтения, то возвращаемое значение равно -1. Если же считать из потока данные не удается из-за каких-то ошибок, или сбоев, будет брошено исключение java.io.IOException. Этот класс наследуется от Exception, т.е. его всегда необходимо обрабатывать явно. 
На практике обычно приходится считывать не один, а сразу несколько байт – то есть массив байт. Для этого используется метод read(), где в качестве параметров передается массив byte[]. Возможна ситуация, когда в потоке байт содержится меньше, чем длина массива. Поэтому метод возвращает значение int, указывающее, сколько байт было реально считано. Понятно, что это значение может быть от 0 до величины длины переданного массива.
Если же мы изначально хотим заполнить не весь массив, а только его часть, то для этих целей используется метод read(), которому, кроме массива byte[], передаются еще два int значения. Первое – это позиция в массиве, с которой следует начать заполнение, второе – количество байт, которое нужно считать.
Метод available( ) возвращает значение типа int, которое показывает, сколько байт в потоке готово к считыванию. При этом не стоит путать количество байт, готовых к считыванию, с тем количеством байт, которые вообще можно будет считать из этого потока. Метод available() возвращает число – количество байт, именно на данный момент готовых к считыванию.
Когда работа с входным потоком данных окончена, его следует закрыть. Для этого вызывается метод close(). Этим вызовом будут освобождены все системные ресурсы, связанные с потоком.
Слайд 6

Классы InputStream и OutputStream Класс OutputStream – это базовый класс для

Классы InputStream и OutputStream

Класс OutputStream – это базовый класс для байтовых потоков вывода.
В классе OutputStream аналогичным

образом определяются три метода write() – один принимающий в качестве параметра int, второй – byte[] и третий –byte[], плюс два int -числа. Все эти методы ничего не возвращают ( void ).
Метод write(int) является абстрактным и поэтому реализован в классах-наследниках. Этот метод принимает в качестве параметра int, но реально записывает в поток только byte – младшие 8 бит в двоичном представлении. Остальные 24 бита будут проигнорированы. В случае возникновения ошибки этот метод бросает java.io.IOException, как, впрочем, и большинство методов, связанных с вводом-выводом.
Для записи в поток сразу некоторого количества байт методу write() передается массив байт. Или, если мы хотим записать только часть массива, то передаем массив byte[] и два int -числа – отступ и количество байт для записи. Понятно, что если указать неверные параметры – например, отрицательный отступ, отрицательное количество байт для записи, либо если сумма отступ плюс длина будет больше длины массива, – во всех этих случаях кидается исключение IndexOutOfBoundsException.
Класс выходного потока может использовать некоторый внутренний механизм для буферизации (временного хранения перед отправкой) данных. Чтобы убедиться, что данные записаны в поток, а не хранятся в буфере, вызывается метод flush(), определенный в OutputStream. В этом классе его реализация пустая, но если какой-либо из наследников использует буферизацию данных, то этот метод должен быть в нем переопределен.
Когда работа с потоком закончена, его следует закрыть. Для этого вызывается метод close(). Этот метод сначала освобождает буфер (вызовом метода flush ), после чего поток закрывается и освобождаются все связанные с ним системные ресурсы. Закрытый поток не может выполнять операции вывода и не может быть открыт заново. В классе OutputStream реализация метода close() не производит никаких действий.
Итак, классы InputStream и OutputStream определяют необходимые методы для работы с байтовыми потоками данных. Эти классы являются абстрактными. Их задача – определить общий интерфейс для классов, которые получают данные из различных источников. Такими источниками могут быть, например, массив байт, файл, строка и т.д. 
Слайд 7

Классы FileInputStream и FileOutputStream Пример 1:

Классы FileInputStream и FileOutputStream

Пример 1:

Слайд 8

Классы FileInputStream и FileOutputStream Пример 1:

Классы FileInputStream и FileOutputStream

Пример 1:

Слайд 9

Классы FileInputStream и FileOutputStream Пример 1:

Классы FileInputStream и FileOutputStream

Пример 1:

Слайд 10

Классы FileInputStream и FileOutputStream Класс FileInputStream используется для чтения данных из

Классы FileInputStream и FileOutputStream

Класс FileInputStream используется для чтения данных из файла. Конструктор

такого класса в качестве параметра принимает название файла, из которого будет производиться считывание. При указании строки имени файла нужно учитывать, что она будет напрямую передана операционной системе, поэтому формат имени файла и пути к нему может различаться на разных платформах. Если при вызове этого конструктора передать строку, указывающую на несуществующий файл или каталог, то будет брошено java.io.FileNotFoundException. Если же объект успешно создан, то при вызове его методов read() возвращаемые значения будут считываться из указанного файла.
Для записи байт в файл используется класс FileOutputStream. При создании объектов этого класса, то есть при вызовах его конструкторов, кроме имени файла, также можно указать, будут ли данные дописываться в конец файла (второй параметр равен true), либо файл будет перезаписан (второй параметр отсутствует, либо равен false). Если указанный файл не существует, то сразу после создания FileOutputStream он будет создан. При вызовах методов write() передаваемые значения будут записываться в этот файл. По окончании работы необходимо вызвать метод close(), чтобы сообщить системе, что работа по записи файла закончена.См. пример на слайде.
При работе с FileInputStream метод available() практически наверняка вернет длину файла, то есть число байт, сколько вообще из него можно считать.
В приведенном примере для наглядности закрытие потоков производилось сразу же после окончания их использования в основном блоке. Однако лучше закрывать потоки в finally блоке.
... }
finally {
try { inFile.close(); } catch(IOException e ){ }; }
Такой подход гарантирует, что поток будет закрыт и будут освобождены все связанные с ним системные ресурсы.
Слайд 11

Классы BufferedInputStream и BufferedOutputStream Пример 2:

Классы BufferedInputStream и BufferedOutputStream

Пример 2:

Слайд 12

Классы BufferedInputStream и BufferedOutputStream Пример 2:

Классы BufferedInputStream и BufferedOutputStream

Пример 2:

Слайд 13

Классы BufferedInputStream и BufferedOutputStream Пример 2:

Классы BufferedInputStream и BufferedOutputStream

Пример 2:

Слайд 14

Классы BufferedInputStream и BufferedOutputStream На практике при считывании с внешних устройств

Классы BufferedInputStream и BufferedOutputStream

На практике при считывании с внешних устройств

ввод данных почти всегда необходимо буферизировать. Для буферизации данных служат классы надстройки  BufferedInputStream и BufferedOutputStream.
Классы FilterI/OStream являются базовыми для надстроек и определяют общий интерфейс для надстраиваемых объектов. Потоки-надстройки не являются источниками данных. Они лишь модифицируют (расширяют) работу надстраиваемого потока.
BufferedInputStream содержит массив байт, который служит буфером для считываемых данных. То есть когда байты из потока считываются либо пропускаются (метод skip() ), сначала заполняется буферный массив, причем, из надстраиваемого потока загружается сразу много байт, чтобы не требовалось обращаться к нему при каждой операции read или skip.
BufferedOutputStream предоставляет возможность производить многократную запись небольших блоков данных без обращения к устройству вывода при записи каждого из них. Сначала данные записываются во внутренний буфер. Непосредственное обращение к устройству вывода и, соответственно, запись в него, произойдет, когда буфер заполнится. Инициировать передачу содержимого буфера на устройство вывода можно и явным образом, вызвав метод flush(). Так же буфер освобождается перед закрытием потока. При этом будет закрыт и надстраиваемый поток (так же поступает BufferedInputStream ).
Пример на слайде наглядно демонстрирует повышение скорости считывания данных из файла с использованием буфера. В данном случае не производилось никаких дополнительных вычислений, занимающих процессорное время, только запись и считывание из файла. При этом считывание с использованием буфера заняло в 10 (!) раз меньше времени, чем аналогичное без буферизации. Для более быстрого выполнения программы запись в файл производилась с буферизацией, однако ее влияние на скорость записи нетрудно проверить, убрав из программы строку, создающую BufferedOutputStream.
Классы BufferedI/OStream добавляют только внутреннюю логику обработки запросов, но не добавляют никаких новых методов.
Слайд 15

Классы DataInputStream и DataOutputStream Пример 3:

Классы DataInputStream и DataOutputStream

Пример 3:

Слайд 16

Классы DataInputStream и DataOutputStream Пример 3:

Классы DataInputStream и DataOutputStream

Пример 3:

Слайд 17

Классы DataInputStream и DataOutputStream Пример 3:

Классы DataInputStream и DataOutputStream

Пример 3:

Слайд 18

Классы DataInputStream и DataOutputStream До сих пор речь шла только о

Классы DataInputStream и DataOutputStream

До сих пор речь шла только

о считывании и записи в поток данных в виде byte. Для работы с другими примитивными типами данных классы-фильтры DataInputStream и DataOutputStream. Их место в иерархии классов ввода/вывода можно увидеть на диаграмме классов второго слайда.
Классы DataInputStream и DataOutputStream, соответственно, реализуют методы считывания и записи значений всех примитивных типов. При этом происходит конвертация этих данных в набор byte и обратно. Чтение необходимо организовать так, чтобы данные запрашивались в виде тех же типов, в той же последовательности, как и производилась запись. Если записать, например, int и long, а потом считывать их как short, чтение будет выполнено корректно, без исключительных ситуаций, но числа будут получены совсем другие. Это наглядно показано в примере на слайде.
Слайд 19

Классы ObjectInputStream и ObjectOutputStream Пример 4:

Классы ObjectInputStream и ObjectOutputStream

Пример 4:

Слайд 20

Классы ObjectInputStream и ObjectOutputStream Пример 4:

Классы ObjectInputStream и ObjectOutputStream

Пример 4:

Слайд 21

Классы ObjectInputStream и ObjectOutputStream Пример 4:

Классы ObjectInputStream и ObjectOutputStream

Пример 4:

Слайд 22

Классы ObjectInputStream и ObjectOutputStream Для объектов процесс преобразования в последовательность байт

Классы ObjectInputStream и ObjectOutputStream

Для объектов процесс преобразования в последовательность

байт и обратно организован несколько сложнее – объекты имеют различную структуру, хранят ссылки на другие объекты и т.д. Поэтому такая процедура получила специальное название - сериализация (serialization), обратное действие, – то есть воссоздание объекта из последовательности байт – десериализация.
Поскольку сериализованный объект – это последовательность байт, которую можно легко сохранить в файл, передать по сети и т.д., то и объект затем можно восстановить на любой машине, вне зависимости от того, где проводилась сериализация. Разумеется, Java позволяет не задумываться при этом о таких факторах, как, например, используемая операционная система на машине-отправителе и получателе. Такая гибкость обусловила широкое применение сериализации при создании распределенных приложений.
Для представления объектов в виде последовательности байт определены классы ObjectInputStream и ObjectOutputStream.
Эти классы используют стандартный механизм сериализации, который предлагает JVM. Для того, чтобы объект мог быть сериализован, класс, от которого он порожден, должен реализовывать интерфейс java.io.Serializable. В этом интерфейсе не определен ни один метод. Он нужен лишь для указания, что объекты класса могут участвовать в сериализации. При попытке сериализовать объект, не имеющий такого интерфейса, будет брошен java.io.NotSerializableException.
Чтобы начать сериализацию объекта, нужен выходной поток OutputStream, в который и будет записываться сгенерированная последовательность байт. Этот поток передается в конструктор ObjectOutputStream. Затем вызовом метода writeObject() объект сериализуется и записывается в выходной поток. См. пример на слайде.
Восстановленный объект не совпадает с исходным по имени, но равен сериализованному по значению.
Рассмотрим основные исключения, которые может генерировать метод readObject() класса ObjectInputStream.
Слайд 23

Классы ObjectInputStream и ObjectOutputStream Предположим, объект некоторого класса TestClass был сериализован

Классы ObjectInputStream и ObjectOutputStream

Предположим, объект некоторого класса TestClass был сериализован и

передан по сети на другую машину для восстановления. Может случиться так, что у считывающей JVM на локальном диске не окажется описания этого класса (файл TestClass.class). Поскольку стандартный механизм сериализации записывает в поток байт лишь состояние объекта, для успешной десериализации необходимо наличие описание класса. В результате будет брошено исключение ClassNotFoundException.
Причина появления java.io.StreamCorruptedException вполне очевидна из названия – неправильный формат входного потока. Предположим, происходит попытка считать сериализованный объект из файла. Если этот файл испорчен, то стандартная процедура десериализации даст сбой. Эта же ошибка возникнет, если считать некоторое количество байт (с помощью метода read) непосредственно из надстраиваемого потока InputStream. В таком случае ObjectInputStream снова обнаружит сбой в формате данных и будет брошено исключение java.io.StreamCorruptedException.
Если при считывании будет вызван метод readObject, а в исходном потоке следующим на очереди записано значение примитивного типа, будет брошено исключение java.io.OptionalDataException. Очевидно, что для корректного восстановления данных из потока их нужно считывать именно в том порядке, в каком были записаны.
Слайд 24

Классы символьных потоков Фрагмент иерархии классов символьных потоков:

Классы символьных потоков
Фрагмент иерархии классов символьных потоков:

Слайд 25

Классы символьных потоков Рассмотренные классы – наследники InputStream и OutputStream –

Классы символьных потоков

Рассмотренные классы – наследники InputStream и OutputStream – работают с байтовыми

данными. Если с их помощью записывать или считывать текст, то сначала необходимо сопоставить каждому символу его числовой код. Такое соответствие называется кодировкой.
Известно, что Java использует кодировку Unicode, в которой символы представляются двухбайтовым кодом. Байтовые потоки зачастую работают с текстом упрощенно – они просто отбрасывают старший байт каждого символа. В реальных же приложениях могут использовать различные кодировки (даже для русского языка их существует несколько). Поэтому в версии Java 1.1 появился дополнительный набор классов, основывающийся на типах Reader и Writer. Их иерархия представлена на слайде.
Эта иерархия очень схожа с аналогичной для байтовых потоков InputStream и OutputStream. Главное отличие между ними – Reader и Writer работают с потоком символов ( char ). Только чтение массива символов в Reader описывается методом read(char[]), а запись в Writer – write(char[]).
Слайд 26

Классы символьных потоков Таблица соответствия основных классов для байтовых и символьных потоков:

Классы символьных потоков
Таблица соответствия основных классов для байтовых и

символьных потоков:
Слайд 27

Классы символьных потоков В таблице на слайде приведены соответствия классов для

Классы символьных потоков

В таблице на слайде приведены соответствия классов для байтовых

и символьных потоков.
Как видно из таблицы, различия крайне незначительны и предсказуемы.
Например, конечно же, отсутствует преобразование в символьное представление примитивных типов Java и объектов (DataInput/Output, ObjectInput/Output). Добавлены классы-мосты, преобразующие символьные потоки в байтовые: InputStreamReader и OutputStreamWriter. Именно на их основе реализованы FileReader и FileWriter. Метод available() класса InputStream в классе Reader отсутствует, он заменен методом ready(), возвращающим булевое значение, – готов ли поток к считыванию (то есть будет ли считывание произведено без блокирования).
В остальном же использование символьных потоков идентично работе с байтовыми потоками. 
Классы-мосты InputStreamReader и OutputStreamWriter при преобразовании символов также используют некоторую кодировку. Ее можно задать, передав в конструктор в качестве аргумента ее название. Если оно не будет соответствовать никакой из известных кодировок, будет брошено исключение UnsupportedEncodingException. Вот некоторые из корректных значений этого аргумента (чувствительного к регистру!) для распространенных кодировок: "Cp1251", "UTF-8" и т.д.
Слайд 28

Классы символьных потоков Пример 5:

Классы символьных потоков
Пример 5:

Слайд 29

Классы символьных потоков Пример 5:

Классы символьных потоков
Пример 5:

Слайд 30

Класс Scanner Пример 6: Применение класса Scanner для для расчета среднего

Класс Scanner
Пример 6: Применение класса Scanner для для расчета

среднего арифметического чисел, считываемых из текстового файла
Слайд 31

Класс Scanner Пример 6:

Класс Scanner

Пример 6:

Слайд 32

Класс Scanner Пример 7: Применение класса Scanner для считывания данных разных типов, хранящихся в текстовом файле

Класс Scanner
Пример 7: Применение класса Scanner для считывания данных

разных типов, хранящихся в текстовом файле
Слайд 33

Класс Scanner Пример 7:

Класс Scanner

Пример 7:

Слайд 34

Класс RandomAccessFile Пример 8:

Класс RandomAccessFile

Пример 8: