Программирование. Программирование на Java Конспект лекций (ИТМО 2010). Справочник по языку Java и может использоваться как конспект лекционного курса Системы программирования Интернетприложений
Скачать 0.85 Mb.
|
66 Работа с потоками ввода-вывода Поток данных (stream) представляет из себя абстрактный объект, предназначенный для получения или передачи данных единым способом, независимо от связанного с потоком источника или приемника данных. Иерархия потоков в Javа Потоки реализуются с помощью классов, входящях в пакет java.io . Потоки делятся на две больших группы — потоки ввода, и потоки вывода. Потоки ввода связаны с источниками данных, потоки вывода — с приемниками данных. Кроме того, потоки делятся на байтовые и символьные. Единицей обмена для байтовых потоков является байт, для символьных — символ Unicode. Потоки ввода Потоки вывода байтовые InputStream OutputStream символьные Reader Writer Кроме этих основных потоков, в пакет входят специализированные потоки, предназначенные для работы с различными источниками или приемниками данных, а также преобразующие потоки, предназначенные для преобразования информации, поступающей на вход потока, и выдачи ее на выход в преобразованном виде. Класс InputStream Представляет абстрактный входной поток байтов и является предком для всех входных байтовых потоков. Конструктор: InputStream() Создает входной байтовый поток Методы: abstract int read() throws IOException Читает очередной байт данных из входного потока. Значение должно быть от 0 до 255. При достижении конца потока возвращается -1. При ошибке ввода-вывода генерируется исключение. Подклассы должны обеспечить реализацию 67 данного метода. int read(byte[] buf) Читает данные в буфер и возвращает количество прочитанных байтов. Int read(byte[] buf, int offset, int len) Читает не более len байтов в буфер, заполняя его со смещением offset , и возвращает количество прочитанных байтов void close() Закрывает поток. int available() Возвращает количество доступных на данный момент байтов для чтения из потока. long skip(long n) Пропускает указанное количество байтов из потока. boolean markSupported() Проверка на возможность повторного чтения из потока. void mark(int limit) Устанавливает метку для последующего повторного чтения. limit – размер буфера для операции повторного чтения. void reset() Возвращает указатель потока на предварительно установленную метку. Дальнейшие вызовы метода read() будут снова возвращать данные, начиная с заданной метки. Класс OutputSrteam Представляет абстрактный выходной поток байтов и является предком для всех выходных байтовых потоков. Конструктор: OutputStream() Создает выходной байтовый поток. Методы: abstract void write(int n) throws IOException Записывает очередной байт данных в выходной поток. Значащими являются 8 младших битов, старшие - игнорируются. При ошибке ввода-вывода генерируется исключение. Подклассы должны обеспечить реализацию данного метода. 68 void write(byte[] buf) Записывает в поток данные из буфера. void write(byte[] buf, int offset, int len) Записывает в поток len байтов из буфера, начиная со смещения offset void close() Закрывает поток. void flush() Заставляет освободить возможный буфер потока, отправляя на запись все записанные в него данные. Класс Reader Представляет абстрактный входной поток символов и является предком для всех входных символьных потоков. Конструктор: Reader() создает входной символьный поток Методы: abstract int read() throws IOException Читает очередной символ Unicode из входного потока. При достижении конца потока возвращается -1. При ошибке ввода- вывода генерируется исключение. Подклассы должны обеспечить реализацию данного метода. int read(char[] buf) Читает данные в буфер и возвра-щает количество прочитанных сим-волов. Int read(char[] buf, int offset, int len) Читает не более len символов в буфер, заполняя его со смещением offset , и возвращает количество прочитанных символов void close() закрывает поток int available() возвращает количество доступных на данный момент символов для чтения из потока long skip(long n) пропускает указанное количество символов из потока 69 boolean markSupported() проверка на возможность повторного чтения из потока void mark(int limit) устанавливает метку для последую-щего повторного чтения. limit – размер буфера для операции повторного чтения void reset() возвращает указатель потока на предварительно установленную метку. Дальнейшие вызовы метода read() будут снова возвращать данные, начиная с заданной метки. Класс Writer Представляет абстрактный выходной поток символов и является предком для всех выходных символьных потоков. Конструктор: Writer() создает выходной символьный поток Методы: abstract void write(int n) throws IOException Записывает очередной символ Unicode в выходной поток. Знача-щими являются 16 младших битов, старшие - игнорируются. При ошибке ввода-вывода генерируется исклю-чение. Подклассы должны обеспе- чить реализацию данного метода. void write(char[] buf) Записывает в поток данные из буфера. void write(char[] buf, int offset, int len) Записывает в поток len символов из буфера, начиная со смещения offset void close() закрывает поток void flush() заставляет освободить возможный буфер потока, отправляя на запись все записанные в него данные. Специализированные потоки В пакет java.io входят потоки для работы со следующими основными типами источников и приемников данных: 70 тип данных байтовые символьные входной выходной входной выходной файл FileInput- Stream FileOutput- Stream FileReader FireWriter массив ByteArray- InputStream ByteArray- OutputStream CharArray- Reader CharArray- Writer строка - - StringReader String- Writer конвейер PipedInput- Stream PipedOutput- Stream PipedReader Piped- Writer Конструкторы этих потоков в качестве аргумента принимают ссылку на источник или приемник данных — файл, массив, строку. Методы для чтения и записи данных — read() для входных потоков, write() для выходных потоков. Конвейер имеет особенность, что источником данных для входного конвейера является выходной конвейер, и наоборот. Обычно конвейеры используются для обмена данными между двумя потоками выполнения ( Thread ). Пример чтения данных из файла: FileReader f = new FileReader(“myfile.txt”); char[] buffer = new char[512]; f.read(buffer); f.close(); Преобразующие потоки Этот тип потоков выполняет некие преобразования над данными других потоков. Конструкторы таких классов в качестве аргумента принимают поток данных. Классы BufferedInputStream , BufferedOutputStream , BufferedReader и BufferedWriter предназначены для буферизации ввода-вывода. Они позволяют читать и записывать данные большими блоками. При этом обмен данными со стороны приложения ведется с буфером, а по мере необходимости в буфер из источника данных подгружается новая порция данных, либо из буфера данные переписываются в приемник данных. Класс BufferedReader имеет дополнительный метод readLine() для чтения строки символов, ограниченной разделителем строк. Класс BufferedWriter имеет дополнительный метод newLine() для вывода разделителя строк. Классы InputStreamReader и OutputStreamWriter предназначены для преобразования байтовых потоков в символьные и наоборот. Кодировка задается в конструкторе класса. Если она опущена, то используется системная 71 кодировка, установленная по умолчанию). В конструктор класса InputStreamReader передается как аргумент объект класса InputStream , а в конструктор класса OutputStreamWriter – объект класса OutputStream Методы read() и write() этих классов аналогичны методам классов Reader и Writer Пример использования: Вариант 1: FileInputStream f = new FileInputStream(“myfile.txt”); InputStreamReader isr = new InputStreamReader(f); BufferedReader br = new BufferedReader(isr); br.readLine(); Вариант 2: BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream(“myfile.txt”))); br.readLine(); f – поток байтов из файла myfile.txt; isr – поток символов, преобразованный из байтового с учетом системной кодировки; br – поток символов с поддержкой буферизации. Классы DataInputStream и DataOutputStream предназначены для записи и чтения примитивных типов данных и содержат методы readBoolean() , readInt() , readDouble() , writeFloat() , writeByte() и другие подобные методы. Для успешного чтения таких данных из потока DataInputStream они должны быть предварительно записаны с помощью соответствующих методов DataOutputStream в том же порядке. Классы PrintStream и PrintWriter предназначены для форматированного вывода в поток вывода. В них определено множество методов print() и println() с различными аргументами, которые позволяют напечатать в поток аргумент, представленный в текстовой форме (с исползьзованием системной кодировки). В качестве аргумента может использоваться любой примитивный тип данных, строка и любой объект. Методы println добавляют в конце разделитель строк. 72 Стандартные потоки ввода-вывода Класс java.lang.System содержит 3 поля, представляющих собой стандарные консольные потоки: поле класс поток по умолчанию System.in InputStream стандартный поток ввода клавиатура System.out PrintStream стандартный поток вывода окно терминала System.err PrintStream стандартный поток ошибок окно терминала Имеется возможность перенаправлять данные потоки с помощью методов System.setIn , System.setOut , System.setErr Пример чтения данных с клавиатуры и вывода в окно терминала: BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); String s = br.readLine(); System.out.println(“Введена строка : “ + s); System.out.println(“Длина строки : “ + s.length); 73 Сериализация объектов Сериализация объектов - запись объекта со всеми полями и ссылками на другие объекты в виде последовательности байтов в поток вывода с последующим воссозданием (десериализацией) копии этого объекта путем чтения последовательности байтов сохраненного объекта из потока ввода. Интерфейс java.io.Serializable Интерфейс-метка, указывающий на то, что реализующий его класс может быть сериализован. Поля класса, не требующие сериализации, должны иметь модификатор transient Класс java.io.ObjectOutputStream Предназначен для записи в поток вывода примитивных типов, подобно классу DataOutputStream и объектов (иерархически). Конструктор класса ObjectOutputStream : ObjectOutputStream(OutputStream o) Cоздает объект класса, связанный с выходным потоком o Методы класса ObjectOutputStream : void writeObject(Object obj) Иерархически записывает в поток заданный объект. void useProtocolVersion(int v) Задает версию протокола сериализа-ции ObjectStreamConstants.PROTO- COL_VERSION_1 (использовалась в JDK версии 1.1) или ObjectStream- Constants.PROTOCOL_VERSION_2 (ис- пользуется по умолчанию начиная с версии JDK 1.2). void defaultWriteObject() Вызывается из метода writeObject сериализуемого класса для сохра-нения нестатических и нетранзитивых полей этого класса writeBoolean, writeByte, Методы, аналогичные методам класса 74 writeShort, writeChar, writeInt, writeLong, writeFloat, writeDOuble, writeUTF DataOutputStream для записи в поток примитивных типов. Класс java.io.ObjectInputStream Предназначен для получения из потока ввода примитивных типов, подобно классу DataOutputStream и объектов (иерархически), которые были предварительно записаны с помощью класса ObjectOutputStream Конструктор класса ObjectInputStream : ObjectInputStream(InputStream i) Создает объект класса, связанный с входным потоком i Методы класса ObjectInputStream : Object readObject() Получает из потока заданный объект и восстанавливает его иерархически. void defaultReadObject() Вызывается из метода readObject сериализуемого клас-са для восстановления нестатических и нетранзитивых полей этого класса. readBoolean, readByte, readShort, readChar, readInt, readLong, readFloat, readDouble, readUTF Методы, аналогичные методам класса DataInputStream для чтения из потока примитивных типов. В случае, если стандартного поведения для сериализации объекта недостаточно, можно определить в сериализуемом классе методы private void writeObject(ObjectOutputStream oos) и private void readObject(ObjectInputStream ois) , и определить в них необходимые действия по сериализации. Пример сериализации и восстановления объекта: ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(“file.ser”)); oos.writeObject(new Date()); oos.close(); ObjectInputStream ois = new ObjectInputStream( new FileInputStream(“file.ser”)); Date d = (Date) ois.readObject(); ois.close(); 75 Интерфейс java.io.Externalizable Предназначен для реализации классами, которым требуется нестандартное поведение при сериализации. В интерфейсе описаны 2 метода — writeExternal(ObjectOutput o) и readExternal(ObjectInput I) , предназначенный для записи и чтения состояния объекта. По умолчанию никакихе поля объекта, реализующего интерфейс Externalizable , в поток не передаются. Контроль версий сериализуемого класса Очевидно, что при сериализации объекта необходимо сохранять некоторую информацию о классе. Эта информация описывается классом java.io.ObjectStreamClass , в нее входит имя класса и идентификатор версии. Последний параметр важен, так как класс более ранней версии может не суметь восстановить сериализованный объект более поздней версии. Идентификатор класса хранится в переменной типа long serialVersionUID . В том случае, если класс не определяет эту переменную, то класс ObjectOutputStream автоматически вычисляет уникальный идентификатор версии для него с помощью алгоритма Secure Hash Algorithm (SHA). При изменении какой-либо переменной класса или какого-нибудь метода не-private происходит изменение этого значения. Для вычисления первоначального значения serialVersionUID используется утилита serialver. Сериализация апплетов Одной из областей применения сериализации объектов является сериализация апплетов. В тег RMI – вызов удаленных методов RMI (Remote method invocation) – технология распределенного объектного взаимодействия, позволяющая объекту, расположен-ному на стороне клиента, вызывать методы объектов, расположенных на стороне сервера (удаленных объектов). Для программиста вызов удаленного метода осуществляется так же, как и локального. Структура RMI Общая структурная организация технологии RMI приведена на рис. 8. Рисунок 8. Структура RMI Определения Удаленный объект — объект, методы которого могут быть вызваны из другой виртуальной Java-машины, возможно расположенной на другой вычислительной системе. Удаленный интерфейс — интерфейс, который реализуют удаленные объекты. 77 Заглушки Каркас Клиент RMI Сервер RMI Уровень удаленных ссылок (виртуальное соединение) Транспортный уровень (сетевое соединение) Вызов удаленного метода — действие по вызову метода и удаленного интерфейса, реализованного в удаленном объекте. Вызов такого метода имеет такой же синтаксис, как и вызов локального. Сервер объектов — программа, предоставляющая удаленные методы для вызова. Клиент — программа, осуществляющая вызов удаленных методов. Каталог удаленных объектов (RMI Registry) — служебная программа, работающая на той же вычислительной системе, что и сервер объектов. Позволяет определять объекты, доступные для удаленных вызовов с данного сервера. Объект-заглушка (Stub) - посредник удаленного объекта со стороны клиента. Предназначен для обработки аргументов и вызова транспортного уровня. Алгоритм работы с RMI 1. Определение удаленных интерфейсов. 2. Создание сервера. 3. Создание клиента. 4. Запуск каталога удаленных объектов, сервера и клиента. Определение удаленных интерфейсов Требования к удаленному интерфейсу: • должен иметь модификатор public ; • должен наследоваться от java.rmi.Remote ; • каждый метод интерфейса должен объявлять, что он выбрасывает java.rmi.RemoteException ; • аргументы и значения методов должны иметь примитивный или сериализуемый тип, либо тип удаленного интерфейса. 78 package hello; import java.rmi.*; public interface Hello extends Remote { String sayHello() throws RemoteException; } Создание сервера 1. Объявление класса, реализующего удаленный интерфейс: package hello; public class Server implements Hello { public Server() { } public String sayHello() { return “Hello, World!”; } } 2. Создание и экспорт удаленного объекта. Метод exportObject класса java.rmi.server.UnicastRemoteObject экпортирует удаленный объект, позволяя ему принимать удаленные вызовы на анонимный порт. Метод возвращает объект-заглушку, которая передается клиентам. Можно задать определенный порт, указав его в качестве второго аргумента. До версии JDK 1.5 заглушки создавались с помощью инструмента rmic . Этот способ необходимо применять в случае необходимости обеспечить совместимость с предущими версиями. Server obj = new Server(); Hello stub = (Hello) UnicastRemoteObject.exportObject(obj,0); 3. Зарегистрировать удаленный объект в каталоге RMI registry: Registry reg = LocateRegistry.getRegistry(); reg.bind(“Hello”, stub); Создание клиентов 1. Получение ссылки на удаленный метод из каталога RMI registry: Registry reg = LocateRegistry.getRegistry(hostname); |