Java — Вывод значения окончания потока


Содержание

19.1. Java — Потоки ввода/вывода и работа с файлами и каталогами

Пакет java.io содержит почти каждый класс, который может потребоваться Вам для совершения ввода и вывода в Java. Все данные потоки представлены потоком ввода и адресом вывода. Поток в пакете java.io осуществляет поддержку различных данных, таких как примитивы, объекты, локализованные символы и т.д.

Содержание

Потоки

Потоки в Java определяются в качестве последовательности данных. Существует два типа потоков:

  • InPutStream – поток ввода используется для считывания данных с источника.
  • OutPutStream – поток вывода используется для записи данных по месту назначения.

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

Байтовый поток

Потоки байтов в Java используются для осуществления ввода и вывода 8-битных байтов. Не смотря на множество классов, связанных с потоками байтов, наиболее распространено использование следующих классов: FileInputStream и FileOutputStream. Ниже рассмотрен пример, иллюстрирующий использование данных двух классов для копирования из одного файла в другой.

Примечание по примеру: чтобы скопировать файл, необходимо в папке проекта создать файл file.txt с любым или пустым содержимым.

Пример

Теперь рассмотрим файл file.txt со следующим содержимым:

В качестве следующего шага необходимо скомпилировать java-программу и выполнить ее, что позволит создать файл copied_file.txt с тем же содержимым, что имеется в file.txt. Таким образом, разместим обозначенный код в файле FileCopy.java и выполним следующее действие:

Символьные потоки

Потоки байтов в Java позволяют произвести ввод и вывод 8-битных байтов, в то время как потоки символов используются для ввода и вывода 16-битного юникода. Не смотря на множество классов, связанных с потоками символов, наиболее распространено использование следующих классов: FileReader и FileWriter. Не смотря на тот факт, что внутренний FileReader использует FileInputStream, и FileWriter использует FileOutputStream, основное различие состоит в том, что FileReader производит считывание двух байтов в конкретный момент времени, в то время как FileWriter производит запись двух байтов за то же время.

Мы можем переформулировать представленный выше пример, в котором два данных класса используются для копирования файла ввода (с символами юникода) в файл вывода.

Примечание по примеру: чтобы скопировать файл, необходимо в папке проекта создать файл file.txt с любым или пустым содержимым.

Пример

Теперь рассмотрим файл file.txt со следующим содержимым:

В качестве следующего шага необходимо скомпилировать программу и выполнить ее, что позволит создать файл copied_file.txt с тем же содержимым, что имеется в file.txt. Таким образом, разместим обозначенный код в файле FileCopy.java и выполним следующее действие:

Стандартные потоки

Все языки программирования обеспечивают поддержку стандартного ввода/вывода, где программа пользователя может произвести ввод посредством клавиатуры и осуществить вывод на экран компьютера. Если вы знакомы с языками программирования C либо C++, вам должны быть известны три стандартных устройства STDIN, STDOUT и STDERR. Аналогичным образом, Java предоставляет следующие три стандартных потока:

  • Стандартный ввод – используется для перевода данных в программу пользователя, клавиатура обычно используется в качестве стандартного потока ввода, представленного в виде System.in.
  • Стандартный вывод – производится для вывода данных, полученных в программе пользователя, и обычно экран компьютера используется в качестве стандартного потока вывода, представленного в виде System.out.
  • Стандартная ошибка – используется для вывода данных об ошибке, полученной в программе пользователя, чаще всего экран компьютера служит в качестве стандартного потока сообщений об ошибках, представленного в виде System.err.

Ниже представлена простая программа, которая создает InputStreamReader для чтения стандартного потока ввода, до введения пользователем «q»:

Пример

Разместим представленный выше код в файле ReadConsole.java и попробуем скомпилировать и выполнить его согласно тому, как это представлено в следующей программе. Данная программа продолжает чтение и вывод одного и того же символа до нажатия ‘q’:

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

Как было указано выше, поток представляет собой последовательность данных. InputStream используется для считывания данных с источника, OutputStream служит для записи данных по месту назначения.

Ниже представлена иерархия классов для управления потоками Ввода и Вывода.

В данном уроке нам предстоит рассмотреть два важных потока: FileInputStream и FileOutputStream.

Поток FileInputStream – чтение из файла

Поток FileInputStream – это поток, который используется в Java для чтения данных из файла. Объекты могут быть созданы при использовании ключевого слова new, доступны несколько типов конструкторов.

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

Представленный ниже конструктор использует объектный файл с целью создания объекта входного потока для чтения файла. Сперва мы создаем объектный файл при использовании метода File() следующим образом:

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

Метод и описание
1 public void close() throws IOException<>
Данный метод в Java закрывает выходной файловый поток. Освобождает какие-либо системные ресурсы, связанные с файлом. Выдает IOException.
2 protected void finalize()throws IOException <>
Данный метод выполняет очистку соединения с файлом. Позволяет удостовериться в вызове закрытого метода данного выходного файлового потока при отсутствии каких-либо ссылок на данный поток. Выдает IOException.
3 public int read(int r)throws IOException<>
Данный метод осуществляет в Java считывание заданных байтов данных из InputStream. Возврат данных типа int. Возврат следующего байта данных, в конце файла будет произведен возврат к -1.
4 public int read(byte[] r) throws IOException<>
Данный метод производит считывание байтов r.length из входного потока в массив. Возврат общего числа считанных байтов. В конце файла будет произведен возврат к -1.
5 public int available() throws IOException<>
Выдает число байтов, которые могут быть считаны из входного файлового потока. Возврат данных типа int.

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

Поток FileOutputStream – создание и запись файла

Поток FileOutputStream – это поток, который используется в Java для создания файла и последующей записи в него. Поток создаст файл в случае его отсутствия перед его открытием для вывода.

Далее представлены два конструктора, которые могут быть задействованы при создании объекта FileOutputStream.

Представленный конструктор использует имя файла в качестве строки с целью создания объекта входного потока для записи файла в Java:

Представленный ниже конструктор использует объектный файл с целью создания объекта выходного потока для записи файла. Сперва мы создаем объектный файл при использовании метода File() следующим образом:

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

Метод и описание
1 public void close() throws IOException<>
Данный метод в Java закрывает выходной файловый поток. Освобождает какие-либо системные ресурсы, связанные с файлом. Выдает IOException.
2 protected void finalize()throws IOException <>
Данный метод выполняет очистку соединения с файлом. Позволяет удостовериться в вызове закрытого метода данного выходного файлового потока при отсутствии каких-либо ссылок на данный поток. Выдает IOException.
3 public void write(int w)throws IOException<>
Данный метод осуществляет запись заданного байта в выходной поток.
4 public void write(byte[] w)
Запись байтов w.length из указанного массива байтов в OutputStream.

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

Пример

В следующем примере представлены InputStream и OutputStream – потоки для чтения, создания и записи файла:

Представленный выше java-код создаст файл file.txt и пропишет заданные символы в формате char. То же самое будет выводиться на экран стандартного вывода.

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

Каталоги в Java

В Java каталог представлен Файлом, который может содержать список других файлов и каталогов. Используя объект File, вы можете создать каталог, прокрутить список файлов, представленных в каталоге. Для получения более детальных сведений, ознакомьтесь с перечнем всех методов, которые могут быть вызваны из объекта File, будучи связанными с каталогами.

Создание каталогов

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

  • Метод mkdir() позволяет создать папку в Java, возвращая значение true при успехе операции, и false в случае сбоя. Сбой свидетельствует о том, что путь указанный в объекте File уже существует, либо что каталог не может быть создан в связи с тем, что полный путь еще не существует.
  • Метод mkdirs() создает каталог и все вышестоящие каталоги.

В следующем примере представлено создание папки «/java/proglang/newdir»:

Пример

Скомпилируйте и выполните следующий код для создания каталога «/java/proglang/newdir».

Примечание ? Java автоматически формирует разделители пути в UNIX и Windows с учетом соглашений. При использовании косой черты (/) при работе с Java в системе Windows, производится корректное разрешение пути.

Список файлов в папке

Метод list(), представленный объектом File, может быть использован для предоставления перечня всех файлов и каталогов, имеющихся в заданной папке, в следующем виде:

Пример

Вследствие этого будет получен следующий результат, основанный на каталогах и файлах, доступных в вашем каталоге /NetBeans 8.2/Projects/ReadDirectory/ReadDirectory/:

Многопоточность Thread, Runnable

Многопоточное программирование позволяет разделить представление и обработку информации на несколько «легковесных» процессов (light-weight processes), имеющих общий доступ как к методам различных объектов приложения, так и к их полям. Многопоточность незаменима в тех случаях, когда графический интерфейс должен реагировать на действия пользователя при выполнении определенной обработки информации. Потоки могут взаимодействовать друг с другом через основной «родительский» поток, из которого они стартованы.

В качестве примера можно привести некоторый поток, отвечающий за представление информации в интерфейсе, который ожидает завершения работы другого потока, загружающего файл, и одновременно отображает некоторую анимацию или обновляет прогресс-бар. Кроме того этот поток может остановить загружающий файл поток при нажатии кнопки «Отмена».


Создатели Java предоставили две возможности создания потоков: реализация (implementing) интерфейса Runnable и расширение(extending) класса Thread. Расширение класса — это путь наследования методов и переменных класса родителя. В этом случае можно наследоваться только от одного родительского класса Thread. Данное ограничение внутри Java можно преодолеть реализацией интерфейса Runnable, который является наиболее распространённым способом создания потоков.

Преимущества потоков перед процессами

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

Главный поток

Каждое java приложение имеет хотя бы один выполняющийся поток. Поток, с которого начинается выполнение программы, называется главным. После создания процесса, как правило, JVM начинает выполнение главного потока с метода main(). Затем, по мере необходимости, могут быть запущены дополнительные потоки. Многопоточность — это два и более потоков, выполняющихся одновременно в одной программе. Компьютер с одноядерным процессором может выполнять только один поток, разделяя процессорное время между различными процессами и потоками.

Класс Thread

В классе Thread определены семь перегруженных конструкторов, большое количество методов, предназначенных для работы с потоками, и три константы (приоритеты выполнения потока).

Конструкторы класса Thread

  • target – экземпляр класса реализующего интерфейс Runnable;
  • name – имя создаваемого потока;
  • group – группа к которой относится поток.

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

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

Несмотря на то, что главный поток создаётся автоматически, им можно управлять. Для этого необходимо создать объект класса Thread вызовом метода currentThread().

Методы класса Thread

Наиболее часто используемые методы класса Thread для управления потоками :

  • long getId() — получение идентификатора потока;
  • String getName() — получение имени потока;
  • int getPriority() — получение приоритета потока;
  • State getState() — определение состояния потока;
  • void interrupt() — прерывание выполнения потока;
  • boolean isAlive() — проверка, выполняется ли поток;
  • boolean isDaemon() — проверка, является ли поток «daemon»;
  • void join() — ожидание завершения потока;
  • void join(millis) — ожидание millis милисекунд завершения потока;
  • void notify() — «пробуждение» отдельного потока, ожидающего «сигнала»;
  • void notifyAll() — «пробуждение» всех потоков, ожидающих «сигнала»;
  • void run() — запуск потока, если поток был создан с использованием интерфейса Runnable;
  • void setDaemon(bool) — определение «daemon» потока;
  • void setPriority(int) — определение приоритета потока;
  • void sleep(int) — приостановка потока на заданное время;
  • void start() — запуск потока.
  • void wait() — приостановка потока, пока другой поток не вызовет метод notify();
  • void wait(millis) — приостановка потока на millis милисекунд или пока другой поток не вызовет метод notify();

Жизненный цикл потока

При выполнении программы объект Thread может находиться в одном из четырех основных состояний: «новый», «работоспособный», «неработоспособный» и «пассивный». При создании потока он получает состояние «новый» (NEW) и не выполняется. Для перевода потока из состояния «новый» в «работоспособный» (RUNNABLE) следует выполнить метод start(), вызывающий метод run().

Поток может находиться в одном из состояний, соответствующих элементам статически вложенного перечисления Thread.State :

NEW — поток создан, но еще не запущен;
RUNNABLE — поток выполняется;
BLOCKED — поток блокирован;
WAITING — поток ждет окончания работы другого потока;
TIMED_WAITING — поток некоторое время ждет окончания другого потока;
TERMINATED — поток завершен.

Пример использования Thread

В примере ChickenEgg рассматривается параллельная работа двух потоков (главный поток и поток Egg), в которых идет спор, «что было раньше, яйцо или курица?». Каждый поток высказывает свое мнение после небольшой задержки, формируемой методом ChickenEgg.getTimeSleep(). Побеждает тот поток, который последним говорит свое слово.

При выполнении программы в консоль было выведено следующее сообщение.

Невозможно точно предсказать, какой поток закончит высказываться последним. При следующем запуске «победитель» может измениться. Это происходит вследствии так называемого «асинхронного выполнения кода». Асинхронность обеспечивает независимость выполнения потоков. Или, другими словами, параллельные потоки независимы друг от друга, за исключением случаев, когда бизнес-логика зависимости выполнения потоков определяется предусмотренными для этого средств языка.

Интерфейс Runnable

Интерфейс Runnable содержит только один метод run() :

Метод run() выполняется при запуске потока. После определения объекта Runnable он передается в один из конструкторов класса Thread.

Пример класса RunnableExample, реализующего интерфейс Runnable

При выполнении программы в консоль было выведено следующее сообщение.

Синхронизация потоков, synchronized

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

В примере определен общий ресурс в виде класса CommonObject, в котором имеется целочисленное поле counter. Данный ресурс используется внутренним классом, создающим поток CounterThread для увеличения в цикле значения counter на единицу. При старте потока полю counter присваивается значение 1. После завершения работы потока значение res.counter должно быть равно 4.

Две строчки кода класса CounterThread закомментированы. О них речь пойдет ниже.

В главном классе программы SynchronizedThread.main запускается пять потоков. То есть, каждый поток должен в цикле увеличить значение res.counter с единицы до четырех; и так пять раз. Но результат работы программы, отображаемый в консоли, будет иным :

То есть, с общим ресурсов res.counter работают все потоки одновременно, поочередно изменяя значение.

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

Блокировка на уровне объекта

Блокировать общий ресурс можно на уровне объекта, но нельзя использовать для этих целей примитивные типы. В примере следует удалить строчные комментарии в классе CounterThread, после чего общий ресурс будет блокироваться как только его захватит один из потоков; остальные потоки будут ждать в очереди освобождения ресурса. Результат работы программы при синхронизации доступа к общему ресурсу резко изменится :

Следующий код демонстрирует порядок использования оператора synchronized для блокирования доступа к объекту.

Блокировка на уровне метода и класса

Блокировать доступ к ресурсам можно на уровне метода и класса. Следующий код показывает, что если во время выполнения программы имеется несколько экземпляров класса DemoClass, то только один поток может выполнить метод demoMethod(), для других потоков доступ к методу будет заблокирован. Это необходимо когда требуется сделать определенные ресурсы потокобезопасными.

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

Некоторые важные замечания использования synchronized

  1. Синхронизация в Java гарантирует, что два потока не могут выполнить синхронизированный метод одновременно.
  2. Оператор synchronized можно использовать только с методами и блоками кода, которые могут быть как статическими, так и не статическими.
  3. Если один из потоков начинает выполнять синхронизированный метод или блок, то этот метод/блок блокируются. Когда поток выходит из синхронизированного метода или блока JVM снимает блокировку. Блокировка снимается, даже если поток покидает синхронизированный метод после завершения из-за каких-либо ошибок или исключений.
  4. Синхронизация в Java вызывает исключение NullPointerException, если объект, используемый в синхронизированном блоке, не определен, т.е. равен null.
  5. Синхронизированные методы в Java вносят дополнительные затраты на производительность приложения. Поэтому следует использовать синхронизацию, когда она абсолютно необходима.
  6. В соответствии со спецификацией языка нельзя использовать synchronized в конструкторе, т.к. приведет к ошибке компиляции.

Примечание : для синхронизации потоков можно использовать объекты синхронизации Synchroniser’s пакета java.util.concurrent.

Взаимная блокировка

С использованием блокировок необходимо быть очень внимательным, чтобы не создать «взаимоблокировку», которая хорошо известна разработчикам. Этот термин означает, что один из потоков ждет от другого освобождения заблокированного им ресурса, в то время как сам также заблокировал один из ресурсов, доступа к которому ждёт второй поток. В данном процессе могут участвовать два и более потоков.

Основные условия возникновения взаимоблокировок в многопотоковом приложении :

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

Взаимодействие между потоками в Java, wait и notify

При взаимодействии потоков часто возникает необходимость приостановки одних потоков и их последующего извещения о завершении определенных действий в других потоков. Так например, действия первого потока зависят от результата действий второго потока, и надо каким-то образом известить первый поток, что второй поток произвел/завершил определенную работу. Для подобных ситуаций используются методы :

  • wait() — освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод notify();
  • notify() — продолжает работу потока, у которого ранее был вызван метод wait();
  • notifyAll() — возобновляет работу всех потоков, у которых ранее был вызван метод wait().

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

Рассмотрим пример «Производитель-Склад-Потребитель» (Producer-Store-Consumer). Пока производитель не поставит на склад продукт, потребитель не может его забрать. Допустим производитель должен поставить 5 единиц определенного товара. Соответственно потребитель должен весь товар получить. Но, при этом, одновременно на складе может находиться не более 3 единиц товара. При реализации данного примера используем методы wait() и notify().

Листинг класса Store


Класс Store содержит два синхронизированных метода для получения товара get() и для добавления товара put(). При получении товара выполняется проверка счетчика counter. Если на складе товара нет, то есть counter

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

Для получения данных, введенных пользователем, а также для вывода сообщений нам необходим ряд классов, через которые мы сможем взаимодействовать с консолью. Частично их использование уже рассматривалось в предыдущих темах. Для взаимодействия с консолью нам необходим класс System . Этот класс располагается в пакете java.lang, который автоматически подключается в программу, поэтому нам не надо дополнительно импортировать данный пакет и класс.

Вывод на консоль

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

В метод println передается любое значение, как правило, строка, которое надо вывести на консоль. При необходимости можно и не переводить курсор на следующую строку. В этом случае можно использовать метод System.out.print() , который аналогичен println за тем исключением, что не осуществляет перевода на следующую строку.

Но с помощью метода System.out.print также можно осуществить перевод каретки на следующую строку. Для этого надо использовать escape-последовательность \n :

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

Форматирование

Но в Java есть также функция для форматированного вывода, унаследованная от языка С: System.out.printf() . С ее помощью мы можем переписать предыдущий пример следующим образом:

В данном случае символы %d обозначают спецификатор, вместо которого подставляет один из аргументов. Спецификаторов и соответствующих им аргументов может быть множество. В данном случае у нас только два аргумента, поэтому вместо первого %d подставляет значение переменной x, а вместо второго — значение переменной y. Сама буква d означает, что данный спецификатор будет использоваться для вывода целочисленных значений типа int .

Кроме спецификатора %d мы можем использовать еще ряд спецификаторов для других типов данных:

  • %x : для вывода шестнадцатеричных чисел
  • %f : для вывода чисел с плавающей точкой
  • %e : для вывода чисел в экспоненциальной форме, например, 1.3e+01
  • %c : для вывода одиночного символа
  • %s : для вывода строковых значений

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

Имя: Иван Возраст: 30 лет Рост: 1,70 метров

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

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

Так как класс Scanner находится в пакете java.util, то мы вначале его импортируем. Для создания самого объекта Scanner в его конструктор передается объект System.in . После этого мы можем получать вводимые значения. Например, чтобы получить введенное число, используется метод in.nextInt(); , который возвращает введенное с клавиатуры целочисленное значение.

В данном случае в цикле вводятся все элементы массива, а с помощью другого цикла все ранее введенные элементы массива выводятся в строчку.

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

  • next() : считывает введенную строку до первого пробела
  • nextLine() : считывает всю введенную строку
  • nextInt() : считывает введенное число int
  • nextDouble() : считывает введенное число double
  • hasNext() : проверяет, было ли введено слово
  • hasNextInt() : проверяет, было ли введено число int
  • hasNextDouble() : проверяет, было ли введено double
Цукерберг рекомендует:  Ждём Junior'ов создаем ленту с автоподгрузкой записей

Кроме того, класс Scanner имеет еще ряд методов nextByte/nextShort/nextFloat/nextBoolean, которые по аналогии с nextInt считывают данные определенного типа данных.

Создадим следующую программу для ввода информации о человеке:

Собеседование по Java — потоки ввода/вывода (вопросы и ответы)

Вопросы и ответы для собеседования Java по теме — потоки ввода/вывода.

К списку вопросов по всем темам

Вопросы

1. Какие существуют виды потоков ввода/вывода?
2. Назовите основные предки потоков ввода/вывода.
3. Что общего и чем отличаются следующие потоки: InputStream, OutputStream, Reader, Writer?
4. Что вы знаете о RandomAccessFile?
5. Какие есть режимы доступа к файлу?
6. В каких пакетах лежат классы-потоки?
7. Что вы знаете о классах-надстройках?
8. Какой класс-надстройка позволяет читать данные из входного байтового потока в формате примитивных типов данных?
9. Какой класс-надстройка позволяет ускорить чтение/запись за счет использования буфера?
10. Какие классы позволяют преобразовать байтовые потоки в символьные и обратно?
11. Какой класс предназначен для работы с элементами файловой системы (ЭФС)?
12. Какой символ является разделителем при указании пути к ЭФС?
13. Как выбрать все ЭФС определенного каталога по критерию (например, с определенным расширением)?
14. Что вы знаете об интерфейсе FilenameFilter?
15. Что такое сериализация?
16. Какие условия “благополучной” сериализации объекта?
17. Какие классы позволяют архивировать объекты?

Ответы

1. Какие существуют виды потоков ввода/вывода?

Разделяют два вида потоков ввода/вывода: байтовые и символьные.

Система ввода/вывода: http://developer.alexanderklimov.ru/android/java/io.php
Oracle Lesson: Basic I/O tutorial: https://docs.oracle.com/javase/tutorial/essential/io/

2. Назовите основные предки потоков ввода/вывода.

Байтовые: java.io.InputStream, java.io.OutputStream;

Символьные: java.io.Reader, java.io.Writer;

3. Что общего и чем отличаются следующие потоки: InputStream, OutputStream, Reader, Writer?

Базовый класс InputStream представляет классы, которые получают данные из различных источников:
— массив байтов
— строка (String)
— файл
— канал (pipe): данные помещаются с одного конца и извлекаются с другого
— последовательность различных потоков, которые можно объединить в одном потоке
— другие источники (например, подключение к интернету)

Класс OutputStream — это абстрактный класс, определяющий потоковый байтовый вывод. В этой категории находятся классы, определяющие, куда направляются ваши данные: в массив байтов (но не напрямую в String; предполагается что вы сможете создать их из массива байтов), в файл или канал.

Символьные потоки имеют два основных абстрактных класса Reader и Writer, управляющие потоками символов Unicode. Класс Reader — абстрактный класс, определяющий символьный потоковый ввод. Класс Writer — абстрактный класс, определяющий символьный потоковый вывод. В случае ошибок все методы класса передают исключение IOException.

4. Что вы знаете о RandomAccessFile?

Класс RandomAccessFile наследуется напрямую от Object и не наследуется от вышеприведенных базовых классов ввода\вывода. Предназначен для работы с файлами, поддерживая произвольный доступ к их содержимому.

Работа с классом RandomAccessFile напоминает использование совмещенных в одном классе потоков DataInputStream и DataOutputStream (они реализуют те же интерфейсы DataInput и DataOutput). Кроме того, метод seek() позволяет переместиться к определенной позиции и изменить хранящееся там значение.

При использовании RandomAccessFile необходимо знать структуру файла. Класс RandomAccessFile содержит методы для чтения и записи примитивов и строк UTF-8.

5. Какие есть режимы доступа к файлу?

RandomAccessFile может открываться в режиме чтения («r») или чтения/записи («rw»). Также есть режим «rws», когда файл открывается для операций чтения-записи и каждое изменение данных файла немедленно записывается на физическое устройство.

6. В каких пакетах лежат классы-потоки?

Классы потоков ввода\вывода лежат в java.io; С JDK 7 добавлен более современный способ работы с потоками — Java NIO. Классы лежат в java.nio. Для работы с архивами используются классы из пакета java.util.

7. Что вы знаете о классах-надстройках?

Классы-надстройки наделяют существующий поток дополнительными свойствами. Примеры классов: BufferedOutputStream , BufferedInputStream , BufferedWriter — буферизируют поток и повышают производительность.

8. Какой класс-надстройка позволяет читать данные из входного байтового потока в формате примитивных типов данных?

Для чтения байтовых данных (не строк) применяется класс DataInputStream. В этом случае необходимо использовать классы из группы InputStream.

Для преобразования строки в массив байтов, пригодный для помещения в поток ByteArrayInputStream, в классе String предусмотрен метод getBytes(). Полученный ByteArrayInputStream представляет собой поток InputStream, подходящий для передачи DataInputStream.

При побайтовом чтении символов из форматированного потока DataInputStream методом readByte() любое полученное значение будет считаться действительным, поэтому возвращаемое значение неприменимо для идентификации конца потока. Вместо этого можно использовать метод available(), который сообщает, сколько еще осталось символов.

Класс DataInputStream позволяет читать элементарные данные из потока через интерфейс DataInput, который определяет методы, преобразующие элементарные значения в форму последовательности байтов. Такие потоки облегчают сохранение в файле двоичных данных.

Конструктор: DataInputStream(InputStream stream)
Методы: readDouble(), readBoolean(), readInt()

9. Какой класс-надстройка позволяет ускорить чтение/запись за счет использования буфера?

Для этого используются классы, позволяющие буферизировать поток:
java.io.BufferedInputStream(InputStream in) || BufferedInputStream(InputStream in, int size),
java.io.BufferedOutputStream(OutputStream out) || BufferedOutputStream(OutputStream out, int size),
java.io.BufferedReader(Reader r) || BufferedReader(Reader in, int sz),
java.io.BufferedWriter(Writer out) || BufferedWriter(Writer out, int sz)

10. Какие классы позволяют преобразовать байтовые потоки в символьные и обратно?

OutputStreamWriter — мост между классом OutputStream и классом Writer. Символы, записанные в поток, преобразовываются в байты.

ГЛАВА 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).

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 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.

Методы класса PrintWriter по умолчанию не очищают буфер, а метод print () не очищает его в любом случае. Для очистки буфера используйте метод flush().

После этого можно выводить любой текст методами класса PrintWriter, которые просто дублируют методы класса Printstream, и писать, например,

pw.println(«Это русский текст»);

как показано в листинге 18.1 и на рис. 18.3.

Следует заметить, что конструктор класса PrintWriter, в котором задан байтовый поток, всегда неявно создает объект класса OutputstreamWriter с локальной кодировкой для преобразования байтового потока в символьный поток.


Ввод с консоли производится методами read о класса inputstream с помощью статического поля in класса system. С консоли идет поток байтов, полученных из scan-кодов клавиатуры. Эти байты должны быть преобразованы в символы Unicode такими же кодовыми таблицами, как и при выводе на консоль. Преобразование идет по той же схеме — для правильного ввода кириллицы удобнее всего определить экземпляр класса BufferedReader, используя в качестве «переходного кольца» объект класса inputstreamReader:

BufferedReader br = new BufferedReader(

new InputstreamReader(System.an, «Cp866»));

Класс BufferedReader переопределяет три метода read о своего суперкласса Reader. Кроме того, он содержит метод readLine ().

Метод readLine о возвращает строку типа string, содержащую символы входного потока, начиная с текущего, и заканчивая символом ‘\п’ и/или ‘\r’. Эти символы-разделители не входят в возвращаемую строку. Если во входном потоке нет символов, то возвращается null.

В листинге 18.1 приведена программа, иллюстрирующая перечисленные методы консольного ввода/вывода. На рис. 18.3 показан вывод этой программы.

Листинг 18.1. Консольный ввод/вывод

public static void main(String[] args)<

new BufferedReader(new InputstreamReader(System.in, «Cp866»));

PrintWriter pw = new PrintWriter(

new OutputstreamWriter(System.out, «Cp866»), true);

String s = «Это строка с русским текстом»;

System.out.println(«System.out puts: » + s);

pw.println(«PrintWriter puts: » + s) ;

while((с = br.read()) != -1)

Поясним рис. 18.3. Первая строка выводится потоком system.out. Как видите, кириллица выводится неправильно. Следующая строка предварительно преобразована в поток байтов, записанных в кодировке СР866.

Затем, после текста «Посимвольный ввод:» с консоли вводятся символы «Россия» и нажимается клавиша . Каждый вводимый символ отображается на экране — операционная система работает в режиме так называемого «эха». Фактический ввод с консоли начинается только после нажатия клавиши , потому что клавиатурный ввод буферизуется операционной системой. Символы сразу после ввода отображаются по одному на строке. Обратите внимание на две пустые строки после буквы я. Это выведены символы ‘\п’ и ‘\г’, которые попали во входной поток при нажатии клавиши . У них нет никакого графического начертания (glyph).

Потом нажата комбинация клавиш + . Она отображается на консоль как «^Z» и означает окончание клавиатурного ввода, завершая цикл ввода символов. Коды этих клавиш уже не попадают во входной поток.

Далее, после текста «Построчный ввод:» с клавиатуры набирается строка «Это строка» и, вслед за нажатием клавиши , заносится в строку s. Затем строка s выводится обратно на консоль.

Для окончания работы набираем q и нажимаем клавишу .

Рис. 18.3. Консольный ввод/вывод

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

Но очень много файлов содержат тексты, составленные из символов. Несмотря на то, что символы могут храниться в кодировке Unicode, эти тексты чаще всего записаны в байтовых кодировках. Поэтому и для текстовых файлов можно использовать байтовые потоки. В таком случае со стороны программы придется организовать преобразование байтов в символы и обратно.

Чтобы облегчить это преобразование, в пакет java.io введены классы FineReader и FileWriter. Они организуют преобразование потока: со стороны программы потоки символьные, со стороны файла — байтовые. Это происходит потому, что данные классы расширяют классы InputStreamReader и OutputstreamWriter, соответственно, значит, содержат «переходное кольцо» внутри себя.

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

В конструкторах всех четырех файловых потоков задается имя файла в виде строки типа string или ссылка на объект класса File. Конструкторы не только создают объект, но и отыскивают файл и открывают его. Например:

Fiieinputstream fis = new FilelnputStreamC’PrWr.Java»);

FileReader fr = new FileReader(«D:\\jdkl.3\\src\\PrWr.Java»);

При неудаче выбрасывается исключение класса FileNotFoundException, но конструктор класса FileWriter выбрасывает более общее исключение IOException.

После открытия выходного потока типа FileWriter или FileQutputStEeam содержимое файла, если он был не пуст, стирается. Для того чтобы можно было делать запись в конец файла, и в том и в другом классе предусмотрен конструктор с двумя аргументами. Если второй аргумент равен true, то происходит дозапись в конец файла, если false, то файл заполняется новой информацией. Например:

FileWriter fw = new FileWriter(«ch!8.txt», true);

FiieOutputstream fos = new FiieOutputstream(«D:\\samples\\newfile.txt»);

Содержимое файла, открытого на запись конструктором с одним аргументом, стирается.

Сразу после выполнения конструктора можно читать файл:

или записывать в него:

По окончании работы с файлом поток следует закрыть методом close ().

Преобразование потоков в классах FileReader и FileWriter выполняется по кодовым таблицам установленной на компьютере локали. Для правильного ввода кирилицы надо применять FileReader, a нe FileInputStream. Если файл содержит текст в кодировке, отличной от локальной кодировки, то придется вставлять «переходное кольцо» вручную, как это делалось для консоли, например:

InputStreamReader isr = new InputStreamReader(fis, «KOI8_R»));

Байтовый поток fis определен выше.

Получение свойств файла

В конструкторах классов файлового ввода/вывода, описанных в предыдущем разделе, указывалось имя файла в виде строки. При этом оставалось неизвестным, существует ли файл, разрешен ли к, нему доступ, какова длина файла.

Получить такие сведения можно от предварительно созданного экземпляра класса File, содержащего сведения о файле. В конструкторе этого класса

указывается путь к файлу или каталогу, записанный по правилам операционной системы. В UNIX имена каталогов разделяются наклонной чертой /, в MS Windows — обратной наклонной чертой \, в Apple Macintosh — двоеточием :. Этот символ содержится в системном свойстве file.separator (см. рис. 6.2). Путь к файлу предваряется префиксом. В UNIX это наклонная черта, в MS Windows — буква раздела диска, двоеточие и обратная наклонная черта. Если префикса нет, то путь считается относительным и к нему прибавляется путь к текущему каталогу, который хранится в системном свойстве user.dir.

Конструктор не проверяет, существует ли файл с таким именем, поэтому после создания объекта следует это проверить логическим методом exists ().

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

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

Для каталога можно получить его содержимое — список имен файлов и подкаталогов— методом list о, возвращающим массив строк stringf]. Можно получить такой же список в виде массива объектов класса File[] методом listFilest). Можно выбрать из списка только некоторые файлы, реализовав интерфейс FileNameFiiter и обратившись к методу

Если каталог с указанным в конструкторе путем не существует, его можно создать логическим методом mkdir(). Этот метод возвращает true, если каталог удалось создать. Логический метод mkdirso создает еще и все несуществующие каталоги, указанные в пути.

Пустой каталог удаляется методом delete ().

Для файла можно получить его длину в байтах методом length (), время последней модификации в секундах с 1 января 1970 г. методом lastModifiedo. Если файл не существует, эти методы возвращают нуль.

Логические методы canRead (), canwrite () показывают права доступа к файлу.

Файл можно переименовать логическим методом renameTo(Fiie newMame) или удалить логическим методом delete о. Эти методы возвращают true, если операция прошла удачно.

Если файл с указанным в конструкторе путем не существует, его можно создать логическим методом createNewFilet), возвращающим true, если файл не существовал, и его удалось создать, и false, если файл уже существовал.

createTempFile(String prefix, String suffix, File tmpDir)

createTempFile(String prefix, String suffix)

можно создать временный файл с именем prefix и расширением suffix в каталоге tmpDir или каталоге, указанном в системном свойстве java.io.tmpdir (см. рис. 6.2). Имя prefix должно содержать не менее трех символов. Если suffix = null, то файл получит суффикс .tmp.

Перечисленные методы возвращают ссылку типа File на созданный файл. Если обратиться к методу deieteOnExit (), то по завершении работы JVM временный файл будет уничтожен.

Несколько методов getxxxo возвращают имя файла, имя каталога и другие сведения о пути к файлу. Эти методы полезны в тех случаях, когда ссылка на объект класса File возвращается другими методами и нужны сведения о файле. Наконец, метод toURL () возвращает путь к файлу в форме URL.

В листинге 18.2 показан пример использования класса File, а на рис. 18.4 — начало вывода этой программы.

Листинг 18.2. Определение свойств файла и каталога

public static void main(String[] args) throws IOException<

Многопоточное программирование в Java 8. Часть первая. Параллельное выполнение кода с помощью потоков

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

Впервые Concurrency API был представлен вместе с выходом Java 5 и с тех пор постоянно развивался с каждой новой версией Java. Большую часть примеров можно реализовать на более старых версиях, однако в этой статье я собираюсь использовать лямбда-выражения. Если вы все еще не знакомы с нововведениями Java 8, рекомендую посмотреть мое руководство.

Потоки и задачи

Все современные операционные системы поддерживают параллельное выполнение кода с помощью процессов и потоков. Процесс — это экземпляр программы, который запускается независимо от остальных. Например, когда вы запускаете программу на Java, ОС создает новый процесс, который работает параллельно другим. Внутри процессов мы можем использовать потоки, тем самым выжав из процессора максимум возможностей.

Потоки (threads) в Java поддерживаются начиная с JDK 1.0. Прежде чем запустить поток, ему надо предоставить участок кода, который обычно называется «задачей» (task). Это делается через реализацию интерфейса Runnable , у которого есть только один метод без аргументов, возвращающий void — run() . Вот пример того, как это работает:

Поскольку интерфейс Runnable функциональный, мы можем использовать лямбда-выражения, которые появились в Java 8. В примере мы создаем задачу, которая выводит имя текущего потока на консоль, и запускаем ее сначала в главном потоке, а затем — в отдельном.

Результат выполнения этого кода может выглядеть так:


Из-за параллельного выполнения мы не можем сказать, будет наш поток запущен до или после вывода «Done!» на экран. Эта особенность делает параллельное программирование сложной задачей в больших приложениях.

21 ноября в 19:30, Санкт-Петербург, беcплатно

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

Когда вы запустите этот код, вы увидите секундную задержку между выводом первой и второй строки на экран. TimeUnit — полезный класс для работы с единицами времени, но то же самое можно сделать с помощью Thread.sleep(1000) .

Работать с потоками напрямую неудобно и чревато ошибками. Поэтому в 2004 году в Java 5 добавили Concurrency API. Он находится в пакете java.util.concurrent и содержит большое количество полезных классов и методов для многопоточного программирования. С тех пор Concurrency API непрерывно развивался и развивается.

Давайте теперь подробнее рассмотрим одну из самых важных частей Concurrency API — сервис исполнителей (executor services).

Исполнители

Concurrency API вводит понятие сервиса-исполнителя (ExecutorService) — высокоуровневую замену работе с потоками напрямую. Исполнители выполняют задачи асинхронно и обычно используют пул потоков, так что нам не надо создавать их вручную. Все потоки из пула будут использованы повторно после выполнения задачи, а значит, мы можем создать в приложении столько задач, сколько хотим, используя один исполнитель.

Вот как будет выглядеть наш первый пример с использованием исполнителя:

Класс Executors предоставляет удобные методы-фабрики для создания различных сервисов исполнителей. В данном случае мы использовали исполнитель с одним потоком.

Результат выглядит так же, как в прошлый раз. Но у этого кода есть важное отличие — он никогда не остановится. Работу исполнителей надо завершать явно. Для этого в интерфейсе ExecutorService есть два метода: shutdown() , который ждет завершения запущенных задач, и shutdownNow() , который останавливает исполнитель немедленно.

Вот как я предпочитаю останавливать исполнителей:

Исполнитель пытается завершить работу, ожидая завершения запущенных задач в течение определенного времени (5 секунд). По истечении этого времени он останавливается, прерывая все незавершенные задачи.

Callable и Future

Кроме Runnable , исполнители могут принимать другой вид задач, который называется Callable . Callable — это также функциональный интерфейс, но, в отличие от Runnable , он может возвращать значение.

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

Callable-задачи также могут быть переданы исполнителям. Но как тогда получить результат, который они возвращают? Поскольку метод submit() не ждет завершения задачи, исполнитель не может вернуть результат задачи напрямую. Вместо этого исполнитель возвращает специальный объект Future, у которого мы сможем запросить результат задачи.

После отправки задачи исполнителю мы сначала проверяем, завершено ли ее выполнение, с помощью метода isDone() . Поскольку задача имеет задержку в одну секунду, прежде чем вернуть число, я более чем уверен, что она еще не завершена.

Вызов метода get() блокирует поток и ждет завершения задачи, а затем возвращает результат ее выполнения. Теперь future.isDone() вернет true , и мы увидим на консоли следующее:

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

Вы, возможно, заметили, что на этот раз мы создаем сервис немного по-другому: с помощью метода newFixedThreadPool(1) , который вернет исполнителя с пулом в один поток. Это эквивалентно вызову метода newSingleThreadExecutor() , однако мы можем изменить количество потоков в пуле.

Таймауты

Любой вызов метода future.get() блокирует поток до тех пор, пока задача не будет завершена. В наихудшем случае выполнение задачи не завершится никогда, блокируя ваше приложение. Избежать этого можно, передав таймаут:

Выполнение этого кода вызовет TimeoutException :

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

InvokeAll

Исполнители могут принимать список задач на выполнение с помощью метода invokeAll() , который принимает коллекцию callable-задач и возвращает список из Future .

В этом примере мы использовали функциональные потоки Java 8 для обработки задач, возвращенных методом invokeAll . Мы прошлись по всем задачам и вывели их результат на консоль. Если вы не знакомы с потоками (streams) Java 8, смотрите мое руководство.

InvokeAny

Другой способ отдать на выполнение несколько задач — метод invokeAny() . Он работает немного по-другому: вместо возврата Future он блокирует поток до того, как завершится хоть одна задача, и возвращает ее результат.

Чтобы показать, как работает этот метод, создадим метод, эмулирующий поведение различных задач. Он будет возвращать Callable , который вернет указанную строку после необходимой задержки:

Используем этот метод, чтобы создать несколько задач с разными строками и задержками от одной до трех секунд. Отправка этих задач исполнителю через метод invokeAny() вернет результат задачи с наименьшей задержкой. В данном случае это «task2»:

В примере выше использован еще один вид исполнителей, который создается с помощью метода newWorkStealingPool() . Этот метод появился в Java 8 и ведет себя не так, как другие: вместо использования фиксированного количества потоков он создает ForkJoinPool с определенным параллелизмом (parallelism size), по умолчанию равным количеству ядер машины.

ForkJoinPool впервые появился в Java 7, и мы рассмотрим его подробнее в следующих частях нашего руководства. А теперь давайте посмотрим на исполнители с планировщиком (scheduled executors).

Исполнители с планировщиком

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

ScheduledExecutorService способен запускать задачи один или несколько раз с заданным интервалом.

Этот пример показывает, как заставить исполнитель выполнить задачу через три секунды:

Когда мы передаем задачу планировщику, он возвращает особый тип Future — ScheduledFuture , который предоставляет метод getDelay() для получения оставшегося до запуска времени.

У исполнителя с планировщиком есть два метода для установки задач: scheduleAtFixedRate() и scheduleWithFixedDelay() . Первый устанавливает задачи с определенным интервалом, например, в одну секунду:

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

Обратите внимание, что метод scheduleAtFixedRate() не берет в расчет время выполнения задачи. Так, если вы поставите задачу, которая выполняется две секунды, с интервалом в одну, пул потоков рано или поздно переполнится.

В этом случае необходимо использовать метод scheduleWithFixedDelay() . Он работает примерно так же, как и предыдущий, но указанный интервал будет отсчитываться от времени завершения предыдущей задачи.

В этом примере мы ставим задачу с задержкой в одну секунду между окончанием выполнения задачи и началом следующей. Начальной задержки нет, и каждая задача выполняется две секунды. Так, задачи будут запускаться на 0, 3, 6, 9 и т. д. секунде. Как видите, метод scheduleWithFixedDelay() весьма полезен, если мы не можем заранее сказать, сколько будет выполняться задача.

Это была первая часть серии статей про многопоточное программирование. Настоятельно рекомендую разобрать вышеприведенные примеры самостоятельно. Все они доступны на GitHub. Можете смело форкать репозиторий и добавлять его в избранное.

Надеюсь, вам понравилась статья. Если у вас возникли какие-либо вопросы, вы можете задать их в твиттере.

Java — Вывод значения окончания потока

Начиная разговор о потоках ввода-вывода, в первую очередь я бы хотел, чтобы вы увидели главную задачу — программа должна иметь возможность передать данные кому-то еще. Еще раз — современная программа не существует в вакууме, в сегодняшних условиях подавляющее большинство программ требует интеграции с другими. Интеграция всегда подразумевает передачу данных. Т.е. одна программа как-то передает данные в другую.
Нам, как программистам,. нужен некий механизм ПЕРЕДАЧИ данных.

Дальше уже идет специализация — каким образом это можно сделать. На сегодня основных вариантов три:

  1. Файловая система — одна программа записывает в файл и другая программа читает данные из файла
  2. Сетевое взаимодействие — две программы используют сеть для передачи данных друг другу
  3. Передача из одной области памяти в другую. Данный вариант достаточно часто используется в рамках одной программы, но это не обязательно

Конечно же есть внешние устройства, которые подключаются, например, по USB, COM-порту или как-то еще. И для них тоже требуется передача данных. Хотя нередко для таких устройств операционная система может предложить некое подобие работы с файловой системой.

Сосредоточимся на передаче данных. Что это с технической точки зрения ? Да очень просто — надо “переслать” некое количество байт. Т.е одна программа (отправитель), используя какой-то механизм, “отправляет” байты, а другая (потребитель), используя тот же механизм, “потребляет” эти байты. Причем логично, что байты идут друг за другом от отправителя к потребителю … в виде некоего ПОТОКА байтов.

Это конечно же исключительно мои догадки, но мне кажется, что разработчики Java думали как-то так, когда создавали библиотеку для передачи данных. Основой для них стало понятие ПОТОКА данных.

Причем, что на мой взгляд, важно отметить — ПОТОК данных предполагает, что среда переноса данных может быть разная. Это файловая система, по сути контроллер жесткого диска. Или это сетевая карта, в которую (или из которой) записываются (читаются) байты. Во всех случаях, по сути, вы должны записать/считать байт по определенному адресу. Дальше в дело вступает электроника — вы записали байт по адресу, контроллер той же сетевой карты отправит это байт в сеть, где контроллер сетевой карты на другом компьютере поместит этот байт по определенному адресу, который будет доступен для чтения.

Таким образом можно рассматривать механизм потоков, как инструмент. С помощью которого вы как-бы подключаетесь к источнику данных (в случае чтения) или приемнику данных (в случае записи) и после подключения вам просто надо либо читать оттуда, либо писать.туда.
Мне почему-то всегды было удобно видеть это как некоторую “трубу”, с одной стороны которой мой код, а с другой стороны все, что может принимать (или порождать) набор байтов. Я просто “заливаю” в эту трубу байты. А как они там на другом конце трубы попадают в приемник — не мое дело. Я просто использую “трубы” под каждый вид приемника — для файла, для сети.

Еще крайне интересным моментов является возможность “соединять” разные потоки. Чуть позже мы увидим практическое воплощение этой идеи.
Если создать образное представление, то вы можете использовать трубу, которая например окрашивает воду, которая проходит через нее, в другой цвет. Или фильтрует. Или насыщает какими-то добавками. И на конец этой трубы вы “прикрепляете” трубу в другими возможностями. Вода (или байты) проходя по этим трубам, трансформируется.
Если смогли представить, то будет здОрово, если не получается — ничего страшного. Надеюсь, что дальнейшие примеры позволят вам это увидеть.

Типы потоков

По направлению движения данных потоки можно разделить на две группы:

  1. Поток ввода (Input) — данные поступают из потока в нашу программу. Мы их читаем из этого потока
  2. Поток вывода (Output) — данные поступают в поток из нашей программы. Мы их пишем в этот поток

Вторым критерием разделения может служить ТИП передаваемых данных. Да-да, байты не всегда являются удобным вариантом передачи — есть еще текст. Символы. Я надеюсь, вы помните, что символ в Java занимает ДВА байта. Так вот передача двух байтов как одного целого числа имеет сложность — какой байт должен идти первым ? Есть такая неприятная ситуация — в разных операционных системах этот вопрос решается по-разному.- вы можете поискать информацию в Интернете на тему big endian little endian и узнать, как непросто бывает подружить Windows и Linux или просто Linux на разных компьютерах. В данной статье я не ставлю такой задачи — просто констатирую факт: передача символов длиной в два байта требует дополнительных усилий. Поэтому и появилось такое разделение:

В итоге мы получаем 4 типа потоков. Для каждого из этих типов Java предлагает отдельный базовый абстрактный класс. Почему абстрактный ? Потому, что у нас есть специализация — файлы, сеть, память. И расширяя базовый класс специальный класс решает свои специальные задачи. Но базовые функции для всех одинаковые. Что удобно — все специальные потоки по своей сути одно и тоже. Это дает гибкость и универсальность. Вот эти классы:

  1. InputStream — поток для чтения байтов (поток ввода)
  2. Reader — поток для чтения символов (поток ввода)
  3. OutputStream — поток для записи байтов (поток вывода)
  4. Writer — поток для записи символов (поток вывода)

Основной функцией для потоков ввода является метод read в нескольких модификациях, которые мы рассмотрим позже. Разница для InputStream и Reader состоит только в том, что первый читает байты (byte), а второй — символы (char).
Вполне логично вытекает название основного метода для классов OutputStream и Writer — метод write. Тоже в нескольких модификациях.

Основные действия с потоком

Схема работы с потоком в упрощенном виде выглядит так:


  1. Создается экземпляр потока
  2. Поток открывается (для чтения или записи)
  3. Производится чтение из потока/запись в поток
  4. Поток закрывается

Первые два пункта часто совмещены в рамках одного действия. По сути потоки можно представить как трубу, в которую “заливаются” байты или символы. Причем что еще интереснее, эти трубы можно “склеивать” друг с другом. Т.е один поток может передавать данные в другой, предварительно как-то их модифицируя.
Этот прием мы еще увидим, а пока давайте решим простую задачу — запишем строку в файл. В текстовый файл. Т.е. Нам потребуется поток для символов — Writer. Потом мы прочитаем этот файл — и для этого используем Reader.
Чуть выше я говорил, что Reader и Writer — абстрактные классы. Для работы с файлами нам потребуются уже конкретные и это будут FileReader и FileWriter.
Первым шагом мы запишем текст в файл. Порядок работы с потоком я в принципе описал, поэтому давайте конкретизируем наши действия. Создание и открытие файлового потока на запись делает в момент создания экземпляра объекта FileWriter, у которого конструктор принимает в качестве параметра строку с именем файла. Далее в цикле мы пишем в поток символы из строки и потом закрываем наш поток. Обратите внимание на конструкцию блока try. Мы уже встречали такое в разделе JDBC — групповые операции.
Повторю его идею — в момент начала блока try .. catch вы можете открыть ресурс (важно, чтобы он реализовывал интерфейс AutoCloseable). В таком случае по окончании блока ресурс будет автоматически закрыт и вам не надо вызывать метод close. Блок try .. catch мы должны использовать, т.к. Операции по открытию и записи в файл могут порождать исключения FileNotFoundException и IOException. Исключение FileNotFoundException является подклассом IOException и в принципе нам нет необходимости обрабатывать его отдельно. Так что мы сократили все до IOException.
Также советую внимательно прочитать комментарии в коде — они объясняют многие моменты.
Перейдем от слов к делу — смотрим код нашего примера

Часть 7. Ввод/Вывод Java.io

Серия контента:

Этот контент является частью # из серии # статей: Практические советы по подготовке к экзамену SCJP 6: Примеры задач

Этот контент является частью серии: Практические советы по подготовке к экзамену SCJP 6: Примеры задач

Следите за выходом новых статей этой серии.

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

Концепция ввода/вывода

Чтобы реализовать такую задачу, разработчики создали множество классов для системы ввода/вывода byte-ориентированных и char-ориентированных данных в формате Unicode. На самом деле, создано такое количество классов и методов вывода, что в них действительно можно запутаться. Задача темы разобраться и понимать, зачем в стандартной библиотеке вода/вывода такое количество классов. Для реализации ввода/вывода в стандартной библиотеке используют концепцию потока. Абстракцию потока представляют в виде источника данных, объекта способного принимать или производить данные порциями. Абстракцию потока создают для сокрытия механизма реализации, того как происходит внутренняя обработка данных, а также, как внутри устройства производится ввод/вывод данных. Все библиотечные классы разделяются на классы вывода и ввода. Среди этих классов есть иерархия, согласно которой все классы, созданные из классов Reader или InputStream наследуют метод read( ), предназначенный для чтения единичного байта или чтения массива байт. Аналогично методу чтения, в каждом классе, наследнике классов Writer или OutputStream, существует метод записи write( ), предназначенный для записи единичного байта или для записи массива байт. Первая особенность, эти методы практически не приспособлены к использованию, они необходимы для создания классов наследников, их полезных интерфейсов. В результате, библиотечные полезные классы помогут создавать пользовательский объект потока и предоставят множество методов, объектов для реализации функциональности. По сути, для реализации потока в Java создаются несколько объектов, создающие необходимый функционал, и этот факт запутывает будущих программистов.

Для удобства понимания, все классы, работающие на вывод, наследуются из класса OutputStream. Под понятием вывод подразумевается вывод, передача информации сетевому соединению, в файл, на флешку, на экран монитора.

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

Такая реализация позволила создавать работающие с любым устройством ввода вывода программы, код которых практически не изменяется от перемены источника информации. В этом заслуга применяемой абстракции (stream) поток. Код реализации класса содержится в нескольких пакетах стандартной библиотеки java.io, при этом код вывода данных инкапсулирован в класс OutputStream, а ввод инкапсулирован в класс InputStream. Такая реализация с помощью абстракции помогает писать программный код, работающий со всеми устройствами, будь-то буфер памяти, сеть или клавиатура.

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

Типы Stream

Работа OutputStream/InputStream заключается в представлении пользователю классов, которые осуществляют вывод данных из различных источников, ввод данных в разные источники. Все источники можно разделить на виды:

  1. Файл (File) – работа с данными, содержащимися на носителе информации.
  2. Массив байт.
  3. Объект String.
  4. Fifo, как и в физической трубе, элемент, помещенный с одной стороны выводиться с другой (первый зашел, первый вышел).
  5. Последовательность потоков, которые можно собрать в единый поток.
  6. Всевозможные источники на подобии Internet соединений.

File (Файл)

В стандартной библиотеке ввода/вывода объект File осуществляет обмен данными с дисковыми файлами. Так как Java разрабатывался для сайтов в интернете, применение объектов, работающих с файлами, в апплетах жестко ограничиваются, но файлы, по-прежнему являются основными ресурсами совместного использования данных, информации, для ее постоянного хранения.

В Java особенная система хранения каталогов. Они представляются в виде файла с дополнительным параметром, в виде свойств и списка файлов, их имен, содержащиеся в каталоге. Чтобы получить список файлов, достаточно вызвать метод list. Указывать путь к каталогу достаточно просто, как это делается в ОС ( в DOS, Windows ‘\’, Unix — ‘/’), с единственным отличием, черту ‘\’ в пути необходимо удваивать: \\data\\lib. Класс File не симметричен, так как в нем методов, определяющих, задающих стандартные свойства каждого объекта этого типа значительно меньше, чем методов, позволяющих узнать определенные свойства объектов. В примере приведены всевозможные методы для получения характеристик, свойств файла, при этом, аналогичных функций для изменения соответствующих характеристик нет.

Результат исполнения программы:

Кроме методов для получения свойств файла, есть сервисные методы, действие которых ограничено влиянием на обычные файлы, при этом не работают с каталогами. Метод для переименования файлов renameTo(File dest) не позволяет переносить файлы в другой каталог. Для уничтожения файла используется метод delete(File arg). Метод не позволяет удалять каталоги, даже пустые, только файлы.

Каталог

Такие объекты класса File отличаются от обычных объектов file тем, что содержат список других каталогов и файлов. Если результат вызова метода isDirectory() возвращает true, то объект File, ссылается на каталог, соответственно можно применить к объекту метод list() и получить его содержимое, список имен каталогов и файлов. Пример использования методов для каталогов.

FilenameFilter

В процессе использования выборок имен файлов и каталогов, содержащихся в папке, при их огромном количестве, может потребоваться ограничение списка имен, который возвращает метод list(). Иными словами нужен фильтр имен в соответствии с шаблоном. Для фильтрации имен в стандартную библиотеку пакета java.io, включен стандартный интерфейс FilenameFilter. Для реализации интерфейса, достаточно объекту определить метод accept(). Метод accept(), вызывается для каждого имени файла, содержащегося в папке. Результат работы метода, логическое «да», в случае, если имя соответствует или «ложь», если имя не нужно включать в итоговый список.

Немного сложно реализована работа с каталогами. В классе File метод mkdirs() применяется для создания каталога и всех родительских каталогов, фактически создает сразу ветку. Второй метод mkdir() применяется в тех случаях, когда необходимо создать подкаталог.

InputStream

Первичный абстрактный класс InputStream скрывает реализацию, а также определяет модель поведения входных потоков в Java, на основании этого класса. Любая ошибка, возникшая в методах этого класса, генерируют исключение IOException, что удобно для перехвата и обработки. Каждый источник данных имеет ассоциированный класс InputStream. Класс InputStream содержит следующие методы:

  • Метод read() — возвращает значение целого типа очередного символа, доступного во входном потоке;
  • Метод read(byte arr[]) — метод для массового считывания данных, который считывает максимум байтов (не более arr.length) из потока, входящих данных в аргумент массива arr и возвращает фактическое количество байтов, считанных из потока;
  • Метод read(byte arr[], int start, int len) считывает из входного потока максимум len байтов, начиная с элемента start в массив arr типа байт (если в потоке было меньше данных, то вернется меньшее количество байтов, а если больше, то вернется не более len байтов);
  • Метод skip(long n) служит для пропуска n байтов из входного потока и результатом вызова метода, будет являться n фактически пропущенных байтов;
  • Метод available () возвращает фактическое количество байтов, которые доступны для считывания на момент вызова метода;
  • Метод close() предназначен для закрытия источника ввода. После исполнения этого метода, поток закрывается и любые обращения к его данным, попытки чтения данных из потока приводят к генерированию IOException;
  • Метод mark(int readlimit) предназначен для задания метки в текущую позицию потока входящих данных. Передаваемый аргумент задает пограничное значение считываемых байт, после превышения, которых не позволительно использовать заданную метку. Иными словами, метка актуальна до тех пор, пока не будет прочитано из потока readlimit байтов;
  • Метод reset() предназначен для возврата указателя потока на точку где была установлена метка;
  • Метод marksupported() необходим для проверки свойств потока. Если поток поддерживает операцию mark/reset, то метод возвращает логическое true.

OutputStream

Первичный абстрактный класс OutputStream аналогичен классу InputStream, он скрывает реализацию, а также определяет модель поведения объектов входных потоков, созданных на основании этого класса. Внутри класса все методы типа void, и в случае возникновения ошибки, генерируют исключение IOException. Класс OutputStream содержит следующие методы:

  • Метод write(int b) предназначен для записи единичного байта в поток выходных данных. Для удобства аргумент метода задан типом int. Такой синтаксис позволяет вызывать метод write с данными, без приведения их к типу byte;
  • Метод write(byte b[]) предназначен для записи в выходной поток данных целочисленного массива байтов, переданных в качестве аргумента;
  • Метод write(byte b[], int off, int len) необходим для записи в поток данных, только определенной части массива-аргумента, длиной len байтов, начиная с элемента с порядковым номером off,т.е. С элемента b[off];
  • Метод flush() предназначен для очистки выходного буфера, с последующим завершением операции вывода данных;
  • Метод close()предназначен для закрытия выходного потока. После исполнения этого метода, поток закрывается. Любые обращения к его данным, попытки записи данных в поток, приводят к генерированию ошибки IOException.

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

Наиболее важный вид потоков, файловый, представлен несколькими классами. Класс FileInputStream предназначен для считывания данных из файлов.

Пример использования одного и того же файла несколькими объектами класса FileInputStream.

При исполнении кода, происходит создание объекта класса FileInputStream, с одновременным открытием файла в режиме чтения. В классе FileInputStream, который является наследником абстрактного класса InputStream, для реализации задачи, замещены шесть методов. Использование методов mark и reset к объекту класса FileInputStream приводят к генерации исключения IOException.

Пример практического применения существующих методов.

FileOutputStream

Для вывода данных в файл существует класс FileOutputStream. В классе FileOutputStream, также присутствуют два конструктора, аналогичных FileInputStream. Ограничений по созданию объектов данного класса нет и можно создавать объекты, даже если файл не существует. В Java при создании нового объекта типа FileOutputStream происходит создание самого файла, а затем его открытие на запись для вывода, сохранения данных.

Пример практического использования методов класса FileOutputStream. Данные считываются с клавиатуры из потока System.in, до тех пор, пока не будет заполнен 12байтовый буфер, затем данные распределяются по 3м файлам. В первый пишутся все четные байты, во второй все данные, в третий только середина потока, ее 50%.

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

ByteArraylnputStream

Универсальный класс ByteArrayInputStream предназначен для реализации входного потока. Этот класс для тех случаев, когда в качестве источника применяется массив типа byte. Как у предыдущих классов этот класс имеет два конструктора. Разница только в том, что первым обязательным параметром является байтовый массив.

StringBufferlnputStream

Особый класс StringBufferInputStream, практически, идентичен классу ByteArrayInputStream. Разница заключается в том, что внутренний буфер объекта типа StringBufferInputStream создан не как байтовый массив, а как String. Особенность класса в том, что в нем есть только один конструктор StringBufferInputStream( String s) и нет симметричного класса ввода StringBufferedOutputStream.

Фильтруемые потоки

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

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

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

BufferedInputStream

Повсеместно используемый класс BufferedInputStream, для буферизации ввода/вывода. Применение такого класса оптимизирует данные виды операций, добавляет производительности. Класс BufferedlnputStream наследник InputStream расширяет возможности прародителя, добавив буферизированный поток к созданному объекту. Класс имеет два конструктора. Первый BufferedInputStream(InputStream in) предназначен для создания потока с буфером в 32 байта. Второй конструктор позволяет создать буферизированный поток большего объема BufferedInputStream(InputStream in, int size). Параметром size задается размер буфера, но его размер, ограничивается конфигурацией компьютера, свободным размером ОЗУ и типа ОС.

BufferedOutputStream

Класс BufferedOutputStream служит для вывода данных, как и любой наследник класса-прародителя OutputStream, с единственным отличием в виде метода flush(), расширяющего возможности класса прародителя. Вызов метода flush() производит физический вывод, хранящейся информации на внешнее устройство, с последующей очисткой буфера. Класс имеет два конструктора. Первый конструктор, BufferedOutputStream (OutputStream out), предназначен для создания потока с 32х байтным буфером. Второй конструктор, BufferedOutputStream(OutputStream out, int size), предназначен для создания потока с буфером размером отличным от 32 байт.

PushbacklnputStream

Специфический класс PushbacklnputStream служит для прочтения символа из потока и возврата его обратно в поток. Специфичность в том, что это дочерний класс InputStream применяет буфер для осуществления задачи. Объект данного класса создается единственным конструктором PushbackInputStream(InputStream in) и может вернуть только единичный символ. Любая попытка загрузить во входной поток больше символов генерирует исключение. Класс наследует все классы интерфейса InputStream и расширяет возможности методом unread(int ch). Метод служит для загрузки во входной поток символа, заданного в качестве аргумента целого типа.

SequencelnputStream

Часто используемый при обработке потоков класс SequencelnputStream, предназначен для слияния входных потоков в один. Конструктор объекта такого класса вызывается с аргументами в виде двух объектов типа InputStream.

Также конструктор может быть с одним аргументом, в виде перечисления, в котором содержится коллекция объектов типа InputStream.

Принцип работы класса прост. Сначала считываются данные из первого потока. В момент окончания данных объект класса переключается на считывание данных со следующего потока.

PrintStream

В этом классе хранится самые востребованные методы, применяемые практически в каждом примере. В классе PrintStream содержится инструментарий форматирования. Механизм реализации исполнения каждого метода System.out.println сокрыт в этом классе. Класс имеет два конструктора. Первый, PrintStream(OutputStream out), отличается от второго, PrintStream(OutputStream out, boolean autoflush), отсутствием логического аргумента autoflush, задающего необходимость автоматической очистки содержимого буфера выходного потока. Среди методов этого класса самыми умелыми считаются методы print и println, так как могут «обрабатывать» любые типы данных, в том числе данные типа object. В случае вызова метода к аргументам не базового типа, вызывается метод toString () корневого класса Object. Затем выводят полученный результат.

Вывод

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

Существующие потоки Java требуют внимательного изучения и применения на практике методов, так чтобы в прикладных задачах грамотно использовать разные классы потоков ввода/вывода, реализацию сложных, комплексных операций. Для успешной сдачи на отлично блока вопросов, связанных с потоками ввода/вывода, важно иметь ясное представление о существующих классах, четкое понимание синтаксиса и функционала, существующей иерархии классов, начиная с абстрактных InputStream и OutputStream.


Ресурсы для скачивания

Похожие темы

  • Практические советы по подготовке к экзамену SCJP 6. Цель SCJP.
  • Практические советы по подготовке к экзамену SCJP 6: Примеры задач. Часть 1.
  • Практические советы по подготовке к экзамену SCJP 6: Примеры задач. Часть 2.
  • Практические советы по подготовке к экзамену SCJP 6: Примеры задач. Часть 3.
  • Практические советы по подготовке к экзамену SCJP 6: Примеры задач. Часть 4.
  • Практические советы по подготовке к экзамену SCJP 6: Примеры задач. Часть 5.
  • Практические советы по подготовке к экзамену SCJP 6: Примеры задач. Часть 6.
  • Практические советы по подготовке к экзамену SCJP 6: Примеры задач. Часть 7.
  • Практические советы по подготовке к экзамену SCJP 6: Примеры задач. Часть 8.
  • Практические советы по подготовке к экзамену SCJP 6: Примеры задач. Часть 9.

Комментарии

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

Пакет java.io

Система ввода/вывода. Потоки данных (stream)

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

Обычно часть вычислительной платформы, которая отвечает за обмен данными , так и называется – система ввода/вывода. В Java она представлена пакетом java.io ( input/output ). Реализация системы ввода/вывода осложняется не только широким спектром источников и получателей данных, но еще и различными форматами передачи информации. Ею можно обмениваться в двоичном представлении, символьном или текстовом, с применением некоторой кодировки (только для русского языка их насчитывается более 4 штук), или передавать числа в различных представлениях. Доступ к данным может потребоваться как последовательный (например, считывание HTML-страницы), так и произвольный (сложная работа с несколькими частями одного файла). Зачастую для повышения производительности применяется буферизация .

В Java для описания работы по вводу/выводу используется специальное понятие поток данных ( stream ). Поток данных связан с некоторым источником или приемником данных, способным получать или предоставлять информацию. Соответственно, потоки делятся на входящие – читающие данные и выходящие – передающие (записывающие) данные. Введение концепции stream позволяет отделить основную логику программы, обменивающейся информацией с любыми устройствами одинаковым образом, от низкоуровневых операций с такими устройствами ввода/вывода.

В Java потоки естественным образом представляются объектами. Описывающие их классы как раз и составляют основную часть пакета java.io . Они довольно разнообразны и отвечают за различную функциональность. Все классы разделены на две части – одни осуществляют ввод данных, другие – вывод .

Существующие стандартные классы помогают решить большинство типичных задач. Минимальной «порцией» информации является, как известно, бит , принимающий значение 0 или 1 (это понятие также удобно применять на самом низком уровне, где данные передаются электрическим сигналом; условно говоря, 1 представляется прохождением импульса, 0 – его отсутствием). Традиционно используется более крупная единица измерения – байт , объединяющая 8 бит . Таким образом, значение , представленное одним байтом, находится в диапазоне от 0 до 2 8 -1=255, или, если использовать знак, – от -128 до +127. Примитивный тип byte в Java в точности соответствует последнему – знаковому диапазону.

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

Например, если нужно сохранить результаты вычислений – набор значений типа double – в файл , то их можно сначала превратить в набор байт , а затем эти байты записать в файл . Аналогичные действия совершаются и в ситуации, когда требуется сохранить объект (т.е. его состояние) – преобразование в набор байт и последующая их запись в файл . Понятно, что при восстановлении данных в обоих рассмотренных случаях проделываются обратные действия – сначала считывается последовательность байт , а затем она преобразуется в нужный формат.

На рисунке 15.1 представлены иерархии классов ввода/вывода. Как и говорилось, все типы поделены на две группы. Представляющие входные потоки классы наследуются от InputStream , а выходные – от OutputStream .

Классы 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() возвращаемые данные будут браться именно из этого массива. Например:

Если запустить такую программу, на экране отобразится следующее:

При вызове метода read() данные считывались из массива bytes , переданного в конструктор ByteArrayInputStream . Обратите внимание, в данном примере второе считанное значение равно 255, а не -1, как можно было бы ожидать. Чтобы понять, почему это произошло, нужно вспомнить, что метод read считывает byte , но возвращает значение int , полученное добавлением необходимого числа нулей (в двоичном представлении). Байт, равный -1 , в двоичном представлении имеет вид 11111111 и, соответственно, число типа int , получаемое приставкой 24-х нулей, равно 255 (в десятичной системе). Однако если явно привести его к byte , получим исходное значение.

Аналогично, для записи байт в массив применяется класс ByteArrayOutputStream . Этот класс использует внутри себя объект byte[] , куда записывает данные, передаваемые при вызове методов write() . Чтобы получить записанные в массив данные, вызывается метод toByteArray() . Пример:

В этом примере в результате массив bytes будет состоять из двух элементов: 10 и 11 .

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

Классы FileInputStream и FileOutputStream

Класс FileInputStream используется для чтения данных из файла. Конструктор такого класса в качестве параметра принимает название файла, из которого будет производиться считывание. При указании строки имени файла нужно учитывать, что она будет напрямую передана операционной системе, поэтому формат имени файла и пути к нему может различаться на разных платформах. Если при вызове этого конструктора передать строку, указывающую на несуществующий файл или каталог, то будет брошено java.io.FileNotFoundException . Если же объект успешно создан, то при вызове его методов read() возвращаемые значения будут считываться из указанного файла.

Для записи байт в файл используется класс FileOutputStream . При создании объектов этого класса, то есть при вызовах его конструкторов, кроме имени файла, также можно указать, будут ли данные дописываться в конец файла, либо файл будет перезаписан. Если указанный файл не существует, то сразу после создания FileOutputStream он будет создан. При вызовах методов write() передаваемые значения будут записываться в этот файл. По окончании работы необходимо вызвать метод close() , чтобы сообщить системе, что работа по записи файла закончена. Пример:

Результатом работы программы будет:

При работе с FileInputStream метод available() практически наверняка вернет длину файла, то есть число байт, сколько вообще из него можно считать. Но не стоит закладываться на это при написании программ, которые должны устойчиво работать на различных платформах,– метод available() возвращает число байт, которое может быть на данный момент считано без блокирования. Тот факт, что, скорее всего, это число и будет длиной файла, является всего лишь частным случаем работы на некоторых платформах.

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

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

PipedInputStream и PipedOutputStream

Классы PipedInputStream и PipedOutputStream характеризуются тем, что их объекты всегда используются в паре – к одному объекту PipedInputStream привязывается (подключается) один объект PipedOutputStream . Они могут быть полезны, если в программе необходимо организовать обмен данными между модулями (например, между потоками выполнения).

Эти классы применяются следующим образом: создается по объекту PipedInputStream и PipedOutputStream , после чего они могут быть соединены между собой. Один объект PipedOutputStream может быть соединен с ровно одним объектом PipedInputStream , и наоборот. Затем в объект PipedOutputStream записываются данные, после чего они могут быть считаны именно в подключенном объекте PipedInputStream . Такое соединение можно обеспечить либо вызовом метода connect() с передачей соответствующего объекта PipedI/OStream (будем так кратко обозначать пару классов, в данном случае PipedInputStream и PipedOutputStream ), либо передать этот объект еще при вызове конструктора.

Использование связки PipedInputStream и PipedOutputStream показано в следующем примере:

Данный пример носит чисто демонстративный характер (в результате его работы массив toRead будет заполнен случайными числами). Более явно выгода от использования PipedI/OStream в основном проявляется при разработке многопоточного приложения. Если в программе запускается несколько потоков исполнения, организовать передачу данных между ними удобно с помощью этих классов. Для этого нужно создать связанные объекты PipedI/OStream , после чего передать ссылки на них в соответствующие потоки . Поток выполнения, в котором производится чтение данных, может содержать подобный код:

Если с объектом 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 закрывает все содержащиеся в нем входные потоки .

В результате выполнения этого примера в файл 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 :

Если заменить в предыдущем примере инициализацию sequenceStream на приведенную здесь, то в файл file3.txt , кроме содержимого файлов file1.txt и file2.txt , будут записаны еще три строки – одна в начале файла, одна между содержимым файлов file1.txt и file2.txt и еще одна в конце file3.txt .

Как вернуть значение из потока (Java)

Я сделал нить, как этот ниже:

но когда я пытаюсь использовать temp с помощью getTemp, я получаю 0

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

5 ответов

Или просто добавьте

ждать окончания потока, прежде чем получить результат.

Ваша проблема в том, что вы пытаетесь получить результат до того, как он будет рассчитан. Вы должны дождаться окончания потока, прежде чем получите результат. Этот ответ, возможно, не самый лучший, но самый простой. Поскольку другие люди уже использовали класс Executors, я не хотел повторять их ответы. Однако я хотел бы ознакомиться с Thread и его методами, прежде чем переходить к Executors, чтобы помочь вам лучше понять темы, так как из вашего поста вы можете быть новичком в этой области.

Спасибо l4mpi (на мета-сайте) за указание на отсутствие объяснения.

Используйте Callable вместо Runnable , он вернет Future, который вы можете использовать, чтобы получить значение, как только оно будет сделано.

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

Вы можете попробовать этот код. Используя Future, вы можете сохранить возвращаемое значение, когда конец потока:

Есть несколько способов «поделиться» переменными с потоками.

Проблема с вашим кодом в том, что вы передаете int который передается по значению . Это означает, что temp и this.temp не являются одной и той же переменной.

Использование Future как предлагают другие ответы, — это один из способов обмена переменными. Используя Future , вы можете убедиться, что поток завершен, прежде чем фактически извлекать результат выполнения этого потока, поэтому он может быть более актуальным для вас.

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

  • передать AtomicInteger и использовать метод set для установки значения.
  • используйте synchronized метод получения, который возвращает значение Threads.
Цукерберг рекомендует:  Html - Просьба оценить верстку
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих