Руководство по Java Core. Файлы и работа с ними.
При разработке программного обеспечения мы сталкиваемся с приложениями, в которых необходимо передавать или принимать файлы. Практически все классы, необходимые для этой задачи находтся в паке java.io. В работе с файлами испольщуется такое понятие, как поток (stream), который принимает входной файл и перемещает его в место назначения.
В языке программирования Java поддерживаются потоки для различных типов данных. Давайте рассмотрим их.
Потоки (Streams)
Мы можем сказать ,что поток – это последовательность данных. Существует два вида потоков:
- Входящий поток
В Java для чтения файлов используется InputStream . - Исходящий поток
В Java используется OutputStream для записи данных в файл.
Схематично это можно изобразить так:

В Java крайне хорошо поддерживается работа с файлами. Рассмотрим различные примеры работы с файлами.
Байты
Чаще всего, для работы с байтами используются FileInputStream и FileOutputStream. Давайте рассмотрим как это работает на примере простого приложения.
Создайте в корневой папке проекта папку resources. В это йпапке создайте два файла: inputFile.txt и outputFile.txt.
Файл inputFile.txt:
Оставьте файл outputFile.txt пустым.
В результате работы программы мы получим следуюющий результат:
После запуска приложения смотрим файл outputFile.txt
Копирование успешно выполнено.
Символы
Байтовые потоки в Java используются для ввода и вывода 8-битных элементов. Символьные потоки используюстся для работы с 16-битной кодировкой Unicode.
Для работы с символами, чаще всего используют FileReader и FileWriter. FileReader читает одновременно по 2 байта, а FileWriter, в своб очередь, записывает по 2 байта за раз.
Для понимания того, как жто работает на практике рассмотирм пример простого приложения.
Создайте в корневой папке проекта папку resources. В это йпапке создайте два файла: inputFile.txt и outputFile.txt.
Файл inputFile.txt:
Оставьте файл outputFile.txt пустым.
В результате работы программы мы получим следуюющий результат:
После запуска приложения смотрим файл outputFile.txt
Копирование успешно выполнено.
Стандартные потоки
Как и во многих других языках программирования, в Java обеспечивается ввод информации в программу во время её выполнения. Для этих целей в Java существует три стандартных потока:
- Стандратный ввод
Используется для ввода пользователем информаци (обычно – клавиатура) и представлен элементом System.in. - Стандартный вывод
Используется для вывода данных пользователю (обычно – экран монитора) и представлен элементом System.out. - Стандартная ошибка
Используется для вывода ошибок произошедших при работе программы (обычно – на экран монитора) и представлен элементом System.err.
Для понимания того, как это работает на практике, рассмотрим пример простого приложения.
Запускаем программу и начинаем вводить символы. Если нам необходимо выйти – вводим символ ‘Q’.
В результате работы рпограммы мы получим, примерно, следующий результат:
Чтение и запись файлов
Теперь рассмотрим чтение и запись файлов более подробно.
Рассмотренные ранее InputStream и OutputSream используются для чтения и записи данных из файлов записи их в файлы.
Давайте посмотрим а иерархию этих двух классов:
Далее мы рассмотрим FileInputStream и FileOutputStream.
FileInputStream
Этот элемент используется для чтения данных из файлов.
FileInputStream имеет следующие методы:
- public void close() throws IOException
Этот метод закрывает поток и освобождает любый ресурсы системы, которые связаны с этим файлом. В случае ошибки “бросает” IOException. - public void finalize() throws IOException
Этот метод “чистит” соединение с файлом. И проверяет, что метод close() был вызван. В случае ошибки “бросает” IOException. - public int read(int r) throws IOException
Этот метод читает определённые байты данных из входящего файла .Возвращает int. Если весь файл прочитан – будет возвращено занчение -1. В случае ошибки “бросает” IOException. - public int read(byte[] r) throws IOException
Этот метод читает количество байтов, равное длине массива (r.length). Если весь файл прочитан – будет возвращено занчение -1. В случае ошибки “бросает” IOException. - public int available() throws IOException
Этот метода возвращает количество байтов, которое может быть прочитано из заданного входного файла. Возвращаетint.
Рассмотрим два ключевых входных потока:
FileOutputStream
Этот поток используется для создания файлов и записи в них данных. Если файл, в который мы хотим записать данные ещё не существует, то FileOutputStream создаст его автоматически.
Исходящий поток имеет следующие методы:
- public void close() throws IOException
Этот метод закрывает исходящий поток и освобождает все ресурсы системы, который связаны с ним. В случае ошибки “бросает” IOException. - protected void finalize() throws IOException
Этот метод “чистит” соединение с файлом. Проверяет был ли вызван метод close(). В случае ошибки “бросает” IOException. - public void write(int w) throws IOException
Этот метод записывает опредлённые байты в указанный файл. В случае ошибки “бросает” IOException. - public void write(byte[] w) throws IOException
Этот метод записывает количество байтов, равное длине заданного массива (w.length) в указанный файл. В случае ошибки “бросает” IOException.
Рассмотрим два ключевых исходящих потока:
Для понимания того, как это работает на практике, рассмотрим пример простого приложения.
В результате работы программы мы получим, следующий результат:
Навигация файлов и I/O
Рассмотрим другие ключевые классы, которые мы модем использовать при работе с файлами и I/O:
Директории в Java
Директория (грубо говоря – папка) – это экземпляр класса File, который содержит список других файлов.
Для создания директорий существует два метода, которые мы можем использовать:
- mkdir()
Этот метод создаёт директорию и возвращает логическое значение true, если создание прошло успешно, и false – если нет. Ошибка может быть вызвана либо тем, что данная директория уже существует, либо тем, что вводнй путь директории ещё не создан. - mkdirs()
Этот метод метод создаёт и саму директорию и путь (родительские папки) к этой директории.
Для понимания того, как это работает на практике, рассмотрим пример простого приложения.
В результате работы программы будет создана директория (папка) с названием testDir по указанному пути. Вы олжны вставить свой желаемый путь создаваемой директории.
Кроме того, мы также можем получить список файлов и директорий, которые находятся в интересующей нас папке с помощью метода list().
Рассмотрим пример простого приложения, в которм мы попробуем получить список всех файлов и директорий в корневой папке проекта.
В результате работы программы мы получим, примерно, следующий результат:
В этом уроке мы изучили основы работы с файлами в языке программирования Java. Мы рассмотрели ключевые классы и ознакомились с примерами простых приложений, которые демонстрируют их работу.
В следующем уроке мы изучим такую важную составляющую языка программирования Java, как исключения (Exceptions).
Как создать файл и записать его на Java?
каков самый простой способ создания и записи в (текстовый) файл на Java?
30 ответов
обратите внимание, что каждый из приведенных ниже примеров кода может бросить IOException . Блоки Try/catch / finally были опущены для краткости. См.в этом уроке для получения информации об обработке исключений.
создание текстового файла (обратите внимание, что это перезапишет файл, если он уже существует):
создание двоичного файла (это также перезапишет файл):
Java 7+ пользователи могут использовать Files класс для записи в файлы:
создание текстового файла:
создание двоичного файла:
в Java 7 и выше:
есть полезные утилиты для этого, хотя:
- пакета fileutils.writeStringtoFile(..) из commons-io
- файлы.писать.(.) из гуавы
обратите внимание также, что вы can использовать FileWriter , но он использует кодировку по умолчанию, что часто является плохой идеей — лучше указать кодировку явно.
ниже оригинал, до Java 7 ответ
Если у вас уже есть содержимое, которое вы хотите записать в файл (и не генерируется на лету), java.nio.file.Files добавление в Java 7 как часть собственного ввода-вывода обеспечивает самый простой и эффективный способ достижения ваших целей.
в основном создание и запись в файл-это только одна строка, более того один простой вызов метода!
в следующем примере создается и записывается в 6 различных файлов, чтобы продемонстрировать, как его можно использовать:
вот пример программы для создания или перезаписи файла. Это длинная версия, поэтому ее легче понять.
используя try() автоматически закрыть поток. Эта версия является короткой, быстрой (буферизованной) и позволяет выбрать кодировку.
эта функция была введена в Java 7.
очень простой способ создания и записи в файл на Java:
здесь мы вводим строку в текстовый файл:
мы можем легко создать новый файл и добавить содержимое в него.
Если вы хотите иметь относительно безболезненный опыт, вы также можете взглянуть на Apache Commons IO пакет, точнее FileUtils класс.
никогда не забывайте проверять сторонние библиотеки. Joda Времени на сегодняшний день манипуляций, Apache Commons Lang StringUtils для общих строковых операций и таких может сделать ваш код более читаемым.
Java-отличный язык, но стандартная библиотека иногда немного низкоуровневый. Мощный, но все же низкий уровень.
поскольку автор не указал, требуется ли им решение для версий Java, которые были Eold (как Sun, так и IBM, и это технически самые распространенные JVMs), и из-за того, что большинство людей, похоже, ответили на вопрос автора, Прежде чем было указано, что это текст (не бинарные) файл, я решил предоставить свой ответ.
прежде всего, Java 6, как правило, достиг конца жизни, и так как автор не сделал укажите, что ему нужна устаревшая совместимость, я думаю, это автоматически означает Java 7 или выше (Java 7 еще не Eol’D IBM). Итак, мы можем посмотреть прямо на файл I/O учебник:https://docs.oracle.com/javase/tutorial/essential/io/legacy.html
- многие методы не создавали исключений, когда они терпели неудачу, поэтому это было невозможно получить сообщение об ошибке. Например, если файл ошибка удаления, программа получит «удалить сбой», но не знал бы, если бы это было потому, что файл не существовал, пользователь не есть разрешения, или была какая-то другая проблема.
- метод rename не работал последовательно на разных платформах.
- не было никакой реальной поддержки для символических ссылок.
- больше поддержка метаданных, например, файл разрешения, владелец файла и другие атрибуты безопасности. Подключение метаданные файлов неэффективны.
- многие методы файлов не масштабируются. Запрос большого списка каталогов через сервер может привести к висеть. Большие каталоги также могут вызвать проблемы с ресурсами памяти , что приводит к отказу в обслуживании.
- невозможно было написать надежный код, который может рекурсивно ходить по дереву файлов и отвечать соответственно, если бы были круговые символы связи.
Ну, это исключает java.Ио.Файл. Если файл не может быть записан / добавлен, вы можете даже не знать, почему.
вот пример (упрощенный):
другой пример (добавление):
упрощенный пример (Java 8 или выше):
другой пример (добавление):
эти методы требуют минимальных усилий со стороны автора и должны быть предпочтительнее всех других при записи в [текстовые] файлы.
Если вы по какой-то причине хотите отделить акт создания и записи, эквивалент Java touch is
createNewFile() проверяет существование и создает файл атомарно. Это может быть полезно, если вы хотите убедиться, что вы были создателем файла перед записью в него, например.
Я думаю, что это самый короткий путь:
создать файл без перезаписи существующего файла:
только одна линия ! path и line строки
самый простой способ, который я могу найти:
Это, вероятно, работать только для 1.7+.
если мы используем Java 7 и выше, а также знаем содержимое, которое будет добавлено (добавлено) в файл, мы можем использовать newBufferedWriter метод в пакете NIO.
есть несколько моментов, чтобы отметить:
- это всегда хорошая привычка, чтобы указать кодировку кодировки и для этого у нас есть константа в классе StandardCharsets .
- код использует try-with-resource инструкция, в которой ресурсы автоматически закрываются после пытаться.
хотя OP не спрашивал, но на всякий случай мы хотим искать строки с определенным ключевым словом, например confidential мы можем использовать API потока в Java:
чтение и запись файлов с помощью input и outputstream:
просто включите этот пакет:
и затем вы можете использовать этот код для записи в файл:
стоит попробовать для Java 7+:
для нескольких файлов вы можете использовать:
он отлично работает.
вот некоторые из возможных способов создания и записи файла на Java:
Использование FileOutputStream
Использование FileWriter
Использование PrintWriter
Использование OutputStreamWriter
для дальнейшего проверить этот учебник о том, как чтение и запись файлов на Java .
есть несколько простых способов, как:
вы даже можете создать временный файл с помощью системное свойство, который будет независимо от того, какую ОС вы используете.
используя библиотеку Guava Google, мы можем создавать и записывать в файл очень легко.
в примере создается новый fruits.txt файл в корневом каталоге проекта.
чтение коллекции с клиентами и сохранение в файл, с JFilechooser.
лучший способ — использовать Java7: Java 7 представляет новый способ работы с файловой системой, а также новый служебный класс-файлы. Используя класс Files, мы также можем создавать, перемещать, копировать, удалять файлы и каталоги; он также может использоваться для чтения и записи в файл.
запись с помощью FileChannel Если вы имеете дело с большими файлами, FileChannel может быть быстрее, чем стандартный IO. Следующая строка записи кода в файл с помощью FileChannel:
запись с помощью DataOutputStream
запись с помощью FileOutputStream
Давайте теперь посмотрим, как мы можем использовать FileOutputStream для записи двоичных данных в файл. Следующий код преобразует строку int bytes и записывает байты в файл с помощью FileOutputStream:
пишите с помощью PrintWriter мы можем использовать PrintWriter для записи форматированного текста в файл:
написать с BufferedWriter: используйте BufferedWriter для записи строки в новый файл:
Как создать файл java
Основные навыки и понятия
- Представление о потоках ввода-вывода
- Отличия байтовых и символьных потоков
- Классы для поддержки байтовых потоков
- Классы для поддержки символьных потоков
- Представление о встроенных потоках
- Применение байтовых потоков
- Использование байтовых потоков для файлового ввода-вывода
- Автоматическое закрытие файлов с помощью оператора try с ресурсами
- Чтение и запись двоичных данных
- Манипулирование файлами с произвольным доступом
- Применение символьных потоков
- Использование символьных потоков для файлового ввода-вывода
- Применение оболочек типов Java для преобразования символьных строк в числа
В примерах программ, приводившихся в предыдущих главах, уже применялись отдельные части системы ввода-вывода в 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
| Метод | Описание |
|---|---|
| int available() | Возвращает количество байтов, доступных для чтения |
| void close() | Закрывает поток ввода. При последующей попытке чтения из потока генерируется исключение IOException |
| void mark(int numBytes) | Ставит отметку на текущей позиции в потоке. Отметка доступна до тех пор, пока на будет прочитано количество байтов, определяемое параметром numBytes |
| boolean markSupported() | Возвращает логическое значение true, если методы mark() и reset() поддерживаются в вызывающем потоке |
| int read() | Возвращает целочисленное представление следующего байта в потоке. Если достигнут конец потока, возвращается значение -1 |
| int read(byte buffer[]) | Предпринимает попытку прочитать количество байтов, определяемое выражением buffer, length, в массив buffer и возвращает фактическое количество успешно прочитанных байтов. Если достигнут конец потока, возвращается значение -1 |
| int read(byte buffer[], int offset, int numBytes) | Предпринимает попытку прочитать количество байтов, определяемое параметром numBytes, в массив buffer, начиная с элемента buffer[offset]. Если достигнут конец потока, возвращается значение -1 |
| void reset() | Устанавливает указатель ввода на помеченной ранее позиции |
| long skip (long numBytes) | Пропускает количество байтов, определяемое параметром numBytes, в потоке ввода. Возвращает фактическое количество пропущенных байтов |
Таблица 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(), предназначенный для чтения байтов. Ниже приведены разные формы объявления этого метода.
В главе 3 было показано, как пользоваться первой формой метода read() для ввода отдельных символов с клавиатуры (а по существу, из потока стандартного ввода System, in). Достигнув конца потока, этот метод возвращает значение -1. Вторая форма метода read() предназначена для чтения данных из потока ввода в массив data. Чтение завершается по достижении конца потока, па заполнении массива или при возникновении ошибки. Метод возвращает количество прочитанных байтов или значение -1, если достигнут конец потока. И третья форма данного метода позволяет разместить прочитанные данные в массиве data, начиная с элемента, обозначаемого индексом start. Максимальное количество байтов, которые могут быть введены в массив, определяется параметром max. Метод возвращает число прочитанных байтов или значение -1, если достигнут конец потока. При возникновении ошибки в каждой из этих форм метода read() генерируется исключение IOException. Условие конца потока ввода System, in устанавливается при нажатии клавиши .
Ниже приведен краткий пример программы, демонстрирующий чтение байтов из потока ввода 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(). Ниже приведена форма объявления этого метода, которой мы будем пользоваться в дальнейшем,
При каждом вызове метод read() читает байт из файла и возвращает его как целочисленное значение. По достижении конца файла этот метод возвращает значение -1. При возникновении ошибки метод генерирует исключение IOException. Как видите, в этой форме метод 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;
/* В этой версии программы ShowFile оператор try с ресурсами применяется для автоматического закрытия файла, когда он уже больше не нужен. Примечание: для компиляции этого кода требуется JDK 7 или более поздняя версия данного комплекта. / import java.io.; class ShowFile < public static void main(String args[]) < int i; // Прежде всего следует убедиться, что оба файла были указаны, if(args.length != 1)
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. Утилита сравнения файлов
В этом проекте предстоит создать простую, но очень полезную утилиту для сравнения содержимого файлов. В ходе выполнения этой сервисной программы сначала открываются два сравниваемых файла, а затем данные читаются из них и сравниваются по соответствующему количеству байтов. Если на какой-то стадии операция сравнения дает отрицательный результат, это означает, что содержимое обоих файлов не одинаково. Если же конец обоих файлов достигается одновременно, это означает, что они содержат одинаковые данные.
- Создайте файл CompFiles.java.
- Введите в файл CompFiles.java приведенный ниже исходный код.
- Для опробования программы скопируйте сначала файл CompFiles. java во временный файл temp, а затем введите в командной строке следующее:
- Программа сообщит, что файлы одинаковы. Далее сравните файл CompFiles.java с рассмотренным ранее файлом CopyFile. j ava, введя в командной строке следующее:
- Эти файлы содержат разные данные, о чем и сообщит программа CompFiles.
- Попробуйте самостоятельно внедрить в программу CompFiles ряд дополнительных возможностей. В частности, введите в нее возможность выполнять сравнение без учета регистра символов. Программу CompFiles можно также доработать таким образом, чтобы она выводила место, где обнаружено первое отличие сравниваемых файлов.
Файлы с произвольным доступом
До сих пор нам приходилось иметь дело с последовательными файлами, содержимое которых вводилось и выводилось побайтно, т.е. строго по порядку. Но в 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
| Метод | Описание |
|---|---|
| abstract void close() | Закрывает поток ввода данных. При последующей попытке чтения генерируется исключение IOException |
| void mark (int numChars) | Ставит отметку на текущей позиции в потоке. Отметка доступна до тех пор, пока на будет прочитано количество символов, определяемое параметром numChars |
| boolean markSupported() | Возвращает логическое значение true, если поток поддерживает методы mark() и reset() |
| int read() | Возвращает целочисленное представление очередного символа из потока ввода. Если достигнут конец потока, возвращается значение -1 |
| int read(char buffer[]) | Предпринимает попытку прочитать количество байтов, определяемое выражением buffer, length, в массив buffer и возвращает фактическое количество успешно прочитанных символов. Если достигнут конец потока, возвращается значение -1 |
| abstract int read(char buffer[], int offset, int numChars) | Предпринимает попытку прочитать количество символов, определяемое параметром numChars, в массив buffer, начиная с элемента buffer [ offset]. Если достигнут конец потока, возвращается значение -1 |
| int read(CharBuffer buffer) | Предпринимает попытку заполнить буфер, определяемый параметром buffer, символами, прочитанными из входного потока. Если достигнут конец потока, возвращается значение -1. CharBuffer — это класс, представляющий последовательность символов, например строку |
| boolean ready() | Возвращает логическое значение true, если следующий запрос на получение символа может быть выполнен немедленно. В противном случае возвращается логическое значение false |
| void reset() | Устанавливает указатель ввода на помеченной ранее позиции |
| long skip(long numChars) | Пропускает количество символов, определяемое параметром numChars, в потоке ввода. Возвращает фактическое количество пропущенных символов |
Таблица 10.8. Методы, определенные в классе Writer
| Метод | Описание |
|---|---|
| Writer append(char ch) | Записывает символ ch в конец текущего потока. Возвращает ссылку на поток |
| Writer append(CharSequence chars) | Записывает символы chars в конец текущего потока. Возвращает ссылку на поток. CharSequence — это интерфейс, в котором описаны только операции чтения последовательности символов |
| Writer append(CharSequence chars, int begin, int end) | Записывает символы chars в конец текущего потока, начинаяс позиции, определяемой параметром begin, и кончая позицией, определяемой параметром end. Возвращает ссылку на поток. CharSequence — это интерфейс, в котором описаны только операции чтения последовательности символов |
| abstract void close() | Закрывает поток вывода. При последующей попытке записи в поток генерируется исключение IOException |
| abstract void flush() | Выводит текущее содержимое буфера на устройство. В результате выполнения данной операции буфер очищается |
| void write(int ch) | Записывает в вызывающий поток вывода один символ. Параметр ch относится к типу int, что позволяет вызывать данный метод в выражениях, не приводя результат их вычисления к типу char |
| void write(char buffer[]) | Записывает в вызывающий поток вывода массив символов buffer |
| abstract void write(char buffer[], int offset, int numChars) | Записывает в вызывающий поток вывода количество символов, определяемое параметром numChars, из массива buffer, начиная с элемента buffer[ offset ] |
| void write(String str) | Записывает в вызывающий поток вывода символьную строку str |
| void write(String str, int offset, int numChars) | Записывает в вызывающий поток вывода часть numChars символов из строки str, начиная с позиции, обозначаемой параметром offset |
Консольный ввод из символьных потоков
Если программа подлежит локализации, то при организации ввода с консоли символьным потокам следует отдать предпочтение перед байтовыми. А поскольку System.in — это байтовый поток, то для него придется построить оболочку в виде класса, производного от класса Reader. Наиболее подходящим для ввода с консоли является класс Buf feredReader, поддерживающий буферизованный поток ввода. Но объект типа Buf feredReader нельзя построить непосредственно из потока стандартного ввода System, in. Сначала нужно преобразовать байтовый поток в символьный. И для этой цели служит класс InputStreamReader, преобразующий байты в символы. Для того чтобы получить объект типа InputStreamReader, связанный с потоком стандартного ввода System, in, нужно воспользоваться следующим конструктором:
Поток ввода System.in является экземпляром класса InputStream, и поэтому его можно указать в качестве параметра inputStream данного конструктора.
Затем на основании объекта типа InputStreamReader можно создать объект типа BufferedReader, используя следующий конструктор:
где inputReader — это поток, который связывается с создаваемым экземпляром класса Buf feredReader. Объединяя обращения к указанным выше конструкторам в одну операцию, мы получаем приведенную ниже строку кода. В ней создается объект типа BufferedReader, связанный с клавиатурой.
После выполнения этого оператора присваивания переменная br будет содержать ссылку на символьный поток, связанный с консолью через поток ввода System.in.
Прочитать символы из потока ввода System, in можно с помощью метода read(), определенного в классе Buf f eredReader. Чтение символов мало чем отличается от чтения данных из байтовых потоков. Ниже приведены общие формы объявления трех вариантов метода read(), предусмотренных в классе Buf f eredReader.
В первом варианте метод read() читает один символ в уникоде. По достижении конца потока этот метод возвращает значение -1. Во втором варианте метод read() читает данные из потока ввода и помещает их в массив. Чтение оканчивается по достижении конца потока, по заполнении массива data символами или при возникновении ошибки. В этом случае метод возвращает число прочитанных символов, а если достигнут конец потока, — значение -1. В третьем варианте метод read() помещает прочитанные символы в массив data, начиная с элемента, определяемого индексом start. Максимальное число символов, которые могут быть записаны в массив, определяется параметром max. В данном случае метод возвращает число прочитанных символов или значение -1, если достигнут конец потока. При возникновении ошибки в каждом из перечисленных выше вариантов метода read() генерируется исключение IOException. При чтении данных из потока ввода System, in конец потока устанавливается нажатием клавиши < Enter>.
Ниже приведен пример программы, демонстрирующий применение метода 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. Кроме того, в классах-оболочках числовых типов содержатся методы, предназначенные для преобразования числовых строк в соответствующие двоичные эквиваленты. Эти методы приведены ниже. Каждый из них возвращает двоичное значение, соответствующее числовой строке.
| Оболочка типа | Метод преобразования |
|---|---|
| Double | static double parseDouble(String str) throws NumberFormatException |
| Float | static float parseFloat(String str) throws NumberFormatException |
| Long | static long parseLong(String str) throws NumberFormatException |
| Integer | static int parselnt(String str) throws NumberFormatException |
| Short | static short parseShort(String str) throws NumberFormatException |
| Byte | static byte parseByte(String str) throws NumberFormatException |
Оболочки целочисленных типов также предоставляют дополнительный метод синтаксического анализа, позволяющий задавать основание системы счисления.
Методы синтаксического анализа позволяют без труда преобразовать во внутренний формат числовые значения, введенные в виде символьных строк с клавиатуры или из текстового файла. Ниже приведен пример программы, демонстрирующий применение для этих целей методов parselnt() и parseDouble(). В этой программе находится среднее арифметическое ряда чисел, введенных пользователем с клавиатуры. Сначала пользователю прелагается указать количество числовых значений для обработки, а затем программа вводит числа с клавиатуры, используя метод readLine(), а с помощью метода parselnt() преобразует символьную строку в целочисленное значение. Далее осуществляется ввод числовых значений и последующее их преобразование в тип double с помощью метода parseDouble().
Выполнение этой программы может дать, например, следующий результат:
Пример для опробования 10.2. Создание справочной системы, находящейся на диске
В примере для опробования 4.1 был создан класс Help, позволяющий отображать сведения об операторах Java. Справочная информация хранилась в самом классе, а пользователь выбирал требуемые сведения из меню. И хотя такая справочная система выполняет свои функции, подход к ее разработке был выбран далеко не самый лучший. Так, если требуется добавить или изменить какие-нибудь сведения в подобной справочной системе, придется внести изменения в исходный код программы, которая ее реализует. Кроме того, выбирать пункт меню по его номеру не очень удобно, а если количество пунктов велико, то такой способ оказывается вообще непригодным. В этом проекте предстоит устранить недостатки, имеющиеся в справочной системе, разместив справочную информацию на диске.
В новом варианте справочная информация должна храниться в файле. Это будет обычный текстовый файл, который можно изменять, не затрагивая исходный код программы. Для того чтобы получить справку по конкретному вопросу, следует ввести название темы. Система будет искать соответствующий раздел в файле. Если поиск завершится успешно, справочная информация будет выведена на экран.
- Создайте файл, в котором будет храниться справочная информация и который будет использоваться в справочной системе. Это должен быть обычный текстовый файл, организованный следующим образом:
- Название каждой темы располагается в отдельной строке и предваряется символом #. Наличие специального символа в строке (в данном случае — #) позволяет программе быстро найти начало раздела. Под названием темы может располагаться любая справочная информация. После окончания одного раздела и перед началом другого должна быть введена пустая строка. Кроме того, в конце строк не должно быть лишних пробелов.
- Ниже приведен пример простого файла со справочной информацией, который можно использовать вместе с новой версией справочной системы. В нем хранятся сведения об операторах Java.
- Присвойте этому файлу имя helpfile.txt.
- Создайте файл FileHelp.java.
- Начните создание новой версии класса Help со следующих строк кода:
- Имя файла со справочной информацией передается конструктору класса Help и запоминается в переменной экземпляра helpfile. А поскольку каждый экземпляр класса Help содержит отдельную копию переменной helpf ile, то каждый из них может взаимодействовать с отдельным файлом. Это дает возможность создавать отельные наборы справочных файлов на разные темы.
- Добавьте в класс Help метод helpon(), код которого приведен ниже. Этот метод извлекает справочную информацию по заданной теме.
- Прежде всего обратите внимание на то, что в методе helpon() обрабатываются все исключения, связанные с вводом-выводом, поэтому в заголовке метода не указано ключевое слово throws. Благодаря такому подходу упрощается разработка методов, в которых используется метод helpon(). В вызывающем методе достаточно обратиться к методу helpon(), не заключая его вызов в блок try/catch.
- Для открытия файла со справочной информацией служит класс FileReader, оболочкой которого является класс Buf feredReader. В справочном файле содержится текст, и поэтому справочную систему удобнее локализовать через символьные потоки ввода-вывода.
- Метод helpon (\ действует следующим образом. Символьная строка, содержащая название темы, передается этому методу в качестве параметра. Метод открывает сначала файл со справочной информацией. Затем в файле производится поиск, т.е. проверяется совпадение содержимого переменной what и названия темы. Напомним, что в файле заголовок темы предваряется символом #, поэтому метод сначала ищет данный символ. Если символ найден, производится сравнение следующего за ним названия темы с содержимым переменной what. Если сравниваемые строки совпадают, то отображается справочная информация по данной теме. И если заголовок темы найден, то метод helpon() возвращает логическое значение true, иначе — логическое значение false.
- В классе Help содержится также метод getSelectionO, который предлагает задать тему и возвращает строку, введенную пользователем.
- В теле этого метода сначала создается объект типа Buf feredReader, который связывается с потоком вывода System, in. Затем в нем запрашивается название темы, которое принимается и далее возвращается вызывающей части программы.
- Ниже приведен весь исходный код программы, реализующей справочную систему, находящуюся на диске.
Упражнение для самопроверки
по материалу главы 10
- Для чего в Java определены как байтовые, так и символьные потоки?
- Как известно, ввод-вывод данных на консоль осуществляется в текстовом виде. Почему же в Java для этой цели используются байтовые потоки?
- Как открыть файл для чтения байтов?
- Как открыть файл для чтения символов?
- Как открыть файл для ввода-вывода с произвольным доступом?
- Как преобразовать числовую строку «123.23» в двоичный эквивалент?
- Напишите программу, которая будет копировать текстовые файлы. Видоизмените ее таким образом, чтобы все пробелы заменялись дефисами. Используйте при написании программы классы, представляющие байтовые потоки, а также традиционный способ закрытия файла явным вызовом метода close().
- Перепишите программу, созданную в ответ на предыдущий вопрос, таким образом, чтобы в ней использовались классы, представляющие символьные потоки. На этот раз воспользуйтесь оператором try с ресурсами для автоматического закрытия файла.
- К какому типу относится поток System. in?
- Что возвращает метод read() из класса InputStream по достижении конца потока?
- Поток какого типа используется для чтения двоичных данных?
- Классы Reader и Writer находятся на вершине иерархии классов _______ .
- Оператор try без ресурсов служит для ______ .
- Если для закрытия файла используется традиционный способ, то это лучше всего делать в блоке finally. Верно или неверно?
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.