Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Чтение и запись файлов. ГЛАВА 18 Потоки ввода/вывода




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

Существует несколько способов чтения и записи в File. Возможно, простейшим из них является следующий:

  • Создать FileOutputStream с объектом File для записи в него.
  • Создать FileInputStream с объектом File для чтения из него.
  • Вызвать read() для чтения из File и write() для записи в него.
  • Закрыть потоки, выполнить при необходимости операции по очистке.

Код может выглядеть примерно так:

try {    File source = new File(" input. txt" );    File sink = new File(" output. txt" );      FileInputStream in = new FileInputStream(source);    FileOutputStream out = new FileOutputStream(sink);    int c;      while ((c = in. read())! = -1)    out. write(c);      in. close();    out. close();                    } catch (Exception e) {    e. printStackTrace(); }         

Здесь мы создаем два объекта File: FileInputStream для чтения из исходного файла и FileOutputStream для записи в выходной файл. Примечание: Этот пример был адаптирован из примера Java. sun. com CopyBytes. java (см. раздел " Ресурсы". ) Затем мы читаем каждый байт источника и записываем его в назначение. После этого мы закрываем потоки. Может показаться разумным поместить вызовы close() в блок finally. Однако компилятор языка Java будет все равно требовать от вас перехвата различных возникающих исключительных ситуаций, что означает появление еще одного выражения catch в вашем блоке finally. Стоит ли? Возможно.

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

Буферизация потоков

Существует несколько способов чтения из объекта File и записи в него, но обычным и самым удобным подходом является следующий:

  • Создать FileWriter с объектом File.
  • Заключить FileWriter в BufferedWriter.
  • Вызывать write() с BufferedWriter столько раз, сколько необходимо для записи содержимого File, заканчивая каждую строку символом конца строки (то есть, \n).
  • Вызвать flush() с BufferedWriter для его очистки.
  • Закрыть BufferedWriter, выполнив другие операции по очистке при необходимости.

Код может выглядеть примерно так:

try {    FileWriter writer = new FileWriter(aFile);    BufferedWriter buffered = new BufferedWriter(writer);    buffered. write(" A line of text. \n" );    buffered. flush(); } catch (IOException e1) {    e1. printStackTrace(); }

Здесь мы создаем FileWriter с aFile, затем заключаем его в BufferedWriter. Буферизированная запись намного эффективнее простой записи байтов по одному. После записи каждой строки (которые вручную завершаем символом \n) мы вызываем flush() с BufferedWriter. Если бы мы этого не сделали, то не увидели бы каких-либо данных в файле, что аннулировало бы цель всех этих операций для записи файла.

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

String line = null; StringBuffer lines = new StringBuffer(); try {    FileReader reader = new FileReader(aFile);    BufferedReader bufferedReader = new BufferedReader(reader);    while ( (line = bufferedReader. readLine())! = null) {           lines. append(line);           lines. append(" \n" ); } } catch (IOException e1) {    e1. printStackTrace(); } System. out. println(lines. toString());

Мы создали FileReader, затем заключили его в BufferedReader. Это позволило нам использовать удобный метод readLine(). Мы читали каждую строку до тех пор, пока они не закончились, добавляя каждую строку в конец нашего StringBuffer. При чтении из файла могла бы возникнуть исключительная ситуация IOException, поэтому мы окружили всю нашу логику чтения файла блоком try/catch.

ГЛАВА 18 Потоки ввода/вывода

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

Для того чтобы отвлечься от особенностей конкретных устройств ввода/вывода, в Java употребляется понятие потока (stream). Считается, что в программу идет входной поток (input stream) символов Unicode или просто байтов, воспринимаемый в программе методами read(). Из программы методами write о или print (), println() выводится выходной поток (output stream) символов или байтов. При этом неважно, куда направлен поток: на консоль, на принтер, в файл или в сеть, методы write () и print () ничего об этом не знают.

Можно представить себе поток как трубу, по которой в одном направлении последовательно " текут" символы или байты, один за другим. Методы read (), write (), print (), println () взаимодействуют с одним концом трубы, другой конец соединяется с источником или приемником данных конструкторами классов, в которых реализованы эти методы.

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

Три потока определены в классе system статическими полями in, out и err. Их можно использовать без всяких дополнительных определений, что мы и делали на протяжении всей книги. Они называются соответственно стандартным вводом (stdin), стандартным выводом (stdout) и стандартным выводом сообщений (stderr). Эти стандартные потоки могут быть соединены с разными конкретными устройствами ввода и вывода.

Потоки out и err — это экземпляры класса Printstream, организующего выходной поток байтов. Эти экземпляры выводят информацию на консоль методами print (), println () и write (), которых в классе Printstream имеется около двадцати для разных типов аргументов.

Поток err предназначен для вывода системных сообщений программы: трассировки, сообщений об ошибках или, просто, о выполнении каких-то этапов программы. Такие сведения обычно заносятся в специальные журналы, log-файлы, а не выводятся на консоль. В Java есть средства переназначения потока, например, с консоли в файл.

Поток in — это экземпляр класса inputstream. Он назначен на клавиатурный ввод с консоли методами read(). Класс inputstream абстрактный, поэтому реально используется какой-то из его подклассов.

Понятие потока оказалось настолько удобным и облегчающим программирование ввода/вывода, что в Java предусмотрена возможность создания потоков, направляющих символы или байты не на внешнее устройство, а в массив или из массива, т. е. связывающих программу с областью оперативной памяти. Более того, можно создать поток, связанный со строкой типа string, находящейся, опять-таки, в оперативной памяти. Кроме того, можно создать канал (pipe) обмена информацией между подпроцессами.

Еще один вид потока — поток байтов, составляющих объект Java. Его можно направить в файл или передать по сети, 'а потом восстановить в оперативной памяти. Эта операция называется сериализацией (serialization) объектов.

Методы организации потоков собраны в классы пакета java. io.

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

Еще одна возможность, предоставляемая классами пакета java. io, — слить несколько потоков в один поток.

Итак, в Java есть целых четыре иерархии классов для создания, преобразования и слияния потоков. Во главе иерархии четыре класса, непосредственно расширяющих класс object:

  • Reader — абстрактный класс, в котором собраны самые общие методы символьного ввода;
  • writer — абстрактный класс, в котором собраны самые общие методы символьного вывода;
  • inputstream — абстрактный класс с общими методами байтового ввода;
  • Outputstream — абстрактный класс с общими методами байтового вывода.

Классы входных потоков Reader и inputstream определяют по три метода ввода:

  • read () — возвращает один символ или байт, взятый из входного потока, в виде целого значения типа int; если поток уже закончился, возвращает -1;
  • read (chart] buf) — заполняет заранее определенный массив buf символами из входного потока; в классе inputstream массив типа bytet] и заполняется он байтами; метод возвращает фактическое число взятых из потока элементов или -1, если поток уже закончился;
  • read (char[] buf, int offset, int len) — заполняет часть символьного или байтового массива buf, начиная с индекса offset, число взятых из потока элементов равно len; метод возвращает фактическое число взятых из потока элементов или -1.

Эти методы выбрасывают IOException, если произошла ошибка ввода/вывода.

Четвертый метод skip (long n) " проматывает" поток с текущей позиции на п символов или байтов вперед. Эти элементы потока не вводятся методами read(). Метод возвращает реальное число пропущенных элементов, которое может отличаться от п, например поток может закончиться.

Текущий элемент потока можно пометить методом mark (int n), а затем вернуться к помеченному элементу методом reset о, но не более чем через п элементов. Не все подклассы реализуют эти методы, поэтому перед расстановкой пометок следует обратиться к логическому методу marksupported (), который возвращает true, если реализованы методы расстановки и возврата к пометкам.

Классы выходных потоков writer и outputstream определяют по три почти одинаковых метода вывода:

  • write (char[] buf) — выводит массив в выходной поток, в классе Outputstream массив имеет тип byte[];
  • write (char[] buf, int offset, int len) — выводит len элементов массива buf, начиная с элемента с индексом offset;
  • write (int elem) в классе Writer - выводит 16, а в классе Outputstream 8 младших битов аргумента elem в выходной поток,

В классе writer есть еще два метода:

  • write (string s) — выводит строку s в выходной поток;
  • write (String s, int offset, int len) — выводит len символов строки s, начиная с символа с номером offset.

Многие подклассы классов writer и outputstream осуществляют буферизованный вывод. При этом элементы сначала накапливаются в буфере, в оперативной памяти, и выводятся в выходной поток только после того, как буфер заполнится. Это удобно для выравнивания скоростей вывода из программы и вывода потока, но часто надо вывести информацию в поток еще до заполнения буфера. Для этого предусмотрен метод flush о. Данный метод сразу же выводит все содержимое буфера в поток.

Наконец, по окончании работы с потоком его необходимо закрыть методом closed.

Классы, входящие в иерархии потоков ввода/вывода, показаны на рис. 18. 1 и 18. 2.

Рис. 18. 1. Иерархия символьных потоков

Рис. 18. 2. Классы байтовых потоков

Все классы пакета java. io можно разделить на две группы: классы, создающие поток (data sink), и классы, управляющие потоком (data processing).

Классы, создающие потоки, в свою очередь, можно разделить на пять групп:

  • классы, создающие потоки, связанные с файлами:

FileReader FilelnputStream

FileWriterFile Outputstream

RandomAccessFile

  • классы, создающие потоки, связанные с массивами:

CharArrayReader ByteArraylnputStream

CharArrayWriter ByteArrayOutputStream

  • классы, создающие каналы обмена информацией между подпроцессами:

PipedReader PipedlnputStream

PipedWriter PipedOutputStream

  • классы, создающие символьные потоки, связанные со строкой:

StringReader

StringWriter

  • классы, создающие байтовые потоки из объектов Java:

ObjectlnputStream

ObjectOutputStream

Слева перечислены классы символьных потоков, справа — классы байтовых потоков.

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

Четыре класса созданы специально для преобразования потоков:

FilterReader FilterlnputStream

FilterWriter FilterOutputStream

Сами по себе эти классы бесполезны — они выполняют тождественное преобразование. Их следует расширять, переопределяя методы ввода/вывода. Но для байтовых фильтров есть полезные расширения, которым соответствуют некоторые символьные классы. Перечислим их.

Четыре класса выполняют буферизованный ввод/вывод:

BufferedReader BufferedlnputStream

BufferedWriter BufferedOutputStream

Два класса преобразуют поток байтов, образующих восемь простых типов Java, в эти самые типы:

DatalnputStream DataOutputStream

Два класса содержат методы, позволяющие вернуть несколько символов или байтов во входной поток:

PushbackReader PushbacklnputStream

Два класса связаны с выводом на строчные устройства — экран дисплея, принтер:

PrintWriter PrintStream

Два класса связывают байтовый и символьный потоки:

  • inputstreamReader — преобразует входной байтовый поток в символьный поток;
  • Outputstreamwriter — преобразует выходной символьный поток в байтовый поток.

Класс streamTokenizer позволяет разобрать входной символьный поток на отдельные элементы (tokens) подобно тому, как класс stringTokenizer, рассмотренный нами в главе 5, разбирал строку.

Из управляющих классов выделяется класс sequenceinputstream, сливающий несколько потоков, заданных в конструкторе, в один поток, и класс

LineNumberReader, " умеющий" читать выходной символьный поток построчно. Строки в потоке разделяются символами '\n' и/или '\г'.

Этот обзор классов ввода/вывода немного проясняет положение, но не объясняет, как их использовать. Перейдем к рассмотрению реальных ситуаций.

Консольный ввод/вывод

Для вывода на консоль мы всегда использовали метод printino класса Pnntstream, никогда не определяя экземпляры этого класса. Мы просто использовали статическое поле out класса system, которое является объектом класса PrintStream. Исполняющая система Java связывает это поле с консолью.

Кстати говоря, если вам надоело писать system. out. printino, то вы можете определить новую ссылку на system, out, например:

PrintStream pr - System. out;

и писать просто pr. printin ().

Консоль является байтовым устройством, и символы Unicode перед выводом на консоль должны быть преобразованы в байты. Для символов Latin 1 с кодами '\u0000' — '\u00FF' при этом просто откидывается нулевой старший байт и выводятся байты '0х00' —'0xFF'. Для кодов кириллицы, которые лежат в диапазоне '\u0400 1 —'\u04FF 1 кодировки Unicode, и других национальных алфавитов производится преобразование по кодовой таблице, соответствующей установленной на компьютере л окал и. Мы обсуждали это в главе 5.

Трудности с отображением кириллицы возникают, если вывод на консоль производится в кодировке, отличной от локали. Именно так происходит в русифицированных версиях MS Windows NT/2000. Обычно в них устанавливается локаль с кодовой страницей СР1251, а вывод на консоль происходит в кодировке СР866.

В этом случае надо заменить Printstream, который не может работать с сим-, вольным потоком, на Printwriter и " вставить переходное кольцо" между потоком символов Unicode и потоком байтов system, out, выводимых на консоль, в виде объекта класса OutputstreamWriter. В конструкторе этого объекта следует указать нужную кодировку, в данном случае, СР866. Все это можно сделать одним оператором:

PrintWriter pw = new PrintWriter(

new OutputstreamWriter(System. out, " Cp866" ), true);

Класс Printstream буферизует выходной поток. Второй аргумент true его конструктора вызывает принудительный сброс содержимого буфера в выходной поток после каждого выполнения метода printin(). Но после print() буфер не сбрасывается! Для сброса буфера после каждого print() надо писать flush(), как это сделано в листинге 18. 2.

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...