Ответы на вопросы по ревью 4. Java io. Ключевым понятием здесь является понятие потока
![]()
|
Запись файлов и класс FileOutputStreamКласс FileOutputStream предназначен для записи байтов в файл. Он является производным от класса OutputStream, поэтому наследует всю его функциональность. Через конструктор класса FileOutputStream задается файл, в который производится запись. Класс поддерживает несколько конструкторов:
Файл задается либо через строковый путь, либо через объект File. Второй параметр - append задает способ записи: eсли он равен true, то данные дозаписываются в конец файла, а при false - файл полностью перезаписывается Например, запишем в файл строку:
Для создания объекта FileOutputStream используется конструктор, принимающий в качестве параметра путь к файлу для записи. Если такого файла нет, то он автоматически создается при записи. Так как здесь записываем строку, то ее надо сначала перевести в массив байтов. И с помощью метода write строка записывается в файл. Для автоматического закрытия файла и освобождения ресурса объект FileOutputStream создается с помощью конструктции try...catch. При этом необязательно записывать весь массив байтов. Используя перегрузку метода write(), можно записать и одиночный байт:
Чтение файлов и класс FileInputStreamДля считывания данных из файла предназначен класс FileInputStream, который является наследником класса InputStream и поэтому реализует все его методы. Для создания объекта FileInputStream мы можем использовать ряд конструкторов. Наиболее используемая версия конструктора в качестве параметра принимает путь к считываемому файлу:
Если файл не может быть открыт, например, по указанному пути такого файла не существует, то генерируется исключение FileNotFoundException. Считаем данные из ранее записанного файла и выведем на консоль:
В данном случае мы считываем каждый отдельный байт в переменную i:
Когда в потоке больше нет данных для чтения, метод возвращает число -1. Затем каждый считанный байт конвертируется в объект типа char и выводится на консоль. Подобным образом можно считать данные в массив байтов и затем производить с ним манипуляции:
Совместим оба класса и выполним чтение из одного и запись в другой файл:
Классы FileInputStream и FileOutputStream предназначены прежде всего для записи двоичных файлов, то есть для записи и чтения байтов. И хотя они также могут использоваться для работы с текстовыми файлами, но все же для этой задачи больше подходят другие классы. При завершении работы с потоком его надо закрыть с помощью метода close(), который определен в интерфейсе Closeable. Метод close имеет следующее определение:
Этот интерфейс уже реализуется в классах InputStream и OutputStream, а через них и во всех классах потоков. При закрытии потока освобождаются все выделенные для него ресурсы, например, файл. В случае, если поток окажется не закрыт, может происходить утечка памяти. Есть два способа закрытия файла. Первый традиционный заключается в использовании блока try..catch..finally. Например, считаем данные из файла:
Поскольку при открытии или считывании файла может произойти ошибка ввода-вывода, то код считывания помещается в блок try. И чтобы быть уверенным, что поток в любом случае закроется, даже если при работе с ним возникнет ошибка, вызов метода close() помещается в блок finally. И, так как метод close() также в случае ошибки может генерировать исключение IOException, то его вызов также помещается во вложенный блок try..catch Начиная с Java 7 можно использовать еще один способ, который автоматически вызывает метод close. Этот способ заключается в использовании конструкции try-with-resources (try-с-ресурсами). Данная конструкция работает с объектами, которые реализуют интерфейс AutoCloseable. Так как все классы потоков реализуют интерфейс Closeable, который в свою очередь наследуется от AutoCloseable, то их также можно использовать в данной конструкции Итак, перепишем предыдущий пример с использованием конструкции try-with-resources:
Синтаксис конструкции следующий: try(название_класса имя_переменной = конструктор_класса). Данная конструкция также не исключает использования блоков catch. После окончания работы в блоке try у ресурса (в данном случае у объекта FileInputStream) автоматически вызывается метод close(). Если нам надо использовать несколько потоков, которые после выполнения надо закрыть, то мы можем указать объекты потоков через точку с запятой:
Для работы с массивами байтов - их чтения и записи используются классы ByteArrayInputStream и ByteArrayOutputStream. Чтение массива байтов и класс ByteArrayInputStreamКласс ByteArrayInputStream представляет входной поток, использующий в качестве источника данных массив байтов. Он имеет следующие конструкторы:
В качестве параметров конструкторы используют массив байтов buf, из которого производится считывание, смещение относительно начала массива offset и количество считываемых символов length. Считаем массив байтов и выведем его на экран:
В отличие от других классов потоков для закрытия объекта ByteArrayInputStream не требуется вызывать метод close. Запись массива байт и класс ByteArrayOutputStreamКласс ByteArrayOutputStream представляет поток вывода, использующий массив байтов в качестве места вывода. Чтобы создать объект данного класса, мы можем использовать один из его конструкторов:
Первая версия создает массив для хранения байтов длиной в 32 байта, а вторая версия создает массив длиной size. Рассмотрим применение класса:
Как и в других потоках вывода в классе ByteArrayOutputStream определен метод write, который записывает в поток некоторые данные. В данном случае мы записываем в поток массив байтов. Этот массив байтов записывается в объекте ByteArrayOutputStream в защищенное поле buf, которое представляет также массив байтов (protected byte[] buf). Так как метод write может сгенерировать исключение, то вызов этого метода помещается в блок try..catch. Используя методы toString() и toByteArray(), можно получить массив байтов buf в виде текста или непосредственно в виде массива байт. С помощью метода writeTo мы можем вывести массив байт в другой поток. Данный метод в качестве параметра принимает объект OutputStream, в который производится запись массива байт:
После выполнения этой программы в папке с программой появится файл hello.txt, который будет содержать строку "Hello Wolrd!". И в заключении также надо сказать, что как и для объектов ByteArrayInputStream, для ByteArrayOutputStream не надо явным образом закрывать поток с помощью метода close. Для оптимизации операций ввода-вывода используются буферизуемые потоки. Эти потоки добавляют к стандартным специальный буфер в памяти, с помощью которого повышается производительность при чтении и записи потоков. Класс BufferedInputStreamКласс BufferedInputStream накапливает вводимые данные в специальном буфере без постоянного обращения к устройству ввода. Класс BufferedInputStream определяет два конструктора:
Первый параметр - это поток ввода, с которого данные будут считываться в буфер. Второй параметр - размер буфера. Например, буферизируем считывание данных из потока ByteArrayInputStream:
Класс BufferedInputStream в конструкторе принимает объект InputStream. В данном случае таким объектом является экземпляр класса ByteArrayInputStream. Как и все потоки ввода BufferedInputStream обладает методом read(), который считывает данные. И здесь мы считываем с помощью метода read каждый байт из массива buffer. Фактические все то же самое можно было сделать и с помощью одного ByteArrayInputStream, не прибегая к буферизированному потоку. Класс BufferedInputStream просто оптимизирует производительность при работе с потоком ByteArrayInputStream. Естественно вместо ByteArrayInputStream может использоваться любой другой класс, который унаследован от InputStream. Класс BufferedOutputStreamКласс BufferedOutputStream аналогично создает буфер для потоков вывода. Этот буфер накапливает выводимые байты без постоянного обращения к устройству. И когда буфер заполнен, производится запись данных. BufferedOutputStream определяет два конструктора:
Первый параметр - это поток вывода, который унаследован от OutputStream, а второй параметр - размер буфера. Рассмотрим на примере записи в файл:
Класс BufferedOutputStream в конструкторе принимает в качестве параметра объект OutputStream - в данном случае это файловый поток вывода FileOutputStream. И также производится запись в файл. Опять же BufferedOutputStream не добавляет много новой функциональности, он просто оптимизирует действие потока вывода. Класс PrintStreamКласс PrintStream - это именно тот класс, который используется для вывода на консоль. Когда мы выводим на консоль некоторую информацию с помощью вызова System.out.println(), то тем самым мы задействует PrintStream, так как переменная out в классе System как раз и представляет объект класса PrintStream, а метод println() - это метод класса PrintStream. Но PrintStream полезен не только для вывода на консоль. Мы можем использовать данный класс для записи информации в поток вывода. Для этого PrintStream определяет ряд конструкторов:
Параметр outputStream - это объект OutputStream, в который производится запись. Параметр autoFlushingOn при значении true позволяет автоматически записывать данные в поток вывода. По умолчанию этот параметр равен false. Параметр charSet позволяет указать кодировку символов. В качестве источника для записи данных вместо OutputStream можно использовать объект File или строковый путь, по которому будет создаваться файл. Для вывода информации в выходной поток PrintStream использует следующие методы: println(): вывод строковой информации с переводом строки print(): вывод строковой информации без перевода строки printf(): форматированный вывод Например, запишем информацию в файл:
В данном случае применяется форма конструктора PrintStream, которая в качестве параметра принимает поток вывода: PrintStream (OutputStream out). Кроме того, мы могли бы использовать ряд других форм конструктора, например, указывая названия файла для записи: PrintStream (string filename) В качестве потока вывода используется объект FileOutputStream. С помощью метода println() производится запись информации в выходной поток - то есть в объект FileOutputStream. (В случае с выводом на консоль с помощью System.out.println() в качестве потока вывода выступает консоль) Кроме того, как и любой поток вывода и наследник класса OutputStream он имеет метод write:
После выполнения этой программы получится файл со следующим содержанием: Hello World!Welcome to Java! Name: Tom Age: 34 PrintStream PrintWriterНа PrintStream похож другой класс PrintWriter. Его можно использовать как для вывода информации на консоль, так и в файл или любой другой поток вывода. Данный класс имеет ряд конструкторов: PrintWriter(File file): автоматически добавляет информацию в указанный файл PrintWriter(File file, String csn): автоматически добавляет информацию в указанный файл с учетом кодировки csn PrintWriter(OutputStream out): для вывода информации используется существующий объект OutputStream, автоматически сбрасывая в него данные PrintWriter(OutputStream out, boolean autoFlush): для вывода информации используется существующий объект OutputStream, второй параметр указывает, надо ли автоматически добавлять в OutputStream данные PrintWriter(String fileName): автоматически добавляет информацию в файл по указанному имени PrintWriter(String fileName, String csn): автоматически добавляет информацию в файл по указанному имени, используя кодировку csn PrintWriter(Writer out): для вывода информации используется существующий объект Writer, в который автоматически идет запись данных PrintWriter(Writer out, boolean autoFlush): для вывода информации используется существующий объект Writer, второй параметр указывает, надо ли автоматически добавлять в Writer данные PrintWriter реализует интерфейсы Appendable, Closable и Flushable, и поэтому после использования представляемый им поток надо закрывать. Для записи данных в поток он также используется методы printf() и println(). Например, применим данный класс для вывода на консоль:
В качестве потока вывода здесь применяется System.out, а на консоль будет выведена строка "Hello world!" Классы DataOutputStream и DataInputStream позволяют записывать и считывать данные примитивных типов. Запись данных и DataOutputStreamКласс DataOutputStream представляет поток вывода и предназначен для записи данных примитивных типов, таких, как int, double и т.д. Для записи каждого из примитивных типов предназначен свой метод: writeBoolean(boolean v) : записывает в поток булевое однобайтовое значение writeByte(int v): записывает в поток 1 байт, которые представлен в виде целочисленного значения writeChar(int v): записывает 2-байтовое значение char writeDouble(double v): записывает в поток 8-байтовое значение double writeFloat(float v): записывает в поток 4-байтовое значение float writeInt(int v): записывает в поток целочисленное значение int writeLong(long v): записывает в поток значение long writeShort(int v): записывает в поток значение short writeUTF(String str): записывает в поток строку в кодировке UTF-8 Считывание данных и DataInputStreamКласс DataInputStream действует противоположным образом - он считывает из потока данные примитивных типов. Соответственно для каждого примитивного типа определен свой метод для считывания: boolean readBoolean(): считывает из потока булевое однобайтовое значение byte readByte(): считывает из потока 1 байт char readChar(): считывает из потока значение char double readDouble(): считывает из потока 8-байтовое значение double float readFloat(): считывает из потока 4-байтовое значение float int readInt(): считывает из потока целочисленное значение int long readLong(): считывает из потока значение long short readShort(): считывает значение short String readUTF(): считывает из потока строку в кодировке UTF-8 int skipBytes(int n): пропускает при чтении из потока n байтов Рассмотрим применение классов на примере:
Здесь мы последовательно записываем в файл данные объекта Person. Объект DataOutputStream в конструкторе принимает поток вывода: DataOutputStream (OutputStream out). В данном случае в качестве потока вывода используется объект FileOutputStream, поэтому вывод будет происходить в файл. И с помощью выше рассмотренных методов типа writeUTF() производится запись значений в бинарный файл. Затем происходит чтение ранее записанных данных. Объект DataInputStream в конструкторе принимает поток для чтения: DataInputStream(InputStream in). Здесь таким потоком выступает объект FileInputStream Хотя с помощью ранее рассмотренных классов можно записывать текст в файлы, однако они предназначены прежде всего дл работы с бинарными потоками данных, и их возможностей для полноценной работы с текстовыми файлами недостаточно. И для этой цели служат совсем другие классы, которые являются наследниками абстрактных классов Reader и Writer. Запись файлов. Класс FileWriterКласс FileWriter является производным от класса Writer. Он используется для записи текстовых файлов. Чтобы создать объект FileWriter, можно использовать один из следующих конструкторов:
Так, в конструктор передается либо путь к файлу в виде строки, либо объект File, который ссылается на конкретный текстовый файл. Параметр append указывает, должны ли данные дозаписываться в конец файла (если параметр равен true), либо файл должен перезаписываться. Запишем в файл какой-нибудь текст:
В конструкторе использовался параметр append со значением false - то есть файл будет перезаписываться. Затем с помощью методов, определенных в базовом классе Writer производится запись данных. Чтение файлов. Класс FileReaderКласс FileReader наследуется от абстрактного класса Reader и предоставляет функциональность для чтения текстовых файлов. Для создания объекта FileReader мы можем использовать один из его конструкторов:
А используя методы, определенные в базом классе Reader, произвести чтение файла:
Также мы можем считывать в промежуточный буфер из массива символов:
В данном случае считываем последовательно символы из файла в массив из 256 символов, пока не дойдем до конца файла в этом случае метод read возвратит число -1. Поскольку считанная порция файла может быть меньше 256 символов (например, в файле всего 73 символа), и если количество считанных данных меньше размера буфера (256), то выполняем копирование массива с помощью метода Arrays.copy. То есть фактически обрезаем массив buf, оставляя в нем только те символы, которые считаны из файла. Запись текста через буфер и BufferedWriterКласс BufferedWriter записывает текст в поток, предварительно буферизируя записываемые символы, тем самым снижая количество обращений к физическому носителю для записи данных. Класс BufferedWriter имеет следующие конструкторы:
В качестве параметра он принимает поток вывода, в который надо осуществить запись. Второй параметр указывает на размер буфера. Например, осуществим запись в файл:
Чтение текста и BufferedReaderКласс BufferedReader считывает текст из символьного потока ввода, буферизируя прочитанные символы. Использование буфера призвано увеличить производительность чтения данных из потока. Класс BufferedReader имеет следующие конструкторы:
Второй конструктор, кроме потока ввода, из которого производится чтение, также определяет размер буфера, в который будут считываться символы. Так как BufferedReader наследуется от класса Reader, то он может использовать все те методы для чтения из потока, которые определены в Reader. И также BufferedReader определяет свой собственный метод readLine(), который позволяет считывать из потока построчно. Рассмотрим применение BufferedReader:
Также можно считать текст построчно:
Считывание с консоли в файлСоединим оба класса BufferedReader и BufferedWriter для считывания с консоли в файл. Для этого определим следующий код программы:
Здесь объект BufferedReader устанавливается для чтения с консоли с помощью объекта new InputStreamReader(System.in). В цикле while считывается введенный текст. И пока пользователь не введет строку "ESC", объект BufferedWriter будет записывать текст файл. 2) На каком паттерне основана иерархия потоков ввода/вывода. |