Что возвращает перегруженный read какое максимальное значение вернет

Система ввода/вывода

Java имеет в своём составе множество классов, связанных с вводом/выводом данных. Рассмотрим некоторые из них.

Класс File

В отличие от большинства классов ввода/вывода, класс File работает не с потоками, а непосредственно с файлами. Данный класс позволяет получить информацию о файле: права доступа, время и дата создания, путь к каталогу. А также осуществлять навигацию по иерархиям подкаталогов.

Подробнее о классе java.io.File

Поток

Есть два типа потоков: байтовые и символьные. В некоторых ситуациях символьные потоки более эффективны, чем байтовые.

За ввод и вывод отвечают разные классы Java. Классы, производные от базовых классов InputStream или Reader, имеют методы с именами read() для чтения отдельных байтов или массива байтов (отвечают за ввод данных). Классы, производные от классов OutputStream или Write, имеют методы с именами write() для записи одиночных байтов или массива байтов (отвечают за вывод данных).

Подробнее о классе InputStream

Класс OutputStream

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

BufferedOutputStream Буферизированный выходной поток ByteArrayOutputStream Создает буфер в памяти. Все данные, посылаемые в этот поток, размещаются в созданном буфере DataOutputStream Выходной поток, включающий методы для записи стандартных типов данных Java FileOutputStream Отправка данных в файл на диске. Реализация класса OutputStream ObjectOutputStream Выходной поток для объектов PipedOutputStream Реализует понятие выходного канала. FilterOutputStream Абстрактный класс, предоставляющий интерфейс для классов-надстроек, которые добавляют к существующим потокам полезные свойства.

BufferedOutputStream

Класс BufferedOutputStream не сильно отличается от класса OutputStream, за исключением дополнительного метода flush(), используемого для обеспечения записи данных в буферизируемый поток. Буферы вывода нужно для повышения производительности.

ByteArrayOutputStream

Класс ByteArrayOutputStream использует байтовый массив в выходном потоке. Метод close() можно не вызывать.

DataOutputStream

Класс DataOutputStream позволяет писать элементарные данные в поток через интерфейс DataOutput, который определяет методы, преобразующие элементарные значения в форму последовательности байтов. Такие потоки облегчают сохранение в файле двоичных данных.

Класс DataOutputStream расширяет класс FilterOutputStream, который в свою очередь, расширяет класс OutputStream.

Методы интерфейса DataOutput:

FileOutputStream

Класс FileOutputStream создаёт объект класса OutputStream, который можно использовать для записи байтов в файл. Создание нового объекта не зависит от того, существует ли заданный файл, так как он создаёт его перед открытием. В случае попытки открытия файла, доступного только для чтения, будет передано исключение.

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

Символьные потоки имеют два основных абстрактных класса Reader и Writer, управляющие потоками символов Unicode.

Reader

Методы класса Reader:

Класс BufferedReader

Класс BufferedReader увеличивает производительность за счёт буферизации ввода.

Класс CharArrayReader

Класс CharArrayReader использует символьный массив в качестве источника.

Класс FileReader

Класс FileReader, производный от класса Reader, можно использовать для чтения содержимого файла. В конструкторе класса нужно указать либо путь к файлу, либо объект типа File.

Writer

Класс BufferedWriter

Класс CharArrayWriter

Класс CharArrayWriter использует массив для выходного потока.

Класс FileWriter

Класс FileWriter создаёт объект класса, производного от класса Writer, который вы можете применять для записи файла. Есть конструкторы, которые позволяют добавить вывод в конец файла. Создание объекта не зависит от наличия файла, он будет создан в случае необходимости. Если файл существует и он доступен только для чтения, то передаётся исключение IOException.

Чтение и запись файлов

В filename нужно указать имя файла, который вы хотите открыть. Если при создании входного потока файл не существует, передаётся исключение FileNotFoundException. Аналогично для выходных потоков, если файл не может быть открыт или создан, также передаётся исключение. Сам класс исключения происходит от класса IOException. Когда выходной файл открыт, любой ранее существовавший файл с тем же именем уничтожается.

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

В JDK 7 метод close() определяется интерфейсом AutoCloseable и можно явно не закрывать файл, а использовать новый оператор try-с-ресурсами, что для Android пока не слишком актуально.

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

Так как исключение FileNotFoundException является подклассом IOException, то не обязательно обрабатывать два исключения отдельно, а оставить только IOException, если вам не нужно отдельно обрабатывать разные причины неудачного открытия файла. Например, если пользователь вводит вручную имя файла, то более конкретное исключение будет к месту.

Для записи в файл используется метод write().

Метод пишет в файл байт, переданный параметром value. Хотя параметр объявлена как целочисленный, в файл записываются только младшие восемь бит. При ошибке записи передаётся исключение.

В JDK 7 есть способ автоматического управления ресурсами:

Когда в Android будет полноценная поддержка JDK 7, то дополним материал.

Чтобы открыть файл для посимвольного чтения, используется класс FileInputReader; имя файла задаётся в виде строки (String) или объекта File. Ускорить процесс чтения помогает буферизация ввода, для этого полученная ссылка передаётся в конструктор класса BufferedReader. Так как в интерфейсе класса имеется метод readLine(), все необходимое для чтения имеется в вашем распоряжении. При достижении конца файла метод readLine() возвращает ссылку null.

Объект FileWriter записывает данные в файл. При вводе/выводе практически всегда применяется буферизация, поэтому используется BufferedWriter.

Когда данные входного потока исчерпываются, метод readLine() возвращает null. Для потока явно вызывается метод close(); если не вызвать его для всех выходных файловых потоков, в буферах могут остаться данные, и файл получится неполным.

PrintWriter форматирует данные так, чтобы их мог прочитать человек. Однако для вывода информации, предназначенной для другого потока, следует использовать классы DataOutputStream для записи данных и DataInputStream для чтения данных.

Единственным надежным способом записать в поток DataOutputStream строку так, чтобы ее можно было потом правильно считать потоком DataInputStream, является кодирование UTF-8, реализуемое методами readUTF() и writeUTF(). Эти методы позволяют смешивать строки и другие типы данных, записываемые потоком DataOutputStream, так как вы знаете, что строки будут правильно сохранены в Юникоде и их будет просто воспроизвести потоком DataInputStream.

Метод writeDouble() записывает число double в поток, а соответствующий ему метод readDouble() затем восстанавливает его (для других типов также существуют подобные методы).

Работа с классом RandomAccessFile напоминает использование совмещенных в одном классе потоков DataInputStream и DataOutputStream (они реализуют те же интерфейсы DataInput и DataOutput). Кроме того, метод seek() позволяет переместиться к определенной позиции и изменить хранящееся там значение.

При использовании RandomAccessFile необходимо знать структуру файла. Класс RandomAccessFile содержит методы для чтения и записи примитивов и строк UTF-8.

RandomAccessFile может открываться в режиме чтения («r») или чтения/записи («rw»). Также есть режим «rws», когда файл открывается для операций чтения-записи и каждое изменение данных файла немедленно записывается на физическое устройство.

Исключения ввода/вывода

В большинстве случаев у классов ввода/вывода используется исключение IOException. Второе исключение FileNotFoundException передаётся в тех случаях, когад файл не может быть открыт. Данное исключение происходит от IOException, поэтому оба исключения можно обрабатывать в одном блоке catch, если у вас нет нужды обрабатывать их по отдельности.

Источник

Русские Блоги

Объясните, почему метод read () потока байтов Java возвращает int вместо byte

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

Нельзя сказать, что онлайн-инструкция ошибочна, но я не думаю, что она четко объяснена. Затем давайте возьмем в качестве примеров FileInputStream / FileOutputStream и BufferedInputStream / BufferedOutputStream. Эти два объяснят, почему это так и каков промежуточный процесс реализации.

1. С точки зрения FileInputStream / FileOutputStream, это в основном связано с вызываемым собственным методом. Я хочу знать, как реализован собственный уровень.

Исходный код read () в FileInputStream:

Здесь мы не можем понять, почему метод read () возвращает тип int, поэтому давайте углубимся и посмотрим на код слоя Native:

Выше приведен процесс реализации. Я считаю, что все здесь знают, почему read () потока ввода-вывода Java возвращает int вместо byte, потому что нижний уровень расширяет byte int.

Использование & 0xFF является причиной 0 расширения (данные, хранящиеся в компьютере, имеют форму дополнения, если вы не понимаете, пожалуйста, сначала поймите)

Мы продолжаем рассматривать метод записи в FileOutputStream, который также вызывает собственный метод.

Посмотрите прямо на код слоя Native:

Здесь нужно записать байты по одному.Перед записью старшие 24-битные байты int были обработаны битовыми операциями в методе OutputStream.write (int).

2. С точки зрения BufferedInputStream / BufferedOutputStream основной причиной является переписывание набора методов ввода-вывода.
Реализация метода read () в BufferedInputStream

Метод writer () в BufferedOutputStream решительно превращает int в байт (восемь бит после перехвата)

Наконец, чтобы подвести итог, метод read () в потоках ввода-вывода Java возвращает int вместо байта, в основном, чтобы избежать преждевременного завершения операции потока и гарантировать, что данные могут быть полностью прочитаны.
Что возвращает перегруженный read какое максимальное значение вернет. 7b325caf27494cbf8809df8c7022e147. Что возвращает перегруженный read какое максимальное значение вернет фото. Что возвращает перегруженный read какое максимальное значение вернет-7b325caf27494cbf8809df8c7022e147. картинка Что возвращает перегруженный read какое максимальное значение вернет. картинка 7b325caf27494cbf8809df8c7022e147. Java имеет в своём составе множество классов, связанных с вводом/выводом данных. Рассмотрим некоторые из них.

Источник

Руководство по классу Java FileReader

Узнайте об основных концепциях читателя и о том, как FileReader упрощает операции чтения с текстовыми файлами, приведя несколько примеров.

1. Обзор

В этом уроке мы изучим основную концепцию Reader и то, как мы можем использовать класс FileReader для выполнения операций чтения в потоке символов в Java.

2. Основы чтения

Это вызывает такие вопросы, как “Кто выполняет тяжелую работу за этим классом?”

Чтобы ответить на этот вопрос, мы должны понять концепцию и иерархию класса Reader в Java.

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

2.1. Когда следует использовать средство чтения файлов

Давайте посмотрим на эту иерархию в определениях классов:

В общем случае мы можем использовать InputStreamReader для чтения символов из любого источника ввода.

Мы можем использовать FileReader , когда хотим прочитать текст из файла, используя набор символов системы по умолчанию. Для любой другой расширенной функциональности было бы идеально использовать InputStreamReader класс напрямую.

3. Чтение текстового файла с помощью программы чтения файлов

Давайте пройдемся по упражнению по кодированию чтения символов из HelloWorld.txt файл с использованием экземпляра FileReader|/.

3.1. Создание программы чтения файлов

Давайте взглянем на эти конструкторы:

В нашем случае мы знаем имя файла входного файла. Следовательно, мы можем использовать первый конструктор для инициализации считывателя:

3.2. Чтение одного символа

Теперь давайте протестируем наш код, проверив, что текст, прочитанный из файла, соответствует ожидаемому тексту:

3.3. Чтение массива символов

Мы даже можем читать несколько символов одновременно, используя унаследованный метод read(char cbuf [], int off, int len) :

Далее давайте проверим правильность нашего кода:

4. Ограничения

Мы видели, что класс FileReader использует системную кодировку символов по умолчанию.

Кроме того, мы все знаем, что циклы ввода-вывода являются дорогостоящими и могут привести к задержке в нашем приложении. Таким образом, в наших интересах минимизировать количество операций ввода-вывода, обернув BufferedReader вокруг нашего FileReader объекта :

5. Заключение

В этом уроке мы узнали об основных концепциях Reader и о том, как FileReader упрощает операции чтения с текстовыми файлами, хотя некоторые примеры.

Источник

Потоки для ввода данных

1. Потоки данных

Любая программа редко существует сама по себе. Обычно она как-то взаимодействует с «внешним миром». Это может быть считывание данных с клавиатуры, отправка сообщений, загрузка страниц из интернета или, наоборот, загрузка файлов на удалённый сервер.

Все эти вещи мы можем назвать одним словом — процесс обмена данными между программой и внешним миром. Хотя это уже не одно слово.

Сам процесс обмена данными можно разделить на два типа: получение данных и отправка данных. Например, вы считываете данные с клавиатуры с помощью объекта Scanner — это получение данных. И выводите данные на экран с помощью команды System.out.println() — это отправка данных.

Для описания процесса обмена данными в программировании используется термин поток. Откуда вообще взялось такое название?

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

Потоки — это универсальный инструмент. Они позволяют программе получать данные откуда угодно (входящие потоки) и отправляют данные куда угодно (исходящие потоки). Делятся на два вида:

Байтовые потоки

Что же это за данные и в каком виде их можно читать? Другими словами, какие типы данных поддерживаются этими классами?

Особенность потоков в том, что данные из них можно читать (писать) только последовательно. Вы не можете прочитать данные из середины потока, не прочитав все данные перед ними.

Именно так работает чтение с клавиатуры через класс Scanner : вы читаете данные с клавиатуры последовательно: строка за строкой. Прочитали строку, прочитали следующую строку, прочитали следующую строку и т.д. Поэтому метод чтения строки и называется nextLine() (дословно — «следующая срока»).

Запись данных в поток OutputStream тоже происходит последовательно. Хороший пример — вывод на экран. Вы выводите строку, за ней еще одну и еще одну. Это последовательный вывод. Вы не можете вывести 1-ю строку, затем 10-ю, а затем вторую. Все данные записываются в поток вывода только последовательно.

Символьные потоки

Недавно вы изучали, что строки — второй по популярности тип данных, и это действительно так. Очень много информации передается в виде символов и целых строк. Компьютер отлично бы передавал все в виде байт, но люди не настолько идеальны.

Если сравнить эти четыре класса, мы получим такую картину:

Байты (byte)Символы (char)
Чтение данных
Запись данных

Практическое применение

2. Класс InputStream

Класс InputStream интересен тем, что является классом-родителем для сотен классов-наследников. В нем самом нет никаких данных, однако у него есть методы, которые есть у всех его классов-наследников.

Объекты-потоки вообще редко хранят в себе данные. Поток — это инструмент чтения/записи данных, но не хранения. Хотя бывают и исключения.

Методы класса InputStream и всех его классов-наследников:

МетодыОписание
Читает один байт из потока
Читает массив байт из потока
Читает все байты из потока
Пропускает n байт в потоке (читает и выкидывает)
Проверяет, сколько байт еще осталось в потоке
Закрывает поток

Вкратце пройдемся по этим методам:

Метод read()

Метод read(byte[] buffer)

Метод readAllBytes()

Метод skip(long n)

Возвращает число байт, которые были реально пропущены (если поток закончился раньше, чем прокрутили n байт).

Метод int available()

Метод возвращает количество байт, которое еще осталось в потоке

Метод void close()

Метод close() закрывает поток данных и освобождает связанные с ним внешние ресурсы. После закрытия потока данные из него читать больше нельзя.

InputStream для чтения из файла
OutputStream для записи в файл

Буфер, в который будем считывать данные
Пока данные есть в потоке

Считываем данные в буфер
Записываем данные из буфера во второй поток

В этом примере мы использовали два класса: FileInputStream — наследник InputStream для чтения данных из файла, и класс FileOutputStream — наследник OutputStream для записи данных в файл. О втором классе расскажем немного позднее.

Источник

Что возвращает перегруженный read какое максимальное значение вернет

Основные навыки и понятия

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

Приступая к изучению системы ввода вырода Java, приготовьтесь к длительной и кропотливой работе. Система ввода-вывода в Java довольно обширна и содержит немало классов, интерфейсов и методов. Объясняется это, в частности, тем, что в Java, по существу, определены две полноценные системы ввода-вывода: одна — для обмена байтами, другая — для обмена символами. Здесь нет возможности рассмотреть все аспекты ввода-вывода в Java, ведь для этого бы потребовалась отдельная книга. Поэтому в данной главе будут рассмотрены лишь наиболее важные и часто используемые языковые средства ввода-вывода. Правда, элементы системы ввода-вывода в Java тесно взаимосвязаны, и поэтому, уяснив основы, вы легко освоите все остальные свойства этой системы.

Прежде чем приступать к рассмотрению системы ввода-вывода, необходимо сделать следующее замечание. Классы, описанные в этой главе, предназначены для консольного и файлового ввода-вывода. Они не применяются для создания графических пользовательских интерфейсов. Поэтому ими не имеет смысла пользоваться при создании оконных приложений. Для графических интерфейсов предусмотрены другие языковые средства. Они будут представлены в главе 14 при рассмотрении апплетов, а также в главе 15, служащей введением в библиотеку Swing. (Swing — это современный набор инструментальных средств, ориентированных на создание графических пользовательских интерфейсов приложений.)

Организация системы ввода-вывода в Java на потоках

Ввод-вывод в программах на Java осуществляется посредством потоков. Поток — это некая абстракция производства или потребления информации. С физическим устройством поток связывает система ввода-вывода. Все потоки действуют одинаково — даже если они связаны с разными физическими устройствами. Поэтому классы и методы ввода-вывода могут применяться к самым разным типам устройств. Например, методами вывода на консоль можно пользоваться и для вывода в файл на диске. Для реализации потоков используется иерархия классов, содержащихся в пакете java.io.

Байтовые и символьные потоки

В современных версиях Java определены два типа потоков: байтовые и символьные. (В первоначальных версиях Java были доступны только байтовые потоки, тогда как символьные потоки были реализованы в дальнейшем.) Байтовые потоки предоставляют удобные средства для ввода и вывода байтов. Они используются, например, при чтении и записи двоичных данных. В особенности они полезны для обращения с файлами. А символьные потоки ориентированы на обмен символьными данными. В них применяется кодировка в уникоде (Unicode), а следовательно, программы, в которых используются символьные потоки, легко поддаются локализации на разные языки мира. В некоторых случаях символьные потоки обеспечивают более высокую эффективность по сравнению с байтовыми.

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

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

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

Для определения байтовых потоков служат две иерархии классов. На их вершине находятся два абстрактных класса: InputStream и OutputStream. В классе InputStream определены свойства, общие для байтовых потоков ввода, а в классе OutputStream — свойства, общие для байтовых потоков вывода.

Производными от классов InputStream и OutputStream являются конкретные подклассы, реализующие различные функциональные возможности и учитывающие особенности обмена данными с разными устройствами, например ввода-вывода в файлы на диске. Классы байтовых потоков приведены в табл. 10.1. Не следует пугаться большого количества этих классов: изучив один из них, легко освоить остальные.

Таблица 10.1. Классы байтовых потоков

Класс байтового потокаОписание
BufferedlnputStreamБуферизованный поток ввода
BufferedOutputStreamБуферизованный поток вывода
ByteArrayInputStreamПоток ввода для чтения из байтового массива
ByteArrayOutputStreamПоток вывода для записи в байтовый массив
DatalnputStreamПоток ввода с методами для чтения стандартных типов данных Java
DataOutputStreamПоток вывода с методами для записи стандартных типов данных Java
FileInputStreamПоток ввода для чтения из файла
FileOutputStreamПоток вывода для записи в файл
FilterlnputStreamПодкласс, производный от класса InputStream
FilterOutputStreamПодкласс, производный от класса OutputStream
InputStreamАбстрактный класс, описывающий потоковый ввод
ObjectInputStreamПоток для ввода объектов
ObjectOutputStreamПоток для вывода объектов
OutputStreamАбстрактный класс, описывающий потоковый вывод
PipedlnputStreamПоток конвейерного ввода
PipedOutputStreamПоток конвейерного вывода
PrintStreamПоток вывода с методами print() и println()
PushbacklnputStreamПоток ввода с возвратом прочитанных байтов в поток
RandomAccessFileКласс, поддерживающий файловый ввод-вывод с произвольным доступом
SequenceInputStreamПоток ввода, сочетающий в себе несколько потоков ввода для поочередного чтения данных из них

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

Для определения символьных потоков служат две иерархические структуры классов, на вершине которых находятся абстрактные классы Reader и Writer соответственно. Класс Reader и его подклассы используются для чтения, а класс Writer и его подклассы — для записи данных. Конкретные классы, производные от классов Reader и Writer, оперируют символами в уникоде.

Классы, производные от классов Reader и Writer, предназначены для выполнения различных операций ввода-вывода символов. Символьные классы присутствуют в Java параллельно с байтовыми классами. Классы символьных потоков приведены в табл. 10.2. Таблица 10.2. Классы символьных потоков

Класс символьного потокаОписание
BufferedReaderБуферизованный поток ввода символов
BufferedWriterБуферизованный поток вывода символов
CharArrayReaderПоток ввода для чтения из символьного массива
CharArrayWriterПоток вывода для записи в символьный массив
FileReaderПоток ввода для чтения символов из файла
FileWriterПоток вывода для записи символов в файл
FilterReaderКласс для чтения символов с фильтрацией
FilterWriterКласс для записи символов с фильтрацией
InputStreamReaderПоток ввода с преобразованием байтов в символы
LineNumberReaderПоток ввода с подсчетом символьных строк
OutputStreamWriterПоток вывода с преобразованием символов в байты
PipedReaderПоток конвейерного ввода
PipedWriterПоток конвейерного вывода
PrintWriterПоток вывода с методами print() и println()
PushbackReaderПоток ввода с возвратом прочитанных символов в поток
ReaderАбстрактный класс, описывающий потоковый ввод символов
StringReaderПоток ввода для чтения из символьной строки
StringWriterПоток вывода для записи в символьную строку
WriterАбстрактный класс, описывающий потоковый вывод символов

Как вам должно быть уже известно, во все программы на Java автоматически импортируется пакет java. lang. В этом пакете определен класс System, инкапсулирующий некоторые элементы среды выполнения программ. Помимо прочего, в нем содержатся предопределенные переменные in, out и err, представляющие стандартные потоки ввода-вывода. Эти поля объявлены как public, final и static. А это означает, что ими можно пользоваться в любой другой части программы, не ссылаясь на конкретный объект типа System.

Переменная System.out ссылается на поток стандартного вывода. По умолчанию этот поток связан с консолью. А переменная System, in ссылается на поток стандартного ввода (по умолчанию с клавиатуры). И наконец, переменная System.err ссылается на поток стандартных сообщений об ошибках, которые по умолчанию выводятся на консоль. По мере необходимости все эти потоки могут быть перенаправлены на другие совместимые устройства ввода-вывода.

Поток System.in представляет собой объект типа InputStream, а потоки System.out и System.err — объекты типа PrintStream. Хотя эти потоки обычно используются для чтения и записи символов, они на самом деле являются байтовыми потоками. Дело в том, что эти потоки были определены в первоначальной спецификации Java, где символьные потоки вообще не были предусмотрены. Как станет ясно в дальнейшем, для этих потоков можно по необходимости создать оболочки, превратив их в символьные потоки.

Применение байтовых потоков

Начнем рассмотрение системы ввода-вывода в Java с байтовых потоков. Как пояснялось ранее, на вершине иерархии байтовых потоков находятся классы InputStream и OutputStream. Методы из класса InputStream приведены в табл. 10.3, а методы из класса OutputStream — в табл. 10.4. При возникновении ошибок в процессе выполнения методы из классов InputStream и OutputStream могут генерировать исключения типа IOException. Методы, определенные в этих двух абстрактных классах, доступны во всех подклассах. Таким образом, они формируют минимальный набор функций ввода-вывода, общих для всех байтовых потоков.

Таблица 10.3. Методы, определенные в классе InputStream

Таблица 10.4. Методы, определенные в классе OutputStream

МетодОписание
void close()Закрывает выходной поток. При последующей попытке записи в поток генерируется исключение IOExceptionВыводит содержимое выходного буфера вывода в
void flush()Выводит содержимое выходного буфера вывода в целевой поток. По завершении этой операции выходной буфер очищается
void write(int b)Записывает один байт в поток вывода. Параметр b относится к типу int, что позволяет вызывать данный метод в выражениях, не приводя результат их вычисления к типу byte
void write(byte buffer[])Записывает массив в поток вывода
void write(byte buffer[], int offset, int numBytes)Записывает в поток вывода часть массива buffer длиной numBytes байтов, начиная с элемента buffer[offset]

Первоначально единственный способ реализовать консольный ввод в Java состоял в использовании байтовых потоков, и во многих программах на Java до сих пор используются исключительно потоки данного типа. В настоящее время для разработки прикладных программ доступны как байтовые, так и символьные потоки. В коммерческих приложениях для чтения данных с консоли в основном используются символьные потоки. Такой подход упрощает локализацию программ и их сопровождение. Ведь намного удобнее оперировать непосредственно символами и не тратить время и труд на преобразование символов в байты, а байтов — в символы. Но в простых служебных и прикладных программах, где данные, введенные с клавиатуры, обрабатываются непосредственно, удобно пользоваться байтовыми потоками. Именно поэтому они здесь и рассматриваются.

Поток System, in является экземпляром класса InputStream, и благодаря этому обеспечивается автоматический доступ к методам, определенным в классе InputStream. К сожалению, в классе InputStream определен только один метод ввода, read(), предназначенный для чтения байтов. Ниже приведены разные формы объявления этого метода.

Ниже приведен краткий пример программы, демонстрирующий чтение байтов из потока ввода System, in в массив. Следует иметь в виду, что исключения, которые могут быть сгенерированы при выполнении данной программы, обрабатываются за пределами метода main(). Такой подход часто используется при чтении данных с консоли. По мере необходимости вы сможете самостоятельно организовать обработку ошибок.

Выполнение этой программы дает например, следующий результат:

Как и для консольного ввода, в Java для консольного вывода первоначально были предусмотрены только байтовые потоки. Но уже в версии Java 1.1 были реализованы символьные потоки. Именно их и рекомендуется применять в прикладных программах, особенно в тех случаях, когда необходимо добиться переносимости кода. Но поскольку System, out является байтовым потоком вывода, он по-прежнему широко используется для побайтового вывода данных на консоль. Именно такой подход до сих пор применялся в примерах, представленных в этой книге. Поэтому он здесь и рассматривается.

Вывести данные на консоль проще всего с помощью уже знакомых вам методов print() и println(). Эти методы определены в классе PrintStream (на объект данного типа ссылается переменная потока стандартного вывода System.out). Несмотря на то что System, out является байтовым потоком вывода, пользоваться им вполне допустимо для организации элементарного вывода данных на консоль.

Класс PrintStream представляет собой выходной поток, производный от класса OutputStream, и поэтому в нем также реализуется метод write() низкоуровневого вывода. Следовательно, этот метод может быть использован для вывода данных на консоль. Самая простая форма метода write(), определенного в PrintStream, имеет следующий вид:

Этот метод записывает в поток байтовое значение, указываемое в качестве параметра byteval. Несмотря на то что этот параметр объявлен как int, учитываются только 8 младших битов его значения. Ниже приведен простой пример программы, где метод write() используется для вывода символов S и новой строки на консоль.

На практике для вывода на консоль метод write.() применяется достаточно редко. Для этой цели намного удобнее пользоваться методами print() и println(). В классе PrintStream реализованы два дополнительных метода, printf() и format(), которые позволяют управлять форматом выводимых данных. Например, при выводе можно указать количество десятичных цифр, минимальную ширину поля или способ представления отрицательных числовых значений. И хотя эти методы не используются в примерах, представленных в данной книге, вам стоит обратить на них пристальное внимание, поскольку они могут оказаться очень полезными при написании прикладных программ.

Чтение и запись в файлы из байтовых потоков

В Java предоставляется большое количество классов и методов, позволяющих читать и записывать данные в файлы. Разумеется, чаще всего приходится обращаться к файлам, хранящимся на дисках. В Java все файлы имеют байтовую организацию, и поэтому для побайтового чтения и записи данных в такие файлы предоставляются соответствующие методы. Следовательно, организовывать чтение и запись данных в файлы из байтовых потоков приходится довольно часто. Кроме того, для байтовых потоков ввода-вывода в файлы в Java разрешено создавать специальные оболочки в виде символьных объектов. Такие оболочки будут рассмотрены далее в этой главе.

Для того чтобы создать байтовый поток и связать его с файлом, следует воспользоваться классом FilelnputStream или FileOutputStream. А для открытия файла достаточно создать объект одного из этих классов, передав имя файла конструктору в качестве параметра. В открытый файл можно записывать данные или читать их из него.

Ввод данных из файла

Файл открывается для ввода созданием объекта типа FilelnputStream. Для этой цели чаще всего используется приведенная ниже форма объявления конструктора данного класса. FilelnputStream(String имя_файла) throws FileNotFoundException

В качестве параметра этому конструктору передается имя_файла, который требуется открыть. Если указанный файл не существует, генерируется исключениеFileNotFoundException.

Для чтения данных из файла служит метод read(). Ниже приведена форма объявления этого метода, которой мы будем пользоваться в дальнейшем,

Завершив операции с файлом, следует закрыть его с помощью метода close(), общая форма объявления которого выглядит следующим образом:

При закрытии файла освобождаются связанные с ним системные ресурсы, чтобы использовать их для работы с другим файлом. Если же файл не будет закрыт, могут произойти “утечки памяти” из-за того, что часть памяти остается выделенной для неиспользуемых ресурсов. Ниже приведен пример программы, где метод read() используется для ввода содержимого текстового файла. Имя файла задается с помощью параметра в командной строке при запуске программы на выполнение. Полученные данные выводятся на экран. Обратите внимание на то, что ошибки ввода-вывода обрабатываются с помощью блока try/catch.

В приведенном выше примере поток ввода из файла закрывается после того, как чтение данных из файла завершается в блоке try. Такой способ оказывается удобным не всегда, и поэтому в Java предоставляется более совершенный и чаще употребляемый способ. А состоит он в вызове метода close() в блоке finally. В этом случае все методы, получающие доступ к файлу, помещаются в блок try, а для закрытия файла используется блок finally. Благодаря этому файл закрывается независимого от того, как завершится блок try. Если продолжить предыдущий пример, то блок try, в котором выполняется чтение из файла, можно переписать следующим образом:

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

Иногда оказывается проще заключить в оболочку те части программы, в которых открывается файл, чтобы получить доступ к нему из единственного блока try, не разделяя его на два блока, а для закрытия файла использовать отдельный блок finally. В качестве примера ниже приведена переделанная версия рассмотренной выше программы ShowFile.

Обратите внимание на то, что переменная fin инициализируется пустым значением null. А в блоке finally файл закрывается только в том случае, если значение переменной fin не является пустым. Такой способ оказывается вполне работоспособным, поскольку переменная fin не будет содержать пустое значение лишь в том случае, если файл был успешно открыт. Следовательно, метод close() не будет вызываться, если во время открытия файла возникнет исключение.

В приведенном выше примере блок try/catch можно сделать более компактным. Ведь исключение FileNotFoundException является подклассом исключения IOException, и поэтому его не нужно перехватывать отдельно. В качестве примера ниже приведен блок оператора catch, которым можно воспользоваться для перехвата обоих этих исключений, не прибегая к перехвату исключения FileNotFoundException в отдельности. В данном случае выводится стандартное сообщение о возникшем исключении с описанием характера ошибки.

FileOutputStream(String имя_файла) throws FileNotFoundException FileOutputStream(String имя_файлаг boolean append) throws FileNotFoundException

void write(int byteval) throws IOException

void close() throws IOException

/* Копирование текстового файла. При вызове этой программы следует указать имя исходного и целевого файлов. Например, для копирования файла FIRST.TXT в файл SECOND.TXT в командной строке нужно указать следующее: java CopyFile FIRST.TXT SECOND.TXT / import java.io.; class CopyFile < public static void main(String args[]) < int i; FilelnputStream fin; FileOutputStream fout;

try(FilelnputStream fin = new FilelnputStream(args[0])) <

/* В этой версии программы CopyFile используется оператор try с ресурсами. В ней демонстрируется управление двумя ресурсами (в данном случае — файлами) с помощью единственного оператора try.

> > Обратите внимание на то, каким образом входной и выходной файлы открываются в операторе try с ресурсами, как показано ниже.

По завершении этого блока try оба файла, на которые ссылаются переменные fin и fout, закрываются автоматически. Если сравнить эту версию программы с предыдущей, то можно заметить, что ее исходный код намного компактнее. Возможность писать более компактный код является еще одним, дополнительным преимуществом оператора try с ресурсами.

Следует также упомянуть о еще одной особенности оператора try с ресурсами. Вообще говоря, когда выполняется блок try, в нем может возникнуть одно исключение, приводящее к другому исключению при закрытии ресурса в блоке finally. И если это блок обычного оператора try, то исходное исключение теряется, прерываясь вторым исключением. А в блоке оператора try с ресурсами второе исключение подавляется. Но оно не теряется, а добавляется в список подавленных исключений, связанных с первым исключением. Этот список можно получить, вызвав метод get Suppressed(), определенный в классе Throwable.

В силу упомянутых выше преимуществ, присущих оператору try с ресурсами, можно ожидать, что он найдет широкое применение в программировании на Java. Поэтому именно он и будет использоваться в остальных примерах программ, представленных далее в этой главе. Но не менее важным остается и умение пользоваться рассмотренным ранее традиционным способом освобождения ресурсов с помощью вызываемого явным образом оператора close(). И на то имеется ряд веских оснований. Во-первых, уже существует немало написанных и повсеместно эксплуатируемых программ на Java, в которых применяется традиционный способ управления ресурсами. Поэтому все программирующие на Java должны как следует усвоить и уметь пользоваться этим традиционным способом для сопровождения устаревшего кода. Во-вторых, переход на JDK 7 может произойти не сразу, а следовательно, придется работать с предыдущей версией данного комплекта. В этом случае воспользоваться преимуществами оператора try с ресурсами не удастся и придется применять традиционный способ управления ресурсами. И наконец, в некоторых классах закрытие ресурса явным образом может оказаться более пригодным, чем его автоматическое освобождение. Но, несмотря на все сказанное выше, новый способ автоматического управления ресурсами считается более предпочтительным при переходе к JDK 7 или более поздней версии данного комплекта, поскольку он рациональнее и надежнее традиционного способа.

Чтение и запись двоичных данных

В приведенных до сих пор примерах программ читались и записывались байтовые значения, содержащие символы в коде ASCII. Но аналогичным образом можно также организовать чтение и запись любых типов данных. Допустим, требуется создать файл, содержащий значения типа int, double или short. Для чтения и записи простых типов данных в Java предусмотрены классы DatalnputStream и DataOutputStream.

Класс DataOutputStream реализует интерфейс DataOutput, в котором определены методы, позволяющие записывать в файл значения любых простых типов. Следует, однако, иметь в виду, что данные записываются во внутреннем двоичном формате, а не в виде последовательности символов. Методы, наиболее часто применяемые для записи простых типов данных в Java, приведены в табл. 10.5. Каждый из них генерирует исключение IOException при возникновении ошибки ввода-вывода.

Таблица 10.5. Наиболее часто употребляемые методы вывода данных, определенные в классе DataOutputStream

Метод вывода данныхОписание
void writeBoolean (boolean val)Записывает логическое значение, определяемое параметром val
void writeByte (int,val)Записывает младший байт целочисленного значения, определяемого параметром val
void writeChar (int,val)Записывает значение, определяемое параметром val, интерпретируя его как символ
void writeDouble (double val)Записывает значение типа double, определяемое параметром val
void writeFloat (float val)Записывает значение типа float, определяемое параметром val
void writelnt(int val)Записывает значение типа int, определяемое параметром val
void writeLong (long val)Записывает значение типа long, определяемое параметром val
void writeShort (int val)Записывает целочисленное значение, определяемое параметром val, преобразуя его в тип short

Ниже приведен конструктор класса DataOutputStream. Обратите внимание на то, что при вызове ему передается экземпляр класса OutputStream.

где OutputStream — это поток вывода, в который записываются данные. Для того чтобы организовать запись данных в файл, следует передать конструктору в качестве параметра OutputStream объект типа FileOutputStream.

Класс DatalnputStream реализует интерфейс Datalnput, в котором объявлены методы для чтения всех простых типов данных в Java (табл. 10.6). В каждом из этих методов может быть сгенерировано исключение IOException при возникновении ошибки ввода-вывода. В качестве своего основания класс DatalnputStream использует экземпляр класса InputStream, перекрывая его методами для чтения различных типов данных в Java. Однако в потоке типа DatalnputStream данные читаются в двоичном виде, а не в удобной для чтения форме. Ниже приведен конструктор класса DatalnputStream.

где inputStream — это поток, связанный с создаваемым экземпляром класса DatalnputStream. Для того чтобы организовать чтение данных из файла, следует передать конструктору в качестве параметра inputStream объект типа FilelnputStream.

Таблица 10.6. Наиболее часто употребляемые методы ввода данных, определенные в классе DatalnputStream

Метод ввода данныхОписание
boolean readBoolean()Читает значение типа boolean
byte readByte()Читает значение типа byte
char readChar()Читает значение типа char
double readDouble()Читает значение типа double
float readFloat()Читает значение типа float
int readlnt()Читает значение типа int
long readLong()Читает значение типа long
short readShort()Читает значение типа short

Ниже приведен пример программы, демонстрирующий применение классов DataOutputStream и DatalnputStream. В этой программе данные разных типов сначала записываются в файл, а затем читаются из файла.

Выполнение этой программы дает следующий результат:

Пример для опробования 10.1. Утилита сравнения файлов

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

Файлы с произвольным доступом

До сих пор нам приходилось иметь дело с последовательными файлами, содержимое которых вводилось и выводилось побайтно, т.е. строго по порядку. Но в Java предоставляется также возможность обращаться к хранящимся в файле данным в произвольном порядке. Для этой цели предусмотрен класс RandomAccessFile, инкапсулирующий файл с произвольным доступом. Класс RandomAccessFile не является производным от класса InputStream или OutputStream. Вместо этого он реализует интерфейсы Datalnput и DataOutput, в которых объявлены основные методы ввода-вывода. В нем поддерживаются также запросы позиционирования, т.е. возможность задавать положение указателя файла произвольным образом. Ниже приведен конструктор класса RandomAccessFile, которым мы будем пользоваться далее.

Здесь конкретный файл указывается с помощью параметра имя_файла, а параметр доступ определяет, какой именно тип доступа будет использоваться для обращения к файлу. Если параметр доступ принимает значение «г», то данные могут читаться из файла, но не записываться в него. Если же указан тип доступа «rw», то файл открывается как для чтения, так и для записи.

Метод seek(), общая форма объявления которого приведена ниже, служит для установки текущего положения указателя файла,

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

В классе RandomAccessFile определены методы read() и write(). Этот класс также реализует интерфейсы Datalnput и DataOuput, т.е. в нем доступны методы чтения и записи простых типов, например readlnt() и writeDouble().

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

Результат выполнения данной программы выглядит следующим образом:

Обратите внимание на расположение каждого числового значения. Ведь значение типа double занимает 8 байтов, и поэтому каждое последующее число начинается на 8-байтовой границе предыдущего числа. Иными словами, первое числовое значение начинается на позиции нулевого байта, второе — на позиции 8-го байта, третье — на позиции 16-го байта и т.д. Поэтому для чтения четвертого числового значения нужно установить указатель файла на позиции 24-го байта при вызове метода seek().

Применение символьных потоков в Java

Как следует из предыдущих разделов этой главы, байтовые потоки в Java довольно эффективны и удобны в употреблении. Но что касается ввода-вывода символов, то байтовые потоки далеки от идеала. Поэтому для этих целей в Java определены классы символьных потоков. На вершине иерархии классов, поддерживающих символьные потоки, находятся абстрактные классы Reader и Writer. Методы класса Reader приведены в табл. 10.7, а методы класса Writer — в табл. 10.8. В большинстве этих методов может быть сгенерировано исключение IOException. Методы, определенные в указанных абстрактных классах Reader и Writer, доступны во всех их подклассах. Таким образом, они образуют минимальный набор функций ввода-вывода, необходимых для всех символьных потоков.

Таблица 10.7. Методы, определенные в классе Reader

Таблица 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, нужно воспользоваться следующим конструктором:

Поток ввода System.in является экземпляром класса InputStream, и поэтому его можно указать в качестве параметра inputStream данного конструктора.

Затем на основании объекта типа InputStreamReader можно создать объект типа BufferedReader, используя следующий конструктор:

где inputReader — это поток, который связывается с создаваемым экземпляром класса Buf feredReader. Объединяя обращения к указанным выше конструкторам в одну операцию, мы получаем приведенную ниже строку кода. В ней создается объект типа BufferedReader, связанный с клавиатурой.

После выполнения этого оператора присваивания переменная br будет содержать ссылку на символьный поток, связанный с консолью через поток ввода System.in.

Прочитать символы из потока ввода System, in можно с помощью метода read(), определенного в классе Buf f eredReader. Чтение символов мало чем отличается от чтения данных из байтовых потоков. Ниже приведены общие формы объявления трех вариантов метода read(), предусмотренных в классе Buf f eredReader.

Ниже приведен пример программы, демонстрирующий применение метода read() для чтения символов с консоли. Символы читаются до тех пор, пока пользователь не введет точку. Следует иметь в виду, что исключения, которые могут быть сгенерированы при выполнении данной программы, обрабатываются за пределами метода main(). Как пояснялось выше, подобный подход характерен для обработки ошибок при чтении данных с консоли. По желанию вы можете употребить другой механизм обработки ошибок.

Результат выполнения данной программы выглядит следующим образом:

Чтение символьных строк

Для ввода символьной строки с клавиатуры следует воспользоваться методом readLine() из класса Buf feredReader. Ниже приведена общая форма объявления этого метода.

Этот метод возвращает объект типа String, содержащий прочитанные символы. При попытке прочитать символьную строку по окончании потока метод возвращает пустое знчение null.

Ниже приведен пример программы, демонстрирующий применение класса BufferedReader и метода readLine(). В этой программе текстовые строки читаются и отображаются до тех пор, пока не будет введено слово «stop».

Консольный вывод в символьные потоки

Несмотря на то что поток стандартного вывода System, out вполне может использоваться для вывода на консоль, такой подход скорее пригоден для целей отладки или при создании очень простых программ, подобных тем, которые приводятся в качестве примеров в этой книге. Для реальных прикладных программ на Java вывод на консоль обычно организуется через поток PrintWriter, относящийся к одному из классов, представляющих символьные потоки. Как упоминалось ранее, применение символьных потоков упрощает локализацию прикладных программ.

В классе PrintWriter определен ряд конструкторов. Далее будет использоваться следующий конструктор:

где в качестве первого параметра OutputStream конструктору передается объект типа OutputStream, а второй параметр f lushOnNewline указывает, должен ли производиться вывод данных из буфера в поток вывода при каждом вызове метода println(). Если параметр f lushOnNewline принимает логическое значение true, данные выводятся из буфера автоматически.

В классе PrintWriter поддерживаются методы print() и println() для всех типов, включая Object. Следовательно, методы print() и println() можно использовать точно так же, как и вместе с потоком вывода System, out. Если значение аргумента не относится к простому типу, то методы из класса PrintWriter вызывают метод toString() для объекта, указываемого в качестве параметра, а затем выводят результат.

Для вывода данных на консоль через поток типа PrintWriter следует указать System.out B качестве потока вывода и обеспечить вывод данных из буфера после каждого вызова метода println(). Например, при выполнении следующей строки кода создается объект типа PrintWriter, связанный с консолью:

Ниже приведен пример прикладной программы, демонстрирующий применение класса PrintWriter для организации вывода на консоль.

Выполнение этой программы дает следующий результат:

Несмотря на все удобство символьных потоков, не следует забывать, что для изучения языка Java или отладки программ можно вполне пользоваться и потоком вывода System, out. Но если в программе применяется поток PrintWriter, то ее проще локализировать. Для кратких примеров программ, представленных в этой книге, применение потока PrintWriter не имеет существенных преимуществ перед потоком System, out, поэтому в и последующих примерах для вывода на консоль будет использоваться поток System.out.

Ввод-вывод в файлы через символьные потоки

На практике чаще всего приходится обращаться с файлами, имеющими байтовую организацию, тем не менее, для этой цели можно пользоваться символьными потоками. Преимущество символьных потоков заключается в том, что они оперируют непосредственно символами в уникоде. Так, если требуется сохранить текст в уникоде, для этой цели лучше всего воспользоваться символьными потоками. Как правило, для файлового ввода-вывода символов служат классы FileReader и FileWriter.

Применение класса FileWriter

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

Здесь имя файла обозначает полный путь к файлу. Если параметр append принимает логическое значение true, данные записываются в конец файла, а иначе они перезаписывают прежние данные на том же месте в файле. При возникновении ошибки в каждом из указанных выше конструкторов генерируется исключение IOException. Класс FileWriter является производным от классов OutputStreamWriter и Writer. Следовательно, в нем доступны методы, объявленные в его суперклассах.

Ниже приведен краткий пример программы, демонстрирующий ввод текстовых строк с клавиатуры и последующий их вывод в файл test. txt. Набираемый текст читается до тех пор, пока пользователь не введет слово «stop». Для вывода текстовых строк в файл используется класс FileWriter.

Применение класса FileReader

В классе FileReader создается объект типа Reader, который можно использовать для чтения содержимого файла. Чаще всего употребляется такой конструктор этого класса:

где имя файла обозначает полный путь к файлу. Если указанный файл не существует, генерируется исключение FileNotFoundException. Класс FileReader является производным от классов InputStreamReader и Reader. Следовательно, в нем доступны методы, объявленные в его суперклассах.

Приведенный ниже пример демонстрирует простую утилиту для отображения на экране содержимого текстового файла test. txt. Она является своего рода дополнением к утилите, рассмотренной в предыдущем разделе.

Обратите внимание на то, что для потока типа FileReader создана оболочка в классе BufferedReader. Благодаря этому появляется возможность обращаться к методу readLine(). Кроме того, закрытие потока типа Buf feredReader, на который в данном примере ссылается переменная br, автоматически приводит к закрытию файла.

Применение оболочек типов для преобразования символьных строк в числа

Прежде чем завершить обсуждение средств ввода-вывода, необходимо рассмотреть еще один способ, помогающий читать числовые строки. Как известно, метод println() предоставляет удобные средства для вывода на консоль различных типов данных, в том числе целых чисел и чисел с плавающей точкой. Он автоматически преобразует числовые значения в удобную для чтения форму. Но в Java отсутствует метод, который читал бы числовые строки и преобразовывал бы их во внутреннюю двоичную форму. Например, не существует варианта метода read(), который читал бы числовую строку «100» и автоматически преобразовывал ее в целое число, пригодное для хранения в переменной типа int. Но для этой цели в Java имеются другие средства. И проще всего подобное преобразование осуществляется с помощью так называемых оболочек типов.

Оболочки типов в Java представляют собой классы, которые инкапсулируют простые типы. Оболочки типов необходимы, поскольку простые типы не являются объектами, что ограничивает их применение. Так, простой тип нельзя передать методу по ссылке. Для того чтобы исключить ненужные ограничения, в Java были предусмотрены классы, соответствующие каждому из простых типов.

Оболочками типов являются классы Double, Float, Long, Integer, Short, Byte, Character и Boolean. Эти классы предоставляют обширный ряд методов, позволяющих полностью интегрировать простые типы в иерархию объектов Java. Кроме того, в классах-оболочках числовых типов содержатся методы, предназначенные для преобразования числовых строк в соответствующие двоичные эквиваленты. Эти методы приведены ниже. Каждый из них возвращает двоичное значение, соответствующее числовой строке.

Оболочка типаМетод преобразования
Doublestatic double parseDouble(String str) throws NumberFormatException
Floatstatic float parseFloat(String str) throws NumberFormatException
Longstatic long parseLong(String str) throws NumberFormatException
Integerstatic int parselnt(String str) throws NumberFormatException
Shortstatic short parseShort(String str) throws NumberFormatException
Bytestatic byte parseByte(String str) throws NumberFormatException

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

Методы синтаксического анализа позволяют без труда преобразовать во внутренний формат числовые значения, введенные в виде символьных строк с клавиатуры или из текстового файла. Ниже приведен пример программы, демонстрирующий применение для этих целей методов parselnt() и parseDouble(). В этой программе находится среднее арифметическое ряда чисел, введенных пользователем с клавиатуры. Сначала пользователю прелагается указать количество числовых значений для обработки, а затем программа вводит числа с клавиатуры, используя метод readLine(), а с помощью метода parselnt() преобразует символьную строку в целочисленное значение. Далее осуществляется ввод числовых значений и последующее их преобразование в тип double с помощью метода parseDouble().

Выполнение этой программы может дать, например, следующий результат:

Пример для опробования 10.2. Создание справочной системы, находящейся на диске

В примере для опробования 4.1 был создан класс Help, позволяющий отображать сведения об операторах Java. Справочная информация хранилась в самом классе, а пользователь выбирал требуемые сведения из меню. И хотя такая справочная система выполняет свои функции, подход к ее разработке был выбран далеко не самый лучший. Так, если требуется добавить или изменить какие-нибудь сведения в подобной справочной системе, придется внести изменения в исходный код программы, которая ее реализует. Кроме того, выбирать пункт меню по его номеру не очень удобно, а если количество пунктов велико, то такой способ оказывается вообще непригодным. В этом проекте предстоит устранить недостатки, имеющиеся в справочной системе, разместив справочную информацию на диске.

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

Упражнение для самопроверки

по материалу главы 10

Источник

Добавить комментарий

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