Программы, написанные нами до этого, воспринимали информацию только с консоли (клавиатуры) или диалоговых окон ввода и выводили результат тоже на консоль (экран монитора) или в графические компоненты.
Однако во многих случаях требуется выводить результат на принтер, в файл, базу данных или передавать по сети. Исходные данные тоже часто приходится загружать из файла, базы данных или из сети.
Для того, чтобы отвлечься от особенностей конкретных устройств ввода/вывода, в Java употребляется понятие потока (stream). Поток представляет собой накапливающуюся последовательность данных, поступающих из какого-то источника. Порция данных может быть считана из потока, при этом она из него изымается. В потоке действует принцип очереди FIFO: первым пришел – первым вышел.
Считается, что в программу идет входной поток (input stream) символов Unicode или просто байтов, воспринимаемый в программе методами read().Из программы методами write(),print(), println(), printf() выводится выходной поток (output stream) символов или байтов. При этом неважно, куда направлен этот поток – на консоль, на принтер, в файл или в сеть.
Понятие потока оказалось настолько удобным и облегчающим программирование ввода/ вывода, что в Java предусмотрена возможность создания потоков, направляющих символы или байты не на внешнее устройство, а в массив или из массива, то есть связывающих программу с областью оперативной памяти. Более того, можно создать поток, связанный со строкой типа String, находящейся также в оперативной памяти. Еще один вид потока – поток байтов, составляющих объект Java. Его можно направить в файл или передать по сети, а потом восстановить в оперативной памяти. Эта операция называется сериализацией (serialization) объектов.
Методы организации потоков собраны в классы пакета java.io.
В общем случае файлом называется последовательность некоторых однотипных информационных компонентов, сохраняемая во внешней памяти компьютера под одним именем. На этом основании файл считается структурой данных.
Полномасштабная работа с файлами принадлежит к прерогативам операционной системы. Здесь мы используем специальные команды создания, просмотра, копирования и удаления файлов, знакомимся с их свойствами, сортируем их, а также объединяем в иерархическую древовидную структуру каталогов. Однако выполнение всех перечисленных операций с файлами организуется и осуществляется, как правило, вручную. Средства работы с файлами, предусмотренные в Java, позволяют автоматизировать эти функции.
Существуют задачи, для которых заранее невозможно определить количество выходных или входных данных. Оно определяется только в процессе решения конкретной задачи, то есть при работе программы. Поэтому возникла необходимость в специальной структуре данных, которая представляла бы собой последовательность компонентов, в общем случае разнотипных, причем длина этой последовательности заранее не определялась, а конкретизировалась при выполнении программы. К тому же, эта структура данных должна была бы храниться не в оперативной памяти компьютера, а на внешних устройствах.
В современных алгоритмических языках такую структуру данных называют файлом.
Таким образом, файл – это упорядоченная совокупность однотипных элементов, имеющая произвольную длину.
Аналогом файлов является магнитофонная лента: количество записей в ней заранее неизвестно, всегда доступна для прослушивания только текущая запись, и для прослушивания очередной записи необходимо прослушать или перемотать все предыдущие.
Поскольку файлы в большинстве современных операционных систем понимаются как последовательность байтов, для файлового ввода/вывода создаются байтовые потоки с помощью классов FileInputStream (поток ввода – чтение из файла) и FileOutputStream (поток вывода – запись в файл). Это особенно удобно для бинарных файлов, хранящих байт-коды, архивы, изображение, звук. Но очень много файлов содержат тексты, составленные из символов. Несмотря на то, что символы могут храниться в кодировке Unicode, эти тексты чаще всего записываются в байтовых кодировках. Поэтому и для текстовых файлов можно использовать байтовые потоки. В таком случае со стороны программы придется организовать преобразование байтов в символы и обратно.
Чтобы облегчить это преобразование, в пакет java.io введены классы FileReader и FileWriter. Они организуют преобразование потока: со стороны программы потоки символьные, со стороны файла – байтовые. Несмотря на различие потоков, использование классов файлового ввода/вывода очень похоже.
Чтобы открыть нужный файл для чтения или записи, необходимо создать объект класса FileInputStream и FileOutputStream. Аргументом конструктора класса указывается полное имя открываемого файла. Конструкторы обоих классов могут выбрасывать необрабатываемое исключение класса FileNotFoundException. Таким образом, создание объекта соответствующего класса в случае успеха означает открытие соответствующего файла для чтения/записи. После завершения работы с файлом его необходимо закрыть с использованием метода close(), который определен в каждом из классов.
Для побайтового считывания данных из файла используется метод read() класса FileInputStream. Считанный байт возвращается в виде целого числа. При считывании символа конца файла (EOF) методом read() в качестве результата возвращается значение -1.
Для записи данных в файл используется метод write() класса FileOutputStream. Записываемый в файл байт указывается аргументом метода. Метод невозвращает никакого результата. Методы close(), read(), write() могут выбрасывать необрабатываемое исключение IOException.
Пример: считать из текстового файла ishod.txt информацию, записанную в нем, и переписать ее в файл result.txt, заменяя пробелы символами подчеркивания. Исходный файл имеет вид: