Главная » Java » Фильтрующие потоки

0

добавляют несколько новых конструкторов к базовым конструкторам классов InputStream и OutputStream. Им передается поток соответствующего  типа (входной или выходной), с которым необходимо соединить объект. позволяют объединять потоки в “цепочки” и тем самым создавать составной поток с большими возможностями. Приведенная программа печатает номер строки файла, в которой будет обнаружено первое вхождение заданного символа:

import java.io.*;

class FindChar {

public static void main (String[] args)

throws Exception

{

if (args.length != 2)

throw new Exception("need char and file");

int match = args[0].charAt(0); FileInputStream

fileIn = new FileInputStream(filein);

int ch;

while ((ch == in.read()) != -1) {

if (ch == match) {

System.out.println("’" + (char)ch +

"’ at line " + in.getLineNumber()); System.exit(0);

}

}

System.out.println(ch + " not found"); System.exit(1);

}

}

Программа создает поток класса FileInputStream с именем fileIn для чтения из указанного файла и затем вставляет перед ним объект класса LineNumberInputStream с именем in. Объекты LineNumberInputStream получают ввод от входных потоков, за которыми они закреплены, и следят за нумерацией строк. При чтении байтов из in на самом деле происходит чтение из потока fileIn, который получает эти байты из входного файла. Если запустить программу с файлом ее собственного исходного текста и буквой ‘I’ в качестве аргументов, то результат работы будет выглядеть следующим образом:

‘I’ at line 10

Вы можете “сцепить” произвольное количество объектов FilterInput Stream. В качестве исходного источника байтов допускается произвольный объект InputStream, не обязательно относящийся к классу FilterInput Stream. Возможность сцепления является одним из основных достоинств фильтрующих потоков, причем самый первый поток в цепочке не должен относиться к классу FilterInputStream.

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

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

LineNumberInputStream

lnum = new LinenumberInputStream(System.in); System.in = lnum;

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

lnum.getLineNumber();

Поток LineNumberInputStream, закрепленный за другим потоком Input Stream, следует контракту последнего, если InputStream — единственный тип, к которому мог бы относиться данный поток. System.in может быть отнесен только к типу InputStream, так что весь код программы, в котором он используется, вправе рассчитывать только на выполнение контракта этого типа. LineNu m berInputStream поддерживает более широкий спектр функций, так что замена исходного объекта на тот же самый объект с добавленными функциями нумерации строк оказывается вполне допустимой.

Упражнение 11.2

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

Упражнение 11.3

Расширьте FilterOutputStream  для создания класса, который преобразует каждое слово входного потока в заглавный регистр (title case). /См. раздел 13.5 – Примеч. перев/

Упражнение 11.4

Создайте пару фильтрующих потоковых классов для работы со сжатыми в произвольном формате данными; при этом поток CompressInputStream должен уметь расшифровывать данные, созданные потоком Compress OutputStream.

11.6. Класс PrintStream

Класс PrintStream используется каждый раз, когда в вашей программе встречается вызов метода print или println. PrintStream является расширением FilterOutputStream,  так что передаваемые байты могут подвергаться фильтрации. Класс содержит методы print и println для следующих типов:

char

int

float

Object

boolean

char[]

long

double

String

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

PrintStream содержит два конструктора. Один из них — конструктор FilterOutputStream, получающий в качестве параметра объект-поток. У другого конструктора имеется второй параметр логического типа, который управляет автоматической  очисткой (autoflushing) потока. Если значение этого аргумента равно true, то запись в поток символа перехода на новую строку ‘\n’ приводит к вызову метода flush. В противном случае такой

символ ничем не отличается от всех остальных, и flush не вызывается. После

конструирования потока его поведение в отношении автоматической  очистки уже не может быть изменено.

При включении автоматической  очистки вызов какого-либо из методов write, записывающего массив байтов, приводит к обращению к flush. Символы ‘\n’, которые встречаются внутри массивов, не вызывают flush, независимо от состояния флага автоматической  очистки.

Методы print(String) и print(char[]) являются синхронизированными. Все остальные методы print и println реализуются с помощью этих двух методов, так что печать в объект PrintStream является безопасной при многопоточной работе.

11.7. Буферизованные потоки

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

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

byte для промежуточного хранения байтов, проходящих через поток.

Если метод read вызывается для пустого потока BufferedInputStream, он выполняет следующие действия: обращается к методу read потока-источника,  заполняет буфер максимально возможным количеством байтов и возвращает запрошенные данные из буфера.

Аналогично ведет себя и BufferedOutputStream. Когда очередной вызов write приводит к заполнению буфера, вызывается метод write потока-приемника,  направляющий содержимое буфера в поток.

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

OutputStream bufferedFile(String path)

throws IOExceptioon

{

OutputStream out = new FileOutputStream(path);

return new BufferedOutputStream(out);

}

Сначала для указанного пути создается FileOutputStream, затем порождается BufferedOutputStream и возвращается полученный буферизованный  объект-поток. Подобная схема позволяет буферизовать вывод, предназначенный  для занесения в файл.

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

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

Байтовые массивы, используемые в качестве источников входных или приемников выходных потоков, могут применяться для построение строк с данными для печати, декодирования данных и т. д. Эти возможности предоставляются потоками ByteArray. Методы потоков ByteArray являются синхронизированными, а следовательно — безопасными в условиях многопоточной среды.

Класс ByteArrayInput использует в качестве источника данных массив типа byte. Он содержит два конструктора:

public ByteArrayInputStream(byte[] buf)

Создает объект ByteArrayInputStream по заданному байтовому массиву. Массив используется непосредственно, а не копируется. Достижение конца массива buf означает завершение ввода данных из потока.

public ByteArrayInputStream(byte[] buf, int offset, int length)

Создает объект ByteArrayInputStream по заданному байтовому массиву, однако используется лишь часть массива buf от buf[offset] до buf [offset+length-1] или до конца массива (в зависимости от того, какая величина окажется меньше).

Класс ByteArrayOutput осуществляет вывод в динамически увеличиваемый байтовый массив. Он содержит следующие конструкторы и методы:

public ByteArrayOutputStream()

Создает объект ByteArrayOutputStream, размер которого выбирается по умолчанию. public ByteArrayOutputStream(int size)

Создает объект ByteArrayOutputStream с заданным исходным размером. public synchronized byte[] toByteArray()

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

изменяя выходных данных. public int size()

Возвращает текущий размер буфера.

public String toString(int hiByte)

Создает новый объект String на основе содержимого байтового массива. Старшие 8 бит каждого 16-разрядного символа в строке устанавливаются  равными 8 младшим битам hiByte. Также имеется переопределенная  безаргументная форма toString, эквивалентная toString(0).

11.9. Класс StringBufferInputStream

StringBufferInputStream читает данные из строки String, а не из байтового массива. Класс содержит единственный конструктор, параметром которого является строка — источник ввода. Работа с символами строки осуществляется так, как если бы это были байты. Например, приведенная ниже программа читает символы из командной строки или из System.in:

class Factor {

public static void main(String[] args) {

if (args.kength == 0) {

factorNumbers(System.in);

} else {

InputStream in;

for (int i = 0; i << args.lengthl i++) {

in = new StringBufferInputStream(args[i]);

factorNumbers(in);

}

}

}

// …

}

Если команда вызывается без параметров, то factorNumbers берет числа из стандартного входного потока. Если же в командной строке присутствуют параметры, то для каждого из них создается объект StringBufferInput Stream и вызывается метод factorNumbers.

Входные данные этого метода рассматриваются  как единая последовательность  байтов, независимо от того, взяты ли они из командной строки или из стандартного входного потока .

Обратите внимание на то, что в конструктор StringBufferInputStream передается объект класса String, а не StringBuffer.

Парного потока для StringBufferOutputStream не существует. При необходимости его можно имитировать, применяя метод toString к потоку Byt eArrayOutputStream.

11.10. Файловые потоки и FileDescriptor

Ввод и вывод в приложениях часто связан с чтением/записью  файлов. Файловый ввод/вывод в Java представлен двумя потоками — FileInput Stream и FileOutputStream. Объекты каждого из этих типов создаются одним из трех конструкторов:

Конструктор с параметром типа String, содержащим имя файла. Конструктор с параметром File (см. раздел “Класс File”). Конструктор с параметром класса FileDescriptor.

Файловый дескриптор FileDescriptor представляет собой системно-зависимый объект,

служащий для описания открытого файла. Он может быть получен вызовом метода getFD для любого объекта класса File или Random AccessFile. Объекты FileDescriptor позволяют создавать новые потоки File или RandomAccessFile  для тех же файлов, что и другие потоки, но при этом не требуется знание имени файлов. Необходимо соблюдать осторожность и следить за тем, чтобы различные потоки не пытались одновременно совершать с файлом различные операции. Например, невозможно предсказать, что случится, когда два потока попытаются одновременно записать информацию в один и тот же файл с использованием двух разных объектов File Descriptor.

Метод flush класса FileOutputStream  гарантирует лишь сброс содержимого буфера в файл. Он не гарантирует, что данные будут записаны на диск — файловая система может осуществлять свою собственную буферизацию.

11.11. Конвейерные потоки

Конвейерные (piped) потоки используются парами, предназначенными  для ввода/вывода; байты, записанные во входной поток пары, считываются на выходе. Конвейерные потоки безопасны в многопоточной среде; на самом деле, один из вполне надежных способов работы с конвейерными потоками заключается в использовании двух программных потоков — одного для чтения, а другого для записи. В случае заполнения конвейера происходит блокировка программного потока, осуществляющего  запись. Если же чтение и запись производятся в одном программном потоке, то он блокируется навсегда.

В приведенном ниже примере создается новый программный поток, получающий входные данные от некоторого объекта-генератора, а его вывод направляется в объект OutputStream:

class Pipe {

public static void main(String[] args) {

try {

PipedOutputStream out = new PipeOutputStream(); PipedInputStream in = new PipedInputStream(out);

// генератор данных выводит данные

// в предоставленный ему выходной поток DataGenerator data = new DataGenerator(out); data.setPriority(Thread.MIN_PRIORITY); data.start();

int ch;

while ((ch = in.read()) != -1) System.out.print((char)ch);

System.out.println();

} catch (IOException e) { System.out.println("Exception: " + e);

}

}

}

Мы создаем конвейерные потоки, задавая PipedInputStream  в качестве параметра конструктора PipedOutputStream.  Порядок значения не имеет: с тем же успехом можно было передавать выходной поток конструктору входного. Важно, чтобы парные потоки

ввода/вывода были соединены друг с другом. Далее конструируется объект DataGenerator и выходным потоком сгенерированных  данных назначается PipedOutputStream.  Затем в цикле происходит чтение данных от генератора и запись их в системный выходной поток. В конце необходимо убедиться, что последняя выводимая строка будет должным образом завершена.

Источник: Арнольд К., Гослинг Д. – Язык программирования Java (1997)

По теме:

  • Комментарии