Контрольнаяработа
Программированиена Java (теория)
СОДЕРЖАНИЕ
1. Динамическая инициализация объектов
2. Чтение и запись файлов
1. Динамическаяинициализация объектов
Объекты в Javaсоздаются с помощью зарезервированного слова new, после которого идетконструктор – специальная подпрограмма, занимающаяся созданием объекта иинициализацией полей создаваемого объекта. Для него не указывается типвозвращаемого значения, и он не является ни методом объекта (вызывается черезимя класса когда объекта еще нет), ни методом класса (в конструкторе доступенобъект и его поля через ссылку this). На самом деле конструктор в сочетании соператором new возвращает ссылку на создаваемый объект и может считаться особымвидом методов, соединяющим в себе черты методов класса и методов объекта.
Если в объекте присоздании не нужна никакая дополнительная инициализация, можно использоватьконструктор, который по умолчанию присутствует для каждого класса. Это имякласса, после которого ставятся пустые круглые скобки – без списка параметров.Такой конструктор при разработке класса задавать не надо, он присутствуетавтоматически. Если требуется инициализация, обычно применяют конструкторы сосписком параметров.
Порядок вызовов присоздании объекта некого класса (будем называть его дочерним классом):
1. Создаетсяобъект, в котором все поля данных имеют значения по умолчанию (нули на двоичномуровне представления).
2. Вызываетсяконструктор дочернего класса.
3. Конструктордочернего класса вызывает конструктор родителя (непосредственного прародителя),а также по цепочке все прародительские конструкторы и инициализации полей,заданных в этих классах, вплоть до класса Object.
4. Проводитсяинициализация полей родительской части объекта значениями, заданными в декларацииродительского класса.
5. Выполняетсятело конструктора родительского класса.
6. Проводитсяинициализация полей дочерней части объекта значениями, заданными в декларациидочернего класса.
7. Выполняетсятело конструктора дочернего класса.
Знание данного порядкаважно в случаях, когда в конструкторе вызываются какие-либо методы объекта, инадо быть уверенным, что к моменту вызова этих методов объект получитправильные значения полей данных.
Как правило, дляинициализации полей сложно устроенных объектов используют конструкторы. Нокроме них в Java, в отличие от большинства других языков программирования, дляэтих целей могут также служить блоки инициализации класса и блоки инициализацииобъекта. Синтаксис задания классов с блоками инициализации следующий:
Модификаторы classИмяКласса extends ИмяРодителя {
Задание полей;
static {
тело блокаинициализации класса
}
{
тело блокаинициализации объекта
}
Задание подпрограмм — методов класса, методов объекта, конструкторов
}
Блоков инициализациикласса и блоков инициализации объекта может быть несколько.
Порядок выполненияоператоров при наличии блоков инициализации главного класса приложения(содержащего метод main):
1. инициализацияполей данных и выполнение блоков инициализации класса (в порядке записи вдекларации класса);
2. методmain;
3. выполнениеблоков инициализации объекта;
4. выполнениетела конструктора класса.
Для других классовпорядок аналогичен, но без вызова метода main:
1. инициализацияполей данных и выполнение блоков инициализации класса (в порядке записи вдекларации класса);
2. выполнениеблоков инициализации объекта;
3. выполнениетела конструктора класса.
Чем лучше пользоваться,блоками инициализации или конструкторами? Ответ, конечно, неоднозначен: в однихситуациях – конструкторами, в других – блоками инициализации. Для приданияначальных значений переменным класса в случаях, когда для этого требуютсясложные алгоритмы, можно пользоваться только статическими блокамиинициализации. Для инициализации полей объектов в общем случае лучше пользоватьсяконструкторами, но если необходимо выполнить какой-либо код инициализации довызова унаследованного конструктора, можно воспользоваться блоком динамическойинициализации.
Приведем несколько примеров.Программа SmallSquares (маленькие квадраты) возвращает квадрат маленькогоцелого числа. SmallSquares имеет две статические переменные и единственнуюоткрытую статическую функцию getSquare().
publicclass SmallSquares {
privatestatic final int LIMIT = 10;
privatestatic final int[] square = new int[LIMIT];
publicSmallSquares() {
for(int i = 0; i
square[i]= i * i;
}
}
}
publicstatic int getSquare(int i) {
returnsquare[i];
}
publicstatic void main(String[] args) {
newSmallSquares();
System.out.println(«3squared is » +
getSquare(3));
}
}
Откомпилировав изапустив SmallSquares, получим следующий результат:
3 squared is 9 (3 вквадрате будет 9)
В данном коде объектсоздается при помощи конструктора так называемым статическим методом.
Приведем примерстатической инициализации. За словом статический (static) следует блок кода,окруженного фигурными скобками. Можно использовать статический блок дляинициализации массива квадратов следующим образом:
static{
for(int i = 0; i
square[i] = i * i;
}
}
Статический блокзапрашивается только один раз во время создания класса. Теперь не нуженконструктор, и можно вызывать статическую функцию getSquare() безпредшествующего создания класса. Улучшенный кодвыглядитследующимобразом:
publicclass SmallSquares {
privatestatic final int LIMIT = 10;
privatestatic final int[] square = new int[LIMIT];
static{
for(int i = 0; i
square[i]= i * i;
}
}
publicstatic int getSquare(int i) {
// Нет обработкиошибки, предположим,0
returnsquare[i];
}
publicstatic void main(String[] args) {
System.out.println(«3squared is » + getSquare(3));
}
}
Приведем примерприменения блока динамической инициализации. Код в программе,ConstructorExample (пример конструктора), снова инициализирует массив целыхчисел. Существует две версии конструктора. Первая — конструктор без аргумента,который по умолчанию определяет значение «Безымянный»(«Anonymous»). Во второй версии есть один аргумент: значение имяпользователя (userName). Конструкторы объединены, так как квадрат долженинициализироваться в каждом случае.
publicclass ConstructorExample {
privatefinal String userName;
privatefinal static int[] square = new int[10];
publicConstructorExample() {
this(«Anonymous»);
}
publicConstructorExample(String userName) {
this.userName= userName;
for(int i = 0; i
square[i]= i * i;
}
}
publicvoid printSquare(int i) {
//no error handling — assume 0
System.out.println(«Hello» + userName);
System.out.println(i+ " squared is " + square[i]);
}
publicstatic void main(String[] args) {
newConstructorExample().printSquare(3);
newConstructorExample(«Ed»).printSquare(5);
}
}
Откомпилируем и запустимConstructorExample. В итоге получим следующий результат:
Hello Anonymous (приветБезымянный)
3 squared is 9 (3 вквадрате будет 9)
Hello Ed (ПриветЭд)
5 squared is 25 (5 вквадрате будет 25)
В приведенном вышепримере квадрат инициализируется не правильно в зависимости от того, вызываетли пользователь конструктор без аргумента или использует сигнатуру, требующуюстроку (String). Пример конструктора можно привести в порядок, переместив полеинициализатора для имени пользователя (userName) и введя следующий блокинициализатора:
{
for(int i = 0; i
square[i]= i * i;
}
}
Данный блокинициализаторов выглядит как блок статического инициализатора без статическогоключевого слова. Он запускается перед тем, как вызвать конструктор.
Можно разделитьконструкторы в примере (ConstructorExample), передвинув следующие строки отконструктора без аргумента:
this(«Anonymous»);
В следующей программепоявляется пустой конструктор, ConstructorExample2 (пример конструктора 2),чтобы продемонстрировать введение данного блока инициализации.
publicclass ConstructorExample2 {
privatefinal String userName;
privatestatic final int[] square = new int[10];
{
for(int i = 0; i
square[i]= i * i;
}
}
publicConstructorExample2() {
userName= «Anonymous»;
}
publicConstructorExample2(String userName) {
this.userName= userName;
}
publicvoid printSquare(int i) {
// Нет обработкиошибки, предположим,0
System.out.println(«Hello» + userName);
System.out.println(i+ " squared is " + square[i]);
}
publicstatic void main(String[] args) {
newConstructorExample2().printSquare(3);
newConstructorExample2(«Ed»).printSquare(5);
}
}
Однажды, попробовавработать с блоками инициализации, возможно найти для них много приложений. Можноубедиться, что статические и динамические инициализаторы очень удобные.
инициализацияjava файл программа
2. Чтение и записьфайлов
Подавляющее большинствопрограмм обменивается данными с внешним миром. Это, безусловно, делают любыесетевые приложения – они передают и получают информацию от других компьютеров испециальных устройств, подключенных к сети. Оказывается, можно точно таким жеобразом представлять обмен данными между устройствами внутри одной машины. Так,например, программа может считывать данные с клавиатуры и записывать их в файл,или же наоборот — считывать данные из файла и выводить их на экран. Такимобразом, устройства, откуда может производиться считывание информации, могутбыть самыми разнообразными – файл, клавиатура, входящее сетевое соединение ит.д. То же касается и устройств вывода – это может быть файл, экран монитора,принтер, исходящее сетевое соединение и т.п. В конечном счете, все данные вкомпьютерной системе в процессе обработки передаются от устройств ввода кустройствам вывода.
Обычно часть вычислительнойплатформы, которая отвечает за обмен данными, так и называется – системаввода/вывода. В Java она представлена пакетом java.io (input/output).Реализация системы ввода/вывода осложняется не только широким спектромисточников и получателей данных, но еще и различными форматами передачиинформации. Ею можно обмениваться в двоичном представлении, символьном илитекстовом, с применением некоторой кодировки (только для русского языка ихнасчитывается более 4 штук), или передавать числа в различных представлениях.Доступ к данным может потребоваться как последовательный (например, считываниеHTML-страницы), так и произвольный (сложная работа с несколькими частями одногофайла). Зачастую для повышения производительности применяется буферизация.
В Java для описанияработы по вводу/выводу используется специальное понятие поток данных (stream).Поток данных связан с некоторым источником, или приемником, данных, способнымполучать или предоставлять информацию. Соответственно, потоки делятся навходящие – читающие данные и выходящие – передающие (записывающие) данные.Введение концепции stream позволяет отделить основную логику программы,обменивающейся информацией с любыми устройствами одинаковым образом, отнизкоуровневых операций с такими устройствами ввода/вывода.
В Java потокиестественным образом представляются объектами. Описывающие их классы как раз исоставляют основную часть пакета java.io. Они довольно разнообразны и отвечаютза различную функциональность. Все классы разделены на две части – одни осуществляютввод данных, другие – вывод.
Существующиестандартные классы помогают решить большинство типичных задач. Минимальной«порцией» информации является, как известно, бит, принимающийзначение 0 или 1 (это понятие также удобно применять на самом низком уровне,где данные передаются электрическим сигналом; условно говоря, 1 представляетсяпрохождением импульса, 0 – его отсутствием). Традиционно используется болеекрупная единица измерения – байт, объединяющая 8 бит. Таким образом, значение,представленное одним байтом, находится в диапазоне от 0 до 28-1=255, или, еслииспользовать знак, – от -128 до +127. Примитивный тип byte в Java в точностисоответствует последнему – знаковому диапазону.
Базовые, наиболееуниверсальные, классы позволяют считывать и записывать информацию именно в виденабора байт. Чтобы их было удобно применять в различных задачах, java.ioсодержит также классы, преобразующие любые данные в набор байт.
Например, если нужносохранить результаты вычислений – набор значений типа double – в файл, то ихможно сначала превратить в набор байт, а затем эти байты записать в файл.Аналогичные действия совершаются и в ситуации, когда требуется сохранить объект(т.е. его состояние) – преобразование в набор байт и последующая их запись вфайл. Понятно, что при восстановлении данных в обоих рассмотренных случаяхпроделываются обратные действия – сначала считывается последовательность байт,а затем она преобразуется в нужный формат.
На рисунке 1представлены иерархии классов ввода/вывода. Как и говорилось, все типы поделенына две группы. Представляющие входные потоки классы наследуются от InputStream,а выходные – от OutputStream.
/>
Рис. 1. Иерархияклассов ввода/вывода.
Классы InputStream иOutputStream
InputStream – этобазовый класс для потоков ввода, т.е. чтения. Соответственно, он описываетбазовые методы для работы с байтовыми потоками данных. Эти методы необходимывсем классам, которые наследуются от InputStream.
Простейшая операцияпредставлена методом read() (без аргументов). Он является абстрактным и,соответственно, должен быть определен в классах-наследниках. Этот методпредназначен для считывания ровно одного байта из потока, однако возвращает приэтом значение типа int. В том случае, если считывание произошло успешно,возвращаемое значение лежит в диапазоне от 0 до 255 и представляет собойполученный байт (значение int содержит 4 байта и получается простым дополнениемнулями в двоичном представлении). Обратите внимание, что полученный такимобразом байт не обладает знаком и не находится в диапазоне от -128 до +127, какпримитивный тип byte в Java. Если достигнут конец потока, то есть в нем большенет информации для чтения, то возвращаемое значение равно -1.
Если же считать изпотока данные не удается из-за каких-то ошибок, или сбоев, будет брошеноисключение java.io.IOException. Этот класс наследуется от Exception, т.е. еговсегда необходимо обрабатывать явно. Дело в том, что каналы передачиинформации, будь то Internet или, например, жесткий диск, могут давать сбоинезависимо от того, насколько хорошо написана программа. А это означает, чтонужно быть готовым к ним, чтобы пользователь не потерял нужные данные.
Метод read() – этоабстрактный метод, но именно с соблюдением всех указанных условий он долженбыть реализован в классах-наследниках.
На практике обычноприходится считывать не один, а сразу несколько байт – то есть массив байт. Дляэтого используется метод read(), где в качестве параметров передается массивbyte[]. При выполнении этого метода в цикле производится вызов абстрактногометода read() (определенного без параметров) и результатами заполняетсяпереданный массив. Количество байт, считываемое таким образом, равно длинепереданного массива. Но при этом может так получиться, что данные в потокезакончатся еще до того, как будет заполнен весь массив. То есть возможнаситуация, когда в потоке данных (байт) содержится меньше, чем длина массива.Поэтому метод возвращает значение int, указывающее, сколько байт было реальносчитано. Понятно, что это значение может быть от 0 до величины длиныпереданного массива.
Если же мы изначальнохотим заполнить не весь массив, а только его часть, то для этих целейиспользуется метод read(), которому, кроме массива byte[], передаются еще дваint значения. Первое – это позиция в массиве, с которой следует начатьзаполнение, второе – количество байт, которое нужно считать. Такой подход,когда для получения данных передается массив и два int числа – offset(смещение) и length (длина), является довольно распространенным и частовстречается не только в пакете java.io.
При вызове методовread() возможно возникновение такой ситуации, когда запрашиваемые данные еще неготовы к считыванию. Например, если мы считываем данные, поступающие из сети, иони еще просто не пришли. В таком случае нельзя сказать, что данных больше нет,но и считать тоже нечего — выполнение останавливается на вызове метода read() иполучается «зависание».
Чтобы узнать, сколькобайт в потоке готово к считыванию, применяется метод available(). Этот методвозвращает значение типа int, которое показывает, сколько байт в потоке готовок считыванию. При этом не стоит путать количество байт, готовых к считыванию, стем количеством байт, которые вообще можно будет считать из этого потока. Методavailable() возвращает число – количество байт, именно на данный момент готовыхк считыванию.
Когда работа с входнымпотоком данных окончена, его следует закрыть. Для этого вызывается методclose(). Этим вызовом будут освобождены все системные ресурсы, связанные спотоком.
Точно так же, какInputStream – это базовый класс для потоков ввода, класс OutputStream – этобазовый класс для потоков вывода.
В классе OutputStreamаналогичным образом определяются три метода write() – один принимающий вкачестве параметра int, второй – byte[] и третий – byte[], плюс два int-числа.Все эти методы ничего не возвращают (void).
Метод write(int)является абстрактным и должен быть реализован в классах-наследниках. Этот методпринимает в качестве параметра int, но реально записывает в поток только byte –младшие 8 бит в двоичном представлении. Остальные 24 бита будутпроигнорированы. В случае возникновения ошибки этот метод бросаетjava.io.IOException, как, впрочем, и большинство методов, связанных свводом-выводом.
Для записи в потоксразу некоторого количества байт методу write() передается массив байт. Или,если мы хотим записать только часть массива, то передаем массив byte[] и дваint-числа – отступ и количество байт для записи. Понятно, что если указатьневерные параметры – например, отрицательный отступ, отрицательное количествобайт для записи, либо если сумма отступ плюс длина будет больше длины массива,– во всех этих случаях кидается исключение IndexOutOfBoundsException.
Реализация потока можетбыть такой, что данные записываются не сразу, а хранятся некоторое время впамяти. Например, мы хотим записать в файл какие-то данные, которые получаемпорциями по 10 байт, и так 200 раз подряд. В таком случае вместо 200 обращенийк файлу удобней будет скопить все эти данные в памяти, а потом одним заходомзаписать все 2000 байт. То есть класс выходного потока может использоватьнекоторый внутренний механизм для буферизации (временного хранения передотправкой) данных. Чтобы убедиться, что данные записаны в поток, а не хранятсяв буфере, вызывается метод flush(), определенный в OutputStream. В этом классеего реализация пустая, но если какой-либо из наследников использует буферизациюданных, то этот метод должен быть в нем переопределен.
Когда работа с потокомзакончена, его следует закрыть. Для этого вызывается метод close(). Этот методсначала освобождает буфер (вызовом метода flush), после чего поток закрываетсяи освобождаются все связанные с ним системные ресурсы. Закрытый поток не можетвыполнять операции вывода и не может быть открыт заново. В классе OutputStreamреализация метода close() не производит никаких действий.
Итак, классыInputStream и OutputStream определяют необходимые методы для работы с байтовымипотоками данных. Эти классы являются абстрактными. Их задача – определить общийинтерфейс для классов, которые получают данные из различных источников. Такимиисточниками могут быть, например, массив байт, файл, строка и т.д. Все они,или, по крайней мере, наиболее распространенные, будут рассмотрены далее.
Классы-реализациипотоков данных
КлассыByteArrayInputStream и ByteArrayOutputStream
Самый естественный ипростой источник, откуда можно считывать байты, – это, конечно, массив байт.Класс ByteArrayInputStream представляет поток, считывающий данные из массивабайт. Этот класс имеет конструктор, которому в качестве параметра передаетсямассив byte[]. Соответственно, при вызове методов read() возвращаемые данныебудут браться именно из этого массива. Например:
byte[]bytes = {1,-1,0};
ByteArrayInputStreamin =
newByteArrayInputStream(bytes);
intreadedInt = in.read(); // readedInt=1
System.out.println(«firstelement read is: „
+ readedInt);
readedInt= in.read();
//readedInt=255. Однако
// (byte)readedInt дастзначение -1
System.out.println(“secondelement read is: „
+ readedInt);
readedInt= in.read(); // readedInt=0
System.out.println(“thirdelement read is: „
+readedInt);
Если запустить такуюпрограмму, на экране отобразится следующее:
firstelement read is: 1
secondelement read is: 255
third element read is:0
При вызове методаread() данные считывались из массива bytes, переданного в конструкторByteArrayInputStream. Обратите внимание, в данном примере второе считанноезначение равно 255, а не -1, как можно было бы ожидать. Чтобы понять, почемуэто произошло, нужно вспомнить, что метод read считывает byte, но возвращаетзначение int, полученное добавлением необходимого числа нулей (в двоичномпредставлении). Байт, равный -1, в двоичном представлении имеет вид 11111111 и,соответственно, число типа int, получаемое приставкой 24-х нулей, равно 255 (вдесятичной системе). Однако если явно привести его к byte, получим исходноезначение.
Аналогично, для записибайт в массив применяется класс ByteArrayOutputStream. Этот класс используетвнутри себя объект byte[], куда записывает данные, передаваемые при вызовеметодов write(). Чтобы получить записанные в массив данные, вызывается методtoByteArray(). Пример:
ByteArrayOutputStreamout =
newByteArrayOutputStream();
out.write(10);
out.write(11);
byte[]bytes = out.toByteArray();
В этом примере врезультате массив bytes будет состоять из двух элементов: 10 и 11.
Использовать классыByteArrayInputStream и ByteArrayOutputStream может быть очень удобно, когданужно проверить, что именно записывается в выходной поток. Например, приотладке и тестировании сложных процессов записи и чтения из потоков. Эти классыхороши тем, что позволяют сразу просмотреть результат и не нужно создавать нифайл, ни сетевое соединение, ни что-либо еще.
Классы FileInputStreamи FileOutputStream
Класс FileInputStreamиспользуется для чтения данных из файла. Конструктор такого класса в качествепараметра принимает название файла, из которого будет производиться считывание.При указании строки имени файла нужно учитывать, что она будет напрямуюпередана операционной системе, поэтому формат имени файла и пути к нему можетразличаться на разных платформах. Если при вызове этого конструктора передатьстроку, указывающую на несуществующий файл или каталог, то будет брошеноjava.io.FileNotFoundException. Если же объект успешно создан, то при вызове егометодов read() возвращаемые значения будут считываться из указанного файла.
Для записи байт в файлиспользуется класс FileOutputStream. При создании объектов этого класса, тоесть при вызовах его конструкторов, кроме имени файла, также можно указать,будут ли данные дописываться в конец файла, либо файл будет перезаписан. Еслиуказанный файл не существует, то сразу после создания FileOutputStream он будетсоздан. При вызовах методов write() передаваемые значения будут записываться вэтот файл. По окончании работы необходимо вызвать метод close(), чтобы сообщитьсистеме, что работа по записи файла закончена. Пример:
byte[]bytesToWrite = {1, 2, 3};
byte[]bytesReaded = new byte[10];
StringfileName = “d:\\test.txt»;
try{
//Создатьвыходнойпоток
FileOutputStreamoutFile = new FileOutputStream(fileName);
System.out.println(«Файлоткрыт для записи»);
// Записать массив
outFile.write(bytesToWrite);
System.out.println(«Записано:» + bytesToWrite.length + " байт");
// По окончаниииспользования должен быть закрыт
outFile.close();
System.out.println(«Выходнойпоток закрыт»);
//Создатьвходнойпоток
FileInputStreaminFile = new FileInputStream(fileName);
System.out.println(«Файлоткрыт для чтения»);
// Узнать, сколько байтготово к считыванию
int bytesAvailable =inFile.available();
System.out.println(«Готовок считыванию: » + bytesAvailable + " байт");
//Считатьвмассив
intcount = inFile.read(bytesReaded,0,bytesAvailable);
System.out.println(«Считано:» + count + " байт");
for(int i=0;i
System.out.print(bytesReaded[i]+",");
System.out.println();
inFile.close();
System.out.println(«Входнойпоток закрыт»);
} catch(FileNotFoundException e) {
System.out.println(«Невозможнопроизвести запись в файл: » + fileName);
} catch (IOException e){
System.out.println(«Ошибкаввода/вывода: » + e.toString());
}
Результатом работыпрограммы будет:
Файл открыт для записи
Записано: 3 байт
Выходной поток закрыт
Файл открыт для чтения
Готово к считыванию: 3байт
Считано: 3 байт
1,2,3,
Входной поток закрыт
При работе сFileInputStream метод available() практически наверняка вернет длину файла, тоесть число байт, сколько вообще из него можно считать. Но не стоитзакладываться на это при написании программ, которые должны устойчиво работатьна различных платформах,– метод available() возвращает число байт, котороеможет быть на данный момент считано без блокирования. Тот факт, что, скореевсего, это число и будет длиной файла, является всего лишь частным случаемработы на некоторых платформах.
В приведенном примередля наглядности закрытие потоков производилось сразу же после окончания ихиспользования в основном блоке. Однако лучше закрывать потоки в finally блоке.
} finally {
try{inFile.close();}catch(IOExceptione){};
}
Такой подходгарантирует, что поток будет закрыт и будут освобождены все связанные с нимсистемные ресурсы.
Классы PipedInputStreamи PipedOutputStream
Классы PipedInputStreamи PipedOutputStream характеризуются тем, что их объекты всегда используются впаре – к одному объекту PipedInputStream привязывается (подключается) одинобъект PipedOutputStream. Они могут быть полезны, если в программе необходимоорганизовать обмен данными между модулями (например, между потокамивыполнения).
Эти классы применяютсяследующим образом: создается по объекту PipedInputStream и PipedOutputStream,после чего они могут быть соединены между собой. Один объект PipedOutputStreamможет быть соединен с ровно одним объектом PipedInputStream, и наоборот. Затемв объект PipedOutputStream записываются данные, после чего они могут бытьсчитаны именно в подключенном объекте PipedInputStream. Такое соединение можнообеспечить либо вызовом метода connect() с передачей соответствующего объектаPipedI/OStream (будем так кратно обозначать пару классов, в данном случаеPipedInputStream и PipedOutputStream), либо передать этот объект еще при вызовеконструктора. Использование связки PipedInputStream и PipedOutputStream показанов следующем примере:
try{
intcountRead = 0;
byte[]toRead = new byte[100];
PipedInputStreampipeIn = new PipedInputStream();
PipedOutputStreampipeOut = new PipedOutputStream(pipeIn);
// Считывать в массив,пока он полностью не будет заполнен
while(countRead
// Записать в потокнекоторое количество байт
for(inti=0; i
pipeOut.write((byte)(Math.random()*127));
}
// Считать из потокадоступные данные,
// добавить их к ужесчитанным.
intwillRead = pipeIn.available();
if(willRead+countRead>toRead.length)
//Нужно считать толькодо предела массива
willRead= toRead.length-countRead;
countRead+= pipeIn.read(toRead, countRead, willRead);
}
}catch (IOException e) {
System.out.println(«Impossible IOException occur: „);
e.printStackTrace();
}
Данный пример носитчисто демонстративный характер (в результате его работы массив toRead будетзаполнен случайными числами). Более явно выгода от использования PipedI/OStreamв основном проявляется при разработке многопоточного приложения. Если впрограмме запускается несколько потоков исполнения, организовать передачуданных между ними удобно с помощью этих классов. Для этого нужно создатьсвязанные объекты PipedI/OStream, после чего передать ссылки на них всоответствующие потоки. Поток выполнения, в котором производится чтение данных,может содержать подобный код:
//inStream — объект классаPipedInputStream
try{
while(true){
byte[]readedBytes = null;
synchronized(inStream){
intbytesAvailable = inStream.available();
readedBytes= new byte[bytesAvailable];
inStream.read(readedBytes);
}
// обработка полученныхданных из readedBytes
// …
} catch(IOException e){
/* IOException будетброшено, когда поток inStream, либо
связанный с нимPipedOutputStream, уже закрыт, и при этом
производится попыткасчитывания из inStream */
System.out.println(“работас потоком inStream завершена»);
}
Если с объектомinStream одновременно могут работать несколько потоков выполнения, то необходимоиспользовать блок synchronized (как и сделано в примере), который гарантирует,что в период между вызовами inStream.available() и inStream.read(…) ни в какомдругом потоке выполнения не будет производиться считывание из inStream. Поэтомувызов inStream.read(readedBytes) не приведет к блокировке и все данные, готовыек считыванию, будут считаны.
Класс StringBufferInputStream
Иногда бывает удобноработать с текстовой строкой String как с потоком байт. Для этого можновоспользоваться классом StringBufferInputStream. При создании объекта этогокласса необходимо передать конструктору объект String. Данные, возвращаемыеметодом read(), будут считываться именно из этой строки. При этом символы будутпреобразовываться в байты с потерей точности – старший байт отбрасывается(напомним, что символ char состоит из двух байт).
Класс SequenceInputStream
КлассSequenceInputStream объединяет поток данных из других двух и более входныхпотоков. Данные будут вычитываться последовательно – сначала все данные изпервого потока в списке, затем из второго, и так далее. Конец потокаSequenceInputStream будет достигнут только тогда, когда будет достигнут конецпотока, последнего в списке.
В этом классе имеетсядва конструктора – принимающий два потока и принимающий Enumeration (в котором,конечно, должны быть только экземпляры InputStream и его наследников). Когдавызывается метод read(), SequenceInputStream пытается считать байт из текущеговходного потока. Если в нем больше данных нет (считанное из него значение равно-1), у него вызывается метод close() и следующий входной поток становитсятекущим. Так продолжается до тех пор, пока не будут получены все данные изпоследнего потока. Если при считывании обнаруживается, что больше входныхпотоков нет, SequenceInputStream возвращает -1. Вызов метода close() уSequenceInputStream закрывает все содержащиеся в нем входные потоки.
Пример:
FileInputStreaminFile1 = null;
FileInputStreaminFile2 = null;
SequenceInputStreamsequenceStream = null;
FileOutputStreamoutFile = null;
try{
inFile1= new FileInputStream(«file1.txt»);
inFile2= new FileInputStream(«file2.txt»);
sequenceStream= new SequenceInputStream(inFile1, inFile2);
outFile= new FileOutputStream(«file3.txt»);
intreadedByte = sequenceStream.read();
while(readedByte!=-1){
outFile.write(readedByte);
readedByte= sequenceStream.read();
}
}catch (IOException e) {
System.out.println(«IOException:» + e.toString());
}finally {
try{sequenceStream.close();}catch(IOExceptione){};
try{outFile.close();}catch(IOExceptione){};
}
В результате выполненияэтого примера в файл file3.txt будет записано содержимое файлов file1.txt иfile2.txt – сначала полностью file1.txt, потом file2.txt. Закрытие потоковпроизводится в блоке finally. Поскольку при вызове метода close() можетвозникнуть IOException, необходим try-catch блок. Причем, каждый вызов методаclose() взят в отдельный try-catch блок — для того, чтобы возникшее исключениепри закрытии одного потока не помешало закрытию другого. При этом нет необходимостизакрывать потоки inFile1 и inFile2 – они будут автоматически закрыты прииспользовании в sequenceStream — либо когда в них закончатся данные, либо привызове у sequenceStream метода close().
ОбъектSequenceInputStream можно было создать и другим способом: сначала получитьобъект Enumeration, содержащий все потоки, и передать его в конструкторSequenceInputStream:
Vectorvector = new Vector();
vector.add(newStringBufferInputStream(«Begin file1\n»));
vector.add(newFileInputStream(«file1.txt»));
vector.add(newStringBufferInputStream("\nEnd of file1, begin file2\n"));
vector.add(newFileInputStream(«file2.txt»));
vector.add(newStringBufferInputStream("\nEnd of file2"));
Enumerationen = vector.elements();
sequenceStream= new SequenceInputStream(en);
Если заменить впредыдущем примере инициализацию sequenceStream на приведенную здесь, то в файлfile3.txt, кроме содержимого файлов file1.txt и file2.txt, будут записаны ещетри строки – одна в начале файла, одна между содержимым файлов file1.txt иfile2.txt и еще одна в конце file3.txt.
В итоге отметим, что Javaимеет широкий набор инструментов для обеспечения ввода-вывода данных в целом изаписи и чтения файлов в частности.
СПИСОК ЛИТЕРАТУРЫ
1. АрнольдК., Гослинг Дж. «Язык программирования Java»
2. ХорстманнК.С., Корнелл Г. « Java 2. Том 1. Основы», 7-е изд.