-1Прикладное программирование в ТС Лекция 3-09 Лекция 3-09 Тема 2.5. Ввод-вывод в Java 2.5.1. Работа с файлами в Java 2.5.1.1. Класс File 2.5.1.2. Класс FileDescriptor 2.5.1.3. Выбор файлов в Swing 2.5.2. Организация ввода-вывода в Java 2.5.3. Байтовые потоки ввода-вывода 2.5.3.1. Иерархия байтовых потоков ввода-вывода 2.5.3.2. Классы InputStream и OutputStream 2.5.3.3. Классы FileInputStream и FileOutputStream 2.5.3.4. Классы ByteArrayInputStream и ByteArrayOutputStream 2.5.3.5. Класс SequenceInputStream 2.5.3.6. Классы FilterInputStream и FilterOutputStream 2.5.3.7. Классы BufferedInputStream и BufferedOutputStream 2.5.3.8. Классы DataInputStream и DataOutputStream 2.5.3.9. Класс PushbackInputStream 2.5.3.10. Классы PipedInputStream и PipedOutputStream 2.5.3.11. Класс PrintStream 2.5.4. Символьные потоки ввода-вывода 2.5.4.1. Иерархия символьных потоков ввода-вывода 2.5.4.2. Классы Reader и Writer 2.5.4.3. Классы InputStreamReader и OutputStreamWriter 2.5.4.4. Классы FileReader и FileWriter 2.5.4.5. Классы CharArrayReader и CharArrayWriter 2.5.4.6. Классы BufferedReader, LineInputReader и BufferedWriter 2.5.4.7. Классы FilterReader, PushbackReader и FilterWriter 2.5.4.8. Классы StringReader и StringWriter 2.5.4.9. Классы PipedReader и PipedWriter 2.5.4.10. Класс PrintWriter 2.5.5. Консольный ввод, переопределение стандартного ввода-вывода 2.5.6. Класс RandomAccessFile 2.5.7. Класс StreamTokenizer Тема 2.5. Ввод-вывод и структуры данных в Java 2.5.1. Работа с файлами в Java Информация на устройствах внешней памяти хранится в файлах – именованных областях на диске, дискете или другом машинном носителе. Структура (как правило, иерархическая) размещения файлов на носителях информации, а также виды и правила задания атрибутов файла (имени, типа, даты создания и/или модификации и т.п.) называется файловой системой. Файловые системы в разных операционных системах, как правило, отличаются друг от друга. Так, при выполнении Java-программы в среде Unix элементы пути должны отделяться прямой косой чертой "/". В компьютерах под управлением Windows элементы пути разделяются символами обратной косой черты "\". Для этой же цели на компьютерах Macintosh используется символ двоеточия ":". Кроме того, могут существовать различные ограничения на длину имен файлов и каталогов. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -2Прикладное программирование в ТС Лекция 3-09 2.5.1.1. Класс File Абстрагироваться от всех зависимых от платформы элементов файлового вводавывода можно с помощью класса File. После создания объекта класса File (при вызове его конструктора по-прежнему потребуется указать некоторую информацию, зависящую от используемой платформы) вся последующая работа с ним будет полностью независимой от платформы. Класс File позволяет получить от системы все данные, начиная с имени файла и заканчивая временем последней его модификации. Класс File можно использовать для создания новых каталогов, а также для удаления и переименования файлов. Для создания объекта File нужно вызвать один из трех конструкторов класса: File(String path) File(String path, String name) File(File dir, String name) Первый конструктор создает объект File с указанным полным именем файла (например, C:\CLASSES\MYAPP.JAVA). Второй конструктор создает этот объект, используя отдельно путь и имя файла, а третий создает объект, используя путь и имя файла, при этом путь определяется другим объектом File. В классе File определены следующие методы: метод public String getName() определяет имя файла; методы public String getPath() public String getAbsolutePath() public String getParent() возвращают соответственно относительный путь к файлу (из текущего каталога), полное имя файла (путь к каталогу из корневого каталога) и родительский каталог файла; методы public boolean exists() public boolean isFile() public boolean isDirectory() public boolean isAbsolute() проверяют, существует ли данный файл, является ли он файлом или каталогом, задано ли его имя как абсолютное имя (т.е. задано с указанием полного пути к данному файлу или каталогу); методы public boolean canRead() public boolean canWrite() проверяют можно ли читать данный файл и можно ли записывать в него данные; методы public long length() public long lastModified() возвращают характеристики файла: длину и время последней модификации; методы public boolean mkdir() public boolean mkdirs() public boolean renameTo(File newName) public boolean delete() выполняют операции над файлами и каталогами: создание каталога, создание дерева каталогов, переименование файла или каталога, удаление файла или каталога; методы Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -3Прикладное программирование в ТС Лекция 3-09 public String[] list() public File[] listFiles() создают массив имен файлов и подкаталогов в каталоге соответственно в виде строк или в виде объектов класса File. Часто требуется ограничить количество файлов, возвращаемых методами list() или listFiles(), чтобы включать в список только те из них, имена которых соответствуют некоторому образцу или фильтру. Для этого этой цели можно использовать перегруженную форму методов list() и listFiles(): public String[] list(FilenameFilter filter) public File[] listFiles(FilenameFilter filter) В этих формах в качестве параметра задается объект класса, который реализует интерфейс типа FilenameFilter. Интерфейс FilenameFilter определяет единственный метод accept(), который имеет следующий вид: public abstract boolean accept(File dir, String name) Этот метод возвращает true для файлов каталога dir, которые должны быть включены в отфильтрованный список (с именами name), и возвращает false для тех файлов, которые должны быть исключены из списка (имена которых не совпадают с name). При использовании метода public File[] listFiles(FileFilter filter) файлы фильтруется не просто по именам, а по их полным именам (с путями в иерархии каталогов). При этом возвращаются файлы с теми именами пути, которые удовлетворяют файловому фильтру filter. Интерфейс FileFilter определяет только один метод, accept(), который вызывается однажды для каждого файла в списке. Его общая форма: boolean accept(File path) Метод возвращает true для файлов, которые должны быть включены в список (т.е. тех файлов, которые указаны в path), и false – для тех файлов, которые должны быть исключены (не указаны в path). Переменная public final static char separatorChar класса File возвращает символ разделения имен каталогов и файлов ("\" в Windows и "/" – в Unix), а переменная public final static char pathSeparatorChar содержит символ-разделитель путей в списке (";" в Windows и ":" – в Unix). 2.5.1.2. Класс FileDescriptor Реализация класса FileDescriptor служит прозрачным обработчиком для зависящей от компьютерной платформы структуры представления открытого файла, открытого порта, а также другого источника или получателя байт. Главным практическим использованием класса FileDescriptor является создание содержащих их объектов FileInputStream или FileOutputStream, которые рассматриваются ниже. Свойства этого класса public static FileDescriptor in public static FileDescriptor out public static err описывает обработчики соответственно стандартного вводного потока, стандартного выводного потока и стандартного потока ошибок. Обычно эти дескрипторы файлов Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -4Прикладное программирование в ТС Лекция 3-09 используются не прямо, а через стандартные вводные потоки System.in, System.out или System.err. Метод класса FileDescriptor public void sync() throws SyncFailedException вызывает принудительную синхронизацию системных буферов с вводными или выводными устройствами, а метод public boolean valid() проверяет, является ли данный объект дескриптора файла действительным. 2.5.1.3. Выбор файлов в Swing Класс JFileChooser в Swing, как и FileDialog в AWT, обеспечивает диалоговое окно для выбора каталогов и файлов, однако предоставляет значительно больше методов по выбору и управлению файлами. Основные конструкторы этого класса: JFileChooser() JFileChooser(File currentDirectory) JFileChooser(String currentDirectoryPath) позволяют открыть окно выбора файлов как для всех файлов, так и для конкретного каталога. Методы класса JFileChooser позволяют: получить или установить выбранные файлы (методы public File getSelectedFile(), public File[] getSelectedFiles() и public void setSelectedFile(File file), public void setSelectedFiles(File[] selectedFiles)); получить или установить выбранные каталоги (методы public File getCurrentDirectory() и public void setCurrentDirectory(File dir)); получить характеристики файла (метод public String getDescription(File f)); устанавливать и получать фильтры для имен выводимых файлов (методы public void setFileFilter(FileFilter filter) и public FileFilter getFileFilter()), а также некоторые другие операции. Для отслеживания событий, связанных с кнопками в диалоговом окне выбора файлов, необходимо реализовать интерфейс и добавить или удалить блок прослушивания для кнопок диалогового окна выбора файлов с помощью методов public void addActionListener(ActionListener l) и public void removeActionListener(ActionListener l) класса JFileChooser. 2.5.2. Организация ввода-вывода в Java Все данные в компьютерной системе проходят от устройств ввода через компьютер к устройствам вывода. Аналогия с перетекающими данными вызвала к жизни термин «потоки» (streams). Два термина в Java, означающие разные понятия: thread и stream, переводятся на русский язык одним и тем же словом – поток. Чтобы избежать путаницы, в этом занятии мы будем использовать для первого термина слова «поток вычислений», а для второго – просто «поток». Потоком называется канал обмена информацией между ее источником и получателем. На одном конце потока всегда находится программа на Java. Если она будет Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -5Прикладное программирование в ТС Лекция 3-09 служить источником данных, то данный поток будет выходным, если программа будет находиться на принимающей стороне – то входным. Начиная с версии JDK 1.1, в Java определены два типа потока: байтовый и символьный потоки. Байтовый поток или оперирует с байтами и используется при чтении (input stream) или записи (output stream) данных в двоичном коде. Символьный поток (reader или writer) используется для ввода и вывода символов. В более старых языках, например C или C++, понятие символ и байт являются эквивалентными, поскольку символы имели однобайтовую кодировку. Однако в Java символ не является эквивалентом байта, так как представляет собой 16-битовый элемент, предназначенный для размещения кодов Unicode. В языке Java потоки представляются классами. Простейшие из этих классов работают с базовыми потоками ввода и вывода, имеющими основные средства работы с потоками. От базовых классов порождаются другие классы, в большей степени ориентированные на конкретный тип ввода или вывода. Все классы ввода-вывода Java описаны в пакете java.io. Следует заметить, что практически каждый метод любого класса в пакете java.io способен генерировать ту или иную форму исключительной ситуации IOException. Поэтому для улучшения стиля программирования следует всегда помещать вызовы операций ввода-вывода в блоки try и catch. 2.5.3. Байтовые потоки ввода-вывода 2.5.3.1. Иерархия байтовых потоков ввода-вывода Иерархии классов для входных и выходных байтовых потоков представлены на рис. 5.1 и 5.2. Как видно из рисунков, все классы байтового ввода и вывода являются подклассами абстрактных классов InputStream и OutputStream, конкретизируя и дополняя свойства и методы, описанные в этих классах. Ниже подробно рассматриваются все классы байтового ввода-вывода. IInnppuuttSSttrreeaam m BByytteeA ArrrraayyIInnppuuttSSttrreeaam m FFiilleeIInnppuuttSSttrreeaam m SSeeqquueenncceeIInnppuuttSSttrreeaam m BBuuffffeerreeddIInnppuuttSSttrreeaam m m FFiilltteerrIInnppuuttSSttrreeaam m PPiippeeddIInnppuuttSSttrreeaam m O ObbjjeeccttIInnppuuttSSttrreeaam m D DaattaaIInnppuuttSSttrreeaam m PPuusshhbbaacckkIInnppuuttSSttrreeaam m Рис. 5.1. Иерархии классов для входных байтовых потоков Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -6Прикладное программирование в ТС Лекция 3-09 O OuuttppuuttSSttrreeaam m BByytteeA ArrrraayyO OuuttppuuttSSttrreeaam m FFiilleeO OuuttppuuttSSttrreeaam m PPiippeeddO OuuttppuuttSSttrreeaam m BBuuffffeerreeddO OuuttppuuttSSttrreeaam m FFiilltteerrO OuuttppuuttSSttrreeaam m O ObbjjeeccttO OuuttppuuttSSttrreeaam m D DaattaaO OuuttppuuttSSttrreeaam m PPrriinnttSSttrreeaam m Рис. 5.2. Иерархии классов для выходных байтовых потоков 2.5.3.2. Классы InputStream и OutputStream Абстрактный класс InputStream представляет собой базовый поток ввода. Класс содержит единственный конструктор InputStream(). Класс InputStream описывает также набор методов, необходимых всем входным потокам. Основным методом класса InputStream является read(). Этот метод осуществляет передачу байтов данных из входного потока в массив, размещенный в памяти. Метод read()реализован в трех формах: public int read() throws IOException public int read(byte b[]) throws IOException public int read(byte b[], int off, int len) throws IOException Первая форма считывают из входного потока соответственно отдельные байты как целые числа. Вторая форма считывает множество байтов в байтовый массив, возвращая количество реально введенных байтов, а третий – множество байтов в байтовый массив с указанием смещения (off) в массиве, с которого начнется запись символов, а также максимального числа считываемых байтов (len). Пример: InputStream myInputStream = входной-поток; byte[] someData = new byte[1024]; myInputStream.read(someData); При выполнении этих операторов метод read() предпримет попытку ввести из входного потока в массив полный буфер данных, в нашем случае – все 1024 байта. По завершении работы метод read() возвращает количество действительно введенных байтов данных. Кроме того, можно вызвать не имеющий аргументов вариант метода read(), который всегда предпринимает попытку ввести из входного потока один байт данных. Если сделать попытку чтения данных после достижения конца входного файла, метод read() возвращает значение -1. Кроме того, генерируется исключительная ситуация EOFException. Как правило, проще и удобнее организовать в программе перехват исключительной ситуации EOFException, чем следить за кодом возврата метода. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -7Прикладное программирование в ТС Лекция 3-09 С помощью третьей формы метода read() можно сделать попытку частично заполнить входной буфер данными. Рассмотрим следующий пример: myInputStream.read(someData, 10, 100); При выполнении этого оператора метод read() класса InputStream сделает попытку считать 100 байт данных из входного потока, помещая их в буфер someData, начиная с 10-го байта. В случае приложений, для которых требуется обеспечить достаточно высокую производительность, следует задавать байтовый массив с размером буферной памяти жесткого диска. Например, при чтении файлов с жесткого диска, блоки которого имеют размер 4096 байт, можно получить дополнительный выигрыш в производительности при работе сразу с несколькими буферами данных размером по 4096 байт. Метод read() является блокирующим, поэтому работа потока будет остановлена до завершения операции ввода. В случае больших потоков вводных или выводных данных рекомендуется помещать вызовы подобных методов в специально созданные для них потоки команд — это позволит избежать эффектов «торможения» работы интерфейса пользователя. Метод public long skip(long n) throws IOException пропускает заданное количество байт во входном потоке, а метод public int available() throws IOException возвращает количество байтов, имеющихся в данный момент в потоке. После вызова метода public void mark(int readlimit) поток «запоминает» положение readlimit текущего указателя данных, а после вызова метода void reset() throws IOException изменяет положение точки ввода таким образом, что следующий вызов метода read() начнет ввод данных с запомненной ранее позиции. Возможность использования метода mark() для объекта ввода проверяется с помощью вызова метода public boolean markSupported(). Большинство подклассов класса InputStream не поддерживает методы mark() и reset(). Поэтому, прежде чем вызывать эти методы, следует выполнить вызов метода markSupported() и определить, поддерживает ли эти методы используемый поток. Метод public void close() throws IOException закрывает вводной поток. Абстрактный класс OutputStream, обеспечивает базовые функции для всех выходных потоков и имеет единственный конструктор OutputStream(). Основным методом класса OutputStream является write(), который, так же как и метод read(), определен в трех формах: public void write(int b) throws IOException public void write(byte b[]) throws IOException public void write(byte b[], int off, int len) throws IOException Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -8Прикладное программирование в ТС Лекция 3-09 За одно обращение формы метода позволяет записать один байт, некоторый массив байтов или часть массива байтов. Ниже приведен пример использования метода write() класса OutputStream. byte[] someData = new byte[256]; … // Запись первых 100 байт данных anOutputStream.write(someData,0,100); // Запись одного байта данных anOutputStream.write((int)someData[0]); // Вывод всех данных буфера anOutputStream.write(someData); Желательно помещать операции ввода-вывода в специально выделенные для них потоки команд. Как и метод read(), метод write() является блокирующим, т.е. выполнение потока команд, в котором он вызывается, будет остановлено вплоть до полного завершения операции вывода. Так как класс OutputStream абстрактный, он может быть связан с устройствами практически любых существующих типов — от сетевого адаптера до принтера или файла. В зависимости от типа используемого устройства, данные, перед выводом на него, могут временно помещаться в буфер. Для принудительной выгрузки помещенных в буфер данных на внешнее устройство используется метод public void flush() throws IOException (). Использование метода flush() связано с определенными ограничениями, поскольку он может применяться только к тем данным, которые хранятся в буфере, размещенном на данном компьютере. Если данные были отправлены во внешний буфер, например буфер принтера, вызов метода flush() не окажет на них никакого влияния. Завершив работу с объектом OutputStream, следует вызвать метод public void close() throws IOException, который полностью освобождает все ресурсы, использовавшиеся при работе потока. Если метод close() не будет вызван, служба сборки мусора рано или поздно обнаружит и освободит все связанные с потоком ресурсы. Подобно другим рассмотренным ранее иерархиям классов Java, более конкретные потоковые классы расширяют функциональные возможности базовых классов InputStream и OutputStream. 2.5.3.3. Классы FileInputStream и FileOutputStream Одним из наиболее распространенных применений входных и выходных потоков данных является процедура их чтения из файла на внешнем носителе или записи на внешние носители. Класс FileInputStream создает (открывает) объект, который можно использовать для чтения байтов из файла с помощью одного из следующих конструкторов: FileInputStream (String filepath) throws FileNotFoundException FileInputSream(File file) throws FileNotFoundException FileInputStream(FileDescriptor fdObj) throws FileNotFoundException Параметрами в этих конструкторах являются: filepath – полное имя (с путем) файла; file – объект класса File, который описывает файл; fdObj – объект класса FileDescriptor. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. -9Прикладное программирование в ТС Лекция 3-09 Следующий пример создает два объекта класса FileInputStream, которые используют один и тот же дисковый файл и каждый из этих двух конструкторов: FileInputStream f0 = new FileInputStream("/autoexec.bat") File f = new File("/autoexec.bat"); FileInputStream f1 = new FileInputStream(f); Класс FileInputStream реализует методы available(), close(), skip() и все формы метода read(). Кроме того, в этом классе добавлен метод public final FileDescriptor getFD() throws IOException, возвращающий объект FileDescriptor для открытого файла и метод protected void finalize() throws IOException для очистки связи с файлом и вызова метода close(). Класс FileOutputStream создает объект OutputStream, который можно применять для записи байтов в файл. Обычно используются следующие конструкторы этого класса: FileOutputStream(String filePath) FileOutputStream(File fileObj) FileOutputStream(FileDescriptor fdObj) FileOutputStream(String filePath, boolean append) Параметры в этих конструкторах имеют тот же смысл, что и в конструкторах класса FileInputStream. Если параметр append в последнем конструкторе равен true, файл (если он уже существует) открывается в режиме добавления, иначе файл записывается заново. Класс FileOutputStream реализует метод close() и все формы метода write() класса OutputStream, а также использует свои собственные методы getFD() и finalize(), аналогичные одноименным методам класса FileInputStream. 2.5.3.4. Классы ByteArrayInputStream и ByteArrayOutputStream Класс ByteArrayInputStream является реализацией входного потока, которая использует байтовый массив как источник. Этот класс имеет два конструктора, каждый из которых использует байтовый массив в качестве источника данных: ByteArrayInputStream(byte array[]) ByteArrayInputStream (byte array [], int off, int len) Здесь байтовый массив array – это источник ввода. Второй конструктор создает объект, состоящий из байтового массива, который начинается с позиции off и имеет длину len байтов. Класс ByteArrayInputStream реализует методы read() класса InputStream для чтения оного байта и части массива. Реализация метода reset() в этом классе имеет следующую особенность: если метод mark() не вызвался, то reset() устанавливает поточный указатель на начало потока, который в этом случае является началом массива байт, передаваемого конструктору. Эту особенность можно использовать, например, для чтения одного и того же потока ввода дважды. При выполнении операции вывода массив байтов можно переслать в локальную память компьютера. Конкретным классом выводного потока OutputStream, способным решить эту задачу, является ByteArrayOutputStream. Класс ByteArrayOutputStream имеет следующие два конструктора: ByteArrayOutputStream() ByteArrayOutputStream(int len) Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 10 Прикладное программирование в ТС Лекция 3-09 Первому из них не передается никаких параметров, поэтому он создает буфер размером 32 байт. При необходимости размер буфера может быть увеличен. Однако если увеличение будет выполняться на несколько байтов за каждое обращение, то в некоторых системах это может вызвать сильную фрагментацию памяти. Если заранее известно, что потребуется буфер размером, например, в 1024 байта, следует использовать вторую версию конструктора, которому передается один параметр, как показано ниже. OutputStream anOutputStream = new ByteArrayOutputStream(1024); В дополнение к методам, унаследованным от базового класса OutputStream, класс ByteArrayOutputStream поддерживает следующие методы: public int size() – возвращает текущий размер буфера; public void reset() – сбрасывает значение счетчика выводного потока в 0, так, что уже накопленное содержимое буфера выводного потока теряется; public byte[] toByteArray() – преобразует данные выводного потока в новый массив байтов; public String toString() – преобразует содержимое буфера в строку в соответствии с правилами кодирования символов по умолчанию; public void writeTo(OutputStream out) throws IOException – записывает все содержимое потока в другой поток. Класс ByteArrayOutputStream можно использовать как один из элементов для построения более сложных объектов, включая процедуры взаимодействия между процессами, или для замены других потоков (например, потока сетевых данных) в процессе тестирования. 2.5.3.5. Класс SequenceInputStream Класс SequenceInputStream позволяет сцеплять множество объектов входного потока. Основная форма конструктора этого класса использует в качестве параметров два объекта класса InputStream или его подклассов: SequenceInputStream(InputStream firstStream, InputStream secondStream) Класс выполняет запросы чтения первого объекта типа InputStream (параметр firstStream), пока он не закончится, и затем переключается на второй (параметр secondStream). В свою очередь, используя в качестве одного из объектов объект класса SequenceInputStream, можно организовать ввод более чем двух объектов входного потока. Класс SequenceInputStream реализует методы available() и close(), а также методы read() для чтения байта и части массива класса InputStream. 2.5.3.6. Классы FilterInputStream и FilterOutputStream Фильтрованные потоки — просто оболочки (wrappers) вокруг основных потоков ввода или вывода, которые прозрачно обеспечивают некоторый расширенный уровень функциональных возможностей. Доступ к этим потокам обычно выполняется с помощью методов, использующих порождающий поток, связанный с суперклассом фильтрованных потоков. Типичные применения фильтрованных потоков — буферизация, трансляция символов и необработанных данных. Фильтрованные байтовые входные и выходные потоки реализованы соответственно в классах FilterInputStream и FilterOutputStream. Их конструкторы имеют форму: FilterInputStream(InputStream in) FilterOutputStream(OutputStream out) Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 11 Прикладное программирование в ТС Лекция 3-09 где in и out – объекты соответственно классов InputStream и OutputStream или их подклассов. Хотя классы FilterInputStream и FilterOutputStream реализованы как конкретный (не абстрактные классы), однако они не добавляет никаких функциональных возможностей к тем потокам, на которых он строится. Нет необходимости создавать экземпляры объектов этих классов – достаточно просто использовать их подклассы. Класс FilterInputStream имеет три подкласса: BufferedInputStream, DataInputStream и PushbackInputStream. Производными от класса FilterOutputStream являются следующие классы: BufferedOutputStream, DataOutputStream и PrintStream. 2.5.3.7. Классы BufferedInputStream и BufferedOutputStream Байтовый буферизированный поток расширяет классы фильтрованного потока, присоединяя буфер памяти к потокам ввода/вывода. Такой буфер позволяет выполнять операции ввода-вывода (используя внутренний буфер) не с одним, а с несколькими байтами одновременно, и, следовательно, увеличивает эффективность работы программы. При наличии буфера становится возможным такие операции, как пропуск байтов, маркировка и переустановка потока. Буферизированные поточные классы – это классы BufferedInputStream и BufferedOutputStream. Класс BufferedInputStream содержит два конструктора: BufferedInputStream (InputStream in) BufferedInputStream(InputStream in, int size) Первая форма создает буферизированный поток, используя размер буфера, заданный по умолчанию. Во второй форме размер буфера передается в параметре size. Переменная protected byte[] buf класса BufferedInputStream содержит внутренний буфер входного потока, а переменная protected int count содержит счетчик количества введенных байт (эта величина меняется от нуля до величины массива size). Класс BufferedInputStream поддерживает все методы класса InputStream, за исключением метода read() для чтения массива байт. Класс BufferedOutputStream является эффективным вариантом класса OutputStream, выполняющим запись байтов во внутренний буфер, который затем может быть выведен в поток нижнего уровня. Конструкторы этого класса BufferedOutputStream(OutputStream out) BufferedOutputStream(OutputStream out, int size) создают объект BufferedOutputStream с буфером по умолчанию (512 байт) или с буфером заданного размера. Свойство этого класса protected byte[] buf возвращает содержимое внутреннего буфера, а свойство protected int count – количество выведенных в буфер байт. Класс BufferedOutputStream реализует методы write()записи одного байта и части массива байт, а также методы close() и flush() класса OutputStream. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 12 Прикладное программирование в ТС Лекция 3-09 2.5.3.8. Классы DataInputStream и DataOutputStream Классы DataInputStream и DataOutputStream позволяют приложению соответственно читать примитивные типы данных Java из нижележащего входного потока или записывать примитивные типы данных Java в нижележащий выходной поток в виде, не зависящем от компьютерной платформы или операционной системы. Конструктор класса DataInputStream: DataInputStream(InputStream in) создает входной фильтрованный поток и сохраняет аргумент in для дальнейшего использования. Помимо реализации методов read() для чтения массива байт и части массива байт класса InputStream, в классе DataInputStream реализованы также следующие собственные методы для чтения данных примитивных типов (эти public методы определены в интерфейсе DataInput, реализуемым классом DataInputStream): final boolean readBoolean()– чтение данного типа boolean из входного потока; final byte readByte() – чтение данного типа byte из входного потока; final public int readUnsignedByte() – чтение данного типа byte из входного потока и расширение его нулями до типа int, т.е. получившееся число будет в диапазоне от 0 до 255; void readFully(byte[] b) – чтение байт из входного потока и помещение их в байтовый массив b (количество прочитанных байт равно размеру массива b); void readFully(byte[] b, int off, int len) – чтение len байт из входного потока и помещение их в байтовый массив b; final short readShort() – чтение данного типа short из входного потока; int readUnsignedShort() – чтение данного типа short из входного потока и расширение его нулями до типа int, т.е. получившееся число будет в диапазоне от 0 до 65535; final void readChar(int v) – чтение данного типа short из входного потока; final int readInt(int v) – чтение данного типа int из входного потока; final long readLong(long v) – чтение данного типа long из входного потока; final float readFloat(float v) – чтение данного типа float из входного потока; final double readDouble(double v) – чтение данного типа double из входного потока; final String readUTF()– чтение строки, закодированной с помощью модифицированного формата UTF-8 из входного потока; public int skipBytes(int n) – пропуск n байт во входном потоке. Все приведенные методы бросают исключение IOException. Класс DataOutputStream с конструктором DataOutputStream(OutputStream out) предоставляет методы для записи данных примитивных типов Java. Эти данные могут затем быть прочитаны с помощью соответствующих методов чтения данных примитивного типа класса DataInputStream. Свойство protected int written Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 13 Прикладное программирование в ТС Лекция 3-09 класса DataOutputStream возвращает текущее число записанных в поток данных (если буфер переполняется, это число устанавливает в Integer.MAX_VALUE). Помимо реализации методов write(byte[] b) и write(byte[] b, int off, int len) для чтения массива байт и части массива байт, а также метода flush() класса OutputStream, в классе DataOutputStream определен также метод public final int size(), возвращающий текущее значение счетчика written. Поскольку класс DataOutputStream реализует интерфейс DataOutput, в нем определены следующие методы public final этого интерфейса (все методы бросают исключение IOException): void writeBoolean(boolean v) – запись данного типа boolean в выходной поток; void writeByte(int v) – запись данного типа byte в выходной поток; void writeBytes(String s) – запись строки s в выходной поток как последовательности байт; void writeShort(int v) – запись данного типа short в выходной поток; void writeChar(int v) – запись данного типа char в выходной поток; void writeChars(String s) – запись строки s в выходной поток как последовательности символов; void writeInt(int v) – запись данного типа int в выходной поток; void writeLong(long v) – запись данного типа long в выходной поток; void writeFloat(float v) – запись данного типа float в выходной поток; void writeDouble(double v) – запись данного типа double в выходной поток; void writeUTF(String s) – запись строки, закодированной с помощью модифицированного формата UTF-8 в выходной поток. 2.5.3.9. Класс PushbackInputStream Одно из применений буферизации – выполнение возврата считанного байта обратно во входной поток (операция pushback). Эту возможность в Java осуществляет класс PushbackInputStream. Этот класс имеет следующие конструкторы: PushbackInputStream (InputStream inputStream) PushbackInputStream (InputStream inputStream, int size) Первая форма создает поточный объект, который позволяет возвратить один байт во входной поток. Вторая форма создает поток, который имеет специальный pushbackбуфер длиной size байт. Он дает возможность выполнять возврат нескольких байтов во входной поток. Кроме методов available(), close(), markSupported(), read() и read() для чтения части массива байт класса InputStream, в PushbackInputStream определен метод unread() в следующих формах: void unread(int b) void unread(byte b[]) void unread(byte b [], int off, int len) Первая форма возвращает младший байт параметра b. Возвращенный байт будет прочитан следующим за unread() вызовом read(). Вторая форма возвращает группу байтов — весь буферный массив b[]. Третья форма обеспечивает получение части массива b[] (len байт, начиная с индекса off). Если осуществляется попытка возвратить байт при заполненном буфере возврата, будет брошено исключение класса IOException. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 14 Прикладное программирование в ТС Лекция 3-09 2.5.3.10. Классы PipedInputStream и PipedOutputStream Как указывалось ранее, вычислительные потоки отличаются от процессов тем, что для них уровень защиты операционной системы от влияния других потоков существенно ниже. При проектировании многопоточных приложений рекомендуется каждый из потоков изолировать в собственном классе, после чего определить каналы, по которым эти процессы будут взаимодействовать между собой. Для того, чтобы позволить одному потоку записать данные в другой поток или считать данные из другого потока, используются классы PipedInputStream и PipedOutputStream. Для класса PipedInputStream определены следующие конструкторы: PipedInputStream() PipedInputStream(PipedOutputStream src) Первый конструктор создает входной поток, еще не связанный с выходным потоком, второй конструктор создает входной поток, связанный с выходным потоком src. Конструкторы класса PipedOutputStream имеют следующий вид: PipedOutputStream() PipedOutputStream(PipedInputStream snk) Первый конструктор создает выходной поток, еще не связанный с входным потоком, второй конструктор создает выходной поток, связанный с входным потоком snk. Для того чтобы позволить одному потоку записать данные в другой поток, необходимо создать объект класса PipedInputStream и передать его в качестве параметра в конструктор класса PipedOutputStream, как показано ниже. PipedInputStream myInputStream = new PipedInputStream(); PipedOutputStream myOutputStream = new PipedOutputStream(theInputStream); Теперь, создав новый поток команд, можно организовать в первом потоке команд вывод данных в выходной поток myOutputStream. В результате другой поток получит возможность считывать передаваемые данные из входного потока myInputStream. Класс PipedInputStream реализует методы available() и close(), а также методы read() для чтения одного байта и части массива байт класса InputStream. Кроме этого в этом классе реализован метод public void connect(PipedOutputStream src) throws IOException, связывающий данный входной поток с выходным потоком src, а также метод protected void receive(int b) throws IOException, получающий один байт данных (этот метод может заблокировать поток, если нет доступных данных). Класс PipedOutputStream реализует методы flush() и close(), а также методы write() для записи одного байта и части массива байт класса OutputStream и, кроме того, собственный метод public void connect(PipedInputStream snk) throws IOException, связывающий данный выходной поток с входным потоком snk. 2.5.3.11. Сериализация объектов и использование классов ObjectOutputStream и ObjectInputStream Сериализация (serialization) – это процесс записи состояния объекта в байтовый поток. Этот процесс используется, когда необходимо сохранить состояние программы в Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 15 Прикладное программирование в ТС Лекция 3-09 постоянной памяти, например, в файле. Сохраненное состояние можно восстановить, используя процесс десериализации (deserialization). Сериализация используется также для реализации механизма вызова удаленных методов (RMI, Remote Method Invocation). Механизм RMI позволяет объекту Java на одной машине вызывать метод другого объекта Java, находящегося на удаленной машине. Удаленный объект можно указывать как аргумент удаленного метода. Посылающая машина сериализирует объект и передает его. Принимающая машина десериализует принятый объект. Следует отметить, что если в объекте имеются ссылки на другие объекты, эти объекты также сериализуются, а в процессе десериализации все эти объекты и их ссылки восстанавливаются в исходную иерархию. Средства сериализации могут работать только с объектами, которые реализуют интерфейс Serializable. В этом интерфейсе не определено никаких конструкторов, свойств и методов и реализация интерфейса Serializable каким-либо классом просто указывает на то, что данный класс может быть сериализован. При этом, если класс сериализован (т. е. реализует интерфейс Serializable), то все его подклассы также сериализованы. Следует отметить, что переменные в классе, которые объявлены как transient или static, не сохраняются средствами сериализации. Класс ObjectOutputStream позволяет записывать данные примитивных типов и образы объектов в выводной поток (сериализовать их). Для этого класса определены следующие конструкторы: ObjectOutputStream() ObjectOutputStream(OutputStream out) Первый конструктор позволяет подклассам этого класса, которые создают свою реализацию класса OutputStream, не выделять память для своих данных, просто используемых в классе ObjectOutputStream, а второй конструктор создает объект ObjectOutputStream, записывающий свои данные в указанный поток out. Класс ObjectOutputStream реализует унаследованные от класса OutputStream методы write() для записи одного байта, байтового массива и части байтового массива, а также метод flush(), сбрасывающий в выходной поток содержимое буферов вывода, и метод close(), закрывающий выводной поток. Кроме того, класс ObjectOutputStream, так же как и рассмотренный ранее класс DataOutputStream, реализует интерфейс DataOutput, содержащий методы для записи в выводной поток данных примитивного типа. Основным же методом класса ObjectOutputStream является метод public void writeObject(Object obj) throws IOException, записывающий в выходной поток объект obj. С помощью этого метода можно осуществлять запись во внешнюю память и в сеть (сериализацию) любых объектов Java (поскольку все без исключения классы Java являются производными от класса Object). Пример сериализации объектов типа String и Date: try { ObjectOutputStream outputStream = new ObjectOutputStream( new FileOutputStream("objects.dat"); outputStream.writeObject("This String is an object."); outputStream.writeObject(new Date()); outputStream.flush(); Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 16 Прикладное программирование в ТС Лекция 3-09 outputStream.close(); } catch (IOException e) { System.err.println("Exception: " + e.getMessage()); } Кроме метода writeObject(), в классе ObjectOutputStream определены и другие методы, позволяющие управлять процессом сериализации объектов. Класс ObjectInputStream читает объекты, предварительно записанные с помощью методов класса ObjectOutputStream, из входного потока. Для класса ObjectInputStream определены следующие конструкторы: ObjectInputStream() ObjectInputStream(InputStream in) Первый конструктор позволяет подклассам этого класса, которые создают свою реализацию класса InputStream, не выделять память для своих данных, просто используемых в классе ObjectInputStream, а второй конструктор создает объект ObjectOutputStream, читающий свои данные из указанного потока out. Класс ObjectInputStream реализует унаследованные от класса InputStream методы read(), позволяющие читать из входного потока один байт, байтовый массив или часть байтового массива, а также метод available(), возвращает количество байтов, которые доступны в буфере ввода, метод skipBytes(), пропускающий во входном потоке заданное количество байт и метод close(), закрывающий входной поток. Класс ObjectInputStream, так же как и рассмотренный ранее класс DataInputStream, реализует интерфейс DataInput, содержащий методы для чтения их входного потока данных примитивного типа. Чтение записанного с помощью метода writeObject() объекта (десериализацию объекта) выполняет метод public Object readObject() throws IOException. Пример десериализации объектов String и Date: try { String s; Date d; ObjectInputStream inputStream = new ObjectInputStream( new FileInputStream("objects.dat"); s = (String) inputStream.readObject(); d = inputStream.readObject(); inputStream.close(); } catch (IOException e) { System.err.println("Exception: " + e.getMessage()); } В классе ObjectInputStream есть также методы для управления процессом десериализации объекта. 5.3.12. Класс PrintStream Класс PrintStream позволяет вывести в поток нижнего уровня представление данных примитивного, строкового или объектного типа, которое они будут иметь при печати. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 17 Прикладное программирование в ТС Лекция 3-09 Два конструктора класса PrintStream: PrintStream(OutputStream out) PrintStream(OutputStream out, boolean autoFlush) создают новый поток для печати. Если второй параметр во втором конструкторе равен true, то выводной буфер будет очищаться в одной из следующих ситуаций: записан массив байт, вызван один из методов println() или в выводной поток записывается символ "\n". Помимо реализации методов write() записи массива байт и части массива байт, а также методов flush() и close() класса OutputStream, в классе PrintStream определены также следующие методы: protected void setError() – установка состояние ошибки для потока в true; public boolean checkError() – очистка буфера потока с проверкой его состояния ошибки. Кроме этого, в классе определены два уже известных метода: public void print(аргумент) и public void println(аргумент) . В качестве аргументов при вызове этих методов могут быть заданы: boolean b, char c, char[] s, float f, double d, int i, long l, String s и Object obj. Следует отметить, что классы System.out и System.err являются экземплярами объектов класса PrintStream. 2.5.4. Символьные потоки ввода-вывода 2.5.4.1. Иерархия символьных потоков ввода-вывода Помимо семейств классов побайтового обмена, производных от классов InputStream и OutputStream, Java предоставляет разработчикам и символьные потоки, предназначенные для обработки символов в кодировке Unicode. На рис. 5.3 показано иерархия классов символьного ввода. R Reeaaddeerr Writer BBuuffffeerreeddR Reeaaddeerr C ChhaarrA ArrrraayyR Reeaaddeerr PPiippeeddR Reeaaddeerr LLiinneeN Nuum mbbeerrR Reeaaddeerr FFiilltteerrR Reeaaddeerr IInnppuuttSSttrreeaam mR Reeaaddeerr SSttrriinnggR Reeaaddeerr PPuusshhbbaacckkR Reeaaddeerr FFiilleeR Reeaaddeerr Рис. 5.3. Иерархия классов символьного ввода Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 18 Прикладное программирование в ТС Лекция 3-09 Семейство классов символьного вывода представлено на рис. 5.4. W Wrriitteerr BBuuffffeerreeddW Wrriitteerr C ChhaarrA ArrrraayyW Wrriitteerr PPiippeeddW Wrriitteerr FFiilltteerrW Wrriitteerr O OuuttppuuttSSttrreeaam mW Wrriitteerr PPrriinnttW Wrriitteerr FFiilleeW Wrriitteerr SSttrriinnggW Wrriitteerr Рис. 5.4. Иерархия классов символьного ввода Символьные потоки лучше всего использовать для реализации многоязычной поддержки. 2.5.4.2. Классы Reader и Writer Функции классов InputStream и OutputStream для символьных потоков выполняют абстрактные классы Reader и Writer. Класс Reader определяет модель символьного входного потока. Конструкторы класса protected Reader() protected Reader(Object lock) определяют новый вводной символьный поток. Критические секции потока в первом конструкторе синхронизируются с самим вводном потоке, а во втором – с указанным объектом lock. Три формы метода read(): public int read() throws IOException public int read(char с[]) throws IOException public int read(char с[], int off, int len) throws IOException действуют аналогично соответствующим методам read() класса InputStream, но не для байт, а для символов. Методы skip(), mark(), reset(), markSupported() и close() действуют аналогично таким же методам класса InputStream, а вместо метода available()используется метод public boolean ready() throws IOException, возвращающий значение true, если гарантируется, что следующая операция read() не будет переведена в состояние ожидания. Класс Writer определяет модель поточного символьного вывода. Конструкторы класса protected Writer () protected Writer (Object lock) определяют новый выводной символьный поток аналогично конструкторам класса Reader. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 19 Прикладное программирование в ТС Лекция 3-09 Метод write() этого класса имеет пять форм: public void write(int с) throws IOException public void write(char с[]) throws IOException public void write(char с[], int off, int len) throws IOException public void write(String str) throws IOException public void write(String str, int off, int len) throws IOException Первые три формы записывают символы и массивы символов аналогично соответствующим методам класса OutputStream, а остальные две формы записывают в выводной поток соответственно всю строку или часть строки. Методы flush() и close() класса Writer также действуют аналогично соответствующим методам класса OutputStream. 2.5.4.3. Классы InputStreamReader и OutputStreamWriter Классы InputStreamReader и OutputStreamWriter являются переходниками между байтовыми и символьными потоками. Класс InputStreamReader преобразует байтовый поток в символьный поток. Основной конструктор этого класса: InputStreamReader(InputStream in) Этот конструктор создает из байтового потока in символьный поток в соответствии с кодировкой по умолчанию (кодировка по умолчанию зависит от реализации виртуальной машины Java и установленных для нее локальных значений). Для класса InputStreamReader определены следующие методы read() для чтения одного символа или части массива символов, методы ready() и close() класса Reader. Класс OutputStreamWriter преобразует символьный поток в байтовый поток. Основной конструктор этого класса: OutputStreamWriter(OutputStream out) Этот конструктор создает из символьного потока out байтовый поток в соответствии с кодировкой по умолчанию. Для вывода символов в классе OutputStreamWriter используются первые три формы метода write(), метод flush() и метод close() класса Writer. 5.4.4. Классы FileReader и FileWriter Класс FileReader создает объект, который можно использовать для чтения содержимого файла. Для создания объекта класса FileReader можно использовать один из следующих конструкторов: FileReader(String fileName) throws FileNotFoundException FileReader(File file) throws FileNotFoundException FileReader(FileDescriptor fd) throws FileNotFoundException В первом конструкторе в качестве параметра задается полное имя файла, во втором – объект класса File и в третьем – объект, реализующий интерфейс FileDescriptor. Класс FileReader наследует методы read() для чтения символа и части массива символов, close() и ready() класса InputStreamReader, а также метод read() для чтения массива символов, mark(), markSupported(), reset() и skip() класса Reader. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 20 Прикладное программирование в ТС Лекция 3-09 Класс FileWriter создает объект, который можно использовать для записи в файл. Конструкторы этого класса: FileWriter(String fileName) throws IOException FileWriter(File file) throws IOException FileWriter(FileDescriptor fd) FileWriter(String fileName, boolean append) throws IOException задают в параметрах либо полное имя файла (параметр fileName), либо объект класса File (параметр file), либо объект, реализующий интерфейс (параметр fd). Второй параметр append последнего конструктора задает режим записи в файл: если true, то вывод добавляется в конец файла, если false – файл перезаписывается. При создании объекта класса FileWriter, если файл существует, он открывается, если файл не существует, он будет создан и открыт. Класс FileWriter наследует первые три формы метода write(), метод flush() и метод close() класса OutputStreamWriter. 2.5.4.5. Классы CharArrayReader и CharArrayWriter Класс CharArrayReader является реализацией входного потока, которая использует символьный массив как источник. Для этого класса определены следующие конструкторы: CharArrayReader(char с[ ]) CharArrayReader(char с[ ], int off, int len) Первый конструктор создает из массива символов c входной поток, а второй конструктор читает len символов массива c, начиная со смещения off. Класс реализует следующие методы класса Reader: skip(), ready(), reset(), mark(), markSupported(), close(), а также методы read() для чтения одного символа и части массива символов. Класс CharArrayWriter реализует выходной поток, записывающий данные в символьный массив. Этот класс имеет два конструктора: CharArrayWriter() CharArrayWriter (int initialSize) В первой форме создается буфер с размером, заданным по умолчанию. Во второй форме – буфер с размером, указанным в параметре initialSize. Буфер содержится в поле buf класса CharArrayWriter. Если необходимо, размер буфера будет увеличен автоматически. Число символов, хранящихся в буфере, содержится в поле count класса CharArrayWriter. И buf и count объявлены с модификатором protected, т.е. являются защищенными полями. Наряду с реализацией методов write() для записи одного символа, части массива символов и части строки, flush() и close() класса Writer, в классе CharArrayWriter реализованы следующие собственные методы: public char[] toCharArray() – возвращает копию вводимых данных; public String toString() – преобразует вводимые данные в строку; public int size() – возвращает текущий размер буфера; public void writeTo(Writer out) throws IOException – записывает содержимое буфера в другой символьный поток. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 21 Прикладное программирование в ТС Лекция 3-09 2.5.4.6. Классы BufferedReader, LineInputReader и BufferedWriter Класс BufferedReader читает текст из символьного входного потока, используя буферизацию символов, так, чтобы обеспечить эффективное чтение символов, массивов и строк. Для класса BufferedReader определены следующие два конструктора: BufferedReader (Reader in) BufferedReader (Reader in, int size) Первая форма создает буферизированный символьный входной поток, используя размер буфера, заданный по умолчанию. Во второй форме размер буфера передается конструктору через параметр size. Класс BufferedReader реализует методы read() для чтения одного символа и части массива символов, skip(), ready(), mark(), markSupported(), reset() и close() класса Reader. Кроме того, в классе реализован собственный метод public String readLine() throws IOException, читающий строку текста (признаком окончания строки является символ перехода на новую строку – '\n', либо символ возврата каретки – '\r', либо оба этих символа, следующие друг за другом). Класс LineNumberReader, являющийся подклассом BufferedReader, наряду с буферизованным вводом, обеспечивает слежение за номерами строк. Его конструкторы LineNumberReader(Reader in) LineNumberReader(Reader in, int size) аналогичны конструкторам класса BufferedReader. Класс LineNumberReader поддерживает методы read() для чтения одного символа и части массива символов, skip(), mark(), readLine() и reset() класса BufferedReader. Кроме того, для операций с номерами строк в классе определены два собственных метода: public int getLineNumber(), получающий текущий номер вводимой строки и public void setLineNumber(int lineNumber), позволяющий переопределить номер вводимой строки (далее нумерация вводимых строк пойдет с приращением 1). Класс BufferedWriter выполняет буферизованную запись символов, массивов и строк в выходной поток. Для создания объекта BufferedWriter класса используются следующие конструкторы: public BufferedWriter(Writer out) public BufferedWriter(Writer out, int size) Первый конструктор создает буферизированный выходной поток, используя буфер с размером, заданным по умолчанию. Во второй форме размер буфера передается через параметр size. Наряду с реализацией методов write() для записи одного символа, части массива символов и части строки, flush() и close() класса Writer, в классе BufferedWriter реализован собственный метод public void newLine() throws IOException, записывающий разделитель строк (конкретный вид разделителя строк в данной операционной среде определяется системным свойством line.separator). Рекомендуется использовать метод newLine() для записи разделителя строк вместо указания конкретного символа (например '\n'), т.к. в этом случае обеспечивается независимость программы от компьютерной платформы. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 22 Прикладное программирование в ТС Лекция 3-09 2.5.4.7. Классы FilterReader, PushbackReader и FilterWriter Абстрактный класс FilterReader предназначен для чтения фильтрованных символьных потоков. Его конструктор protected FilterReader(Reader in) открывает чтение данных из входного потока in. В классе FilterReader определены методы read() для чтения одного символа и части массива символов skip(), mark(), markSupported(), ready(), close () и reset() класса Reader. Подклассом FilterReader является класс PushbackReader, который позволяет возвращать символы обратно в поток, т.е. действует аналогично классу PushbackInputStream. Для класса PushbackReader определены два конструктора: public PushbackReader(Reader in) public PushbackReader(Reader in, int size) Первый конструктор создает входной поток с односимвольным буфером для возвращаемого обратно в поток символа, второй конструктор задает для входного потока буфер возврата размером size. Класс PushbackReader поддерживает методы read() для чтения одного символа и части массива символов, mark(), markSupported(), ready(), close() и reset() класса FilterReader, а также собственные методы public void unread(char[] cbuf) throws IOException и public void unread(char[] cbuf, int off, int len) throws IOException, помещающие в начало буфера возврата соответственно символьный массив cbuf или фрагмент символьного массива cbuf, начиная с позиции off и длиной len. Абстрактный класс FilterWriter предназначен для записи символов в фильтрованный символьный поток. Его конструктор protected FilterWriter(Writer in) открывает запись данных в выходной поток out. В классе FilterWriter определены write() для записи одного символа, части массива символов и части строки, flush() и close() класса Writer. Классы, расширяющие FilterReader и FilterWriter, должны реализовать те методы, которые необходимы для конкретной реализации фильтрованных символьных потоков по заданному критерию. 2.5.4.8. Классы StringReader и StringWriter Классы StringReader и StringWriter создают соответственно входной поток из строки и выходной поток в строку. Для класса StringReader определен конструктор public StringReader(String s) который открывает входной поток из строки s. Для этого класса реализованы следующие методы класса Reader: методы read() для чтения одного символа и части массива символов, mark(), markSupported(), ready(), close (), skip() и reset(). Конструкторы класса StringWriter public StringWriter() public StringWriter(int initialSize) Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 23 Прикладное программирование в ТС Лекция 3-09 открывает выходной поток в строку, используя соответственно первоначальный размер буферной строки либо по умолчанию (в первом конструкторе), либо строку-буфер заданного размера initialSize. Класс реализует методы write() для записи одного символа, части массива символов, строки и части строки, flush() и close() класса Writer. Собственный метод public StringBuffer getBuffer() позволяет получить буферную строку как объект класса StringBuffer, а метод public String toString() позволяет получить значение буферной строки как объект String. 2.5.4.9. Классы PipedReader и PipedWriter Классы PipedReader и PipedWriter выполняют для символьных потоков функции, аналогичные функциям классов PipedInputStream и PipedOutputStream для байтовых потоков, т.е. связывают входной и выходной потоки в разных вычислительных потоках. Класс PipedReader имеет следующие два конструктора: PipedReader() PipedReader(PipedWriter src) Первый конструктор открывает входной канал, еще не подключенный к выходному каналу, а второй открывает входной канал, подключенный к выходному каналу src. Для класса PipedReader определены методы read() для чтения одного символа и части массива символов, ready() и close() класса Reader, а также свой метод public void connect(PipedWriter src) throws IOException, подключающий входной канал к выходному каналу src. Конструкторы класса PipedWriter: PipedWriter() PipedWriter(PipedReader snk) Первый конструктор открывает выходной канал, еще не подключенный к входному каналу, а второй открывает выходной канал, подключенный к входному каналу snk. Методы write() для записи одного символа, части массива символов, flush() и close() унаследованы от класса Writer, а метод public void connect(PipedReader snk) throws IOException подключает выходной канал к входному каналу snk. 2.5.4.10. Класс PrintWriter Класс PrintWriter выводит форматированное представление объектов в текстовый выводной поток. Для класса PrintWriter определены следующие конструкторы: PrintWriter(OutputStream out) PrintWriter(OutputStream out, boolean autoFlush) PrintWriter(Writer out) PrintWriter(Writer out, boolean autoFlush) Первый и второй конструкторы создают новый объект PrintWriter из существующего выводного байтового потока out, третий и четвертый конструкторы создают новый объект PrintWriter из существующего выводного символьного потока Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 24 Прикладное программирование в ТС Лекция 3-09 out (при этом создается промежуточный объект OutputStreamWriter, преобразующий символы в байты с использованием кодирования символов по умолчанию). Первый и третий конструкторы выводят символы в выходной поток без автоматического освобождения буфера при переходе на новую строку. Задание для autoFlush значения true во втором и четвертом конструкторах вызывает очистку буферов при использовании методов println(). Методы этого класса не бросают исключения ввода-вывода. Вместо них используются методы данного класса protected void setError() и public boolean checkError() соответственно устанавливающий состояние ошибки в true и проверяющий состояние ошибки. Методы print() и println() класса PrintWriter имеют те же аргументы, что и соответствующие методы класса PrintStream, и действуют аналогично. Кроме того, для класса PrintWriter определены все методы класса Writer. 2.5.5. Консольный ввод, переопределение стандартного ввода-вывода Ранее был рассмотрен вывод на дисплей с использованием стандартных выходных потоков System.out и System.err. Аналогичным образом можно вводить данные с клавиатуры, не создавая собственных потоковых объектов с помощью объекта System.in, являющегося экземпляром класса InputStream. Поскольку объект является экземпляром класса, ему доступны все методы этого класса. Так чтение данных с клавиатуры можно организовать с помощью следующей последовательности операторов: // Объявление буферной области длиной 255 байт byte buffer[] = new byte[255]; // Количество введенных байт int bufferCount = 0; … // Проверка чтения из буфера try { // Чтение из буфера bufferCount = System.in.read(buffer, 0, 255); } // Обработка исключения catch (Exception e) { // Формирование строки сообщения String err = e.toString(); // Вывод строки сообщения на дисплей System.out.println(err); } Признаком окончания ввода с клавиатуры может служить ввод заданного символа или последовательности символов, а также нажатие клавиш Ctrl+Z. Хотя можно обрабатывать входной поток с клавиатуры как байтовый, в Java 2 рекомендуется преобразовать вводимый байтовый поток в символьный, используя объект класса InputStreamReader, а затем преобразовать его в буферный ввод с использованием класса BufferedReader. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 25 Прикладное программирование в ТС Лекция 3-09 Как уже отмечалось, стандартный входной поток System.in соответствует вводу с клавиатуры, а стандартные выходные потоки System.out и System.err – выводу на дисплей. Однако иногда требуется переопределить эти потоки на другие устройства вводавывода, например, оставить выходной поток сообщений на дисплей, а вывод данных направить в файл. Это можно выполнить с помощью следующих методов класса System: public static void setIn(InputStream in) – установка стандартного ввода на входной поток in; public static void setOut(PrintStream out) – установка стандартного вывода на выходной поток out; public static void setErr(PrintStream err) – установка стандартного вывода ошибок на выходной поток err. Так, стандартный выходной поток можно переопределить в файл output.txt, используя следующий оператор: System.setOut( new PrintStream(FileOutputStream("output.txt"))); 2.5.6. Класс RandomAccessFile Классы потоков (включая и символьные потоки) выполняют либо ввод, либо вывод информации, но не то и другое одновременно. Однако иногда в приложениях необходимо иметь доступ к некоторому файлу и для чтения, и для записи, причем одновременно. В этом случае работу с файлом следует выполнять с помощью класса RandomAccessFile. Он напоминает комбинацию классов DataInputFile и DataOutputFile, дополненную некоторыми специальными методами, предназначенными для выполнения операций эффективного поиска в файле. Объект RandomAccessFile создается при помощи одного из двух конструкторов класса: RandomAccessFile(String name, String mode) RandomAccessFile(File file, String mode) Первый конструктор создает объект RandomAccessFile из строки, содержащей имя файла, и другой строки, указывающей на режим доступа к файлу (r – для чтения и rw – для чтения/записи). Второй конструктор создает этот объект на базе объекта File и строки доступа. После создания объекта RandomAccessFile для манипулирования файлом можно вызывать следующие методы этого класса: методы public int read() public boolean readBoolean() public byte readByte() public char readChar() public short readShort() public int readInt() public long readLong() public float readFloat() public double readDouble() public String readLine() возвращают прочитанную порцию информации: байт (как целое число), булевское значение, байт, символ, короткое целое число, целое число, длинное целое число, число с плавающей точкой, число с плавающей точкой двойной точности и строку; методы Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 26 Прикладное программирование в ТС Лекция 3-09 public void write(int b) public void write(byte[] b) public void write(byte[] b, int off, int len) записывают в файл соответственно один байт, массив байтов и фрагмент массива байтов; методы public void writeByte(int v) public void writeBoolean(boolean v) public void writeBytes(String s) public void writeChar(int v) public void writeChars(String s) public void writeShort (int v) public void writeInt(int v) public void writeLong(long v) public void writeFloat(float v) public void writeDouble(double v) записывают порцию информации: байт, булевское значение, символ, строку как последовательность байт, символ, строку как последовательность символов, короткое целое число, целое число, длинное целое число, число с плавающей точкой и число с плавающей точкой двойной точности; методы public void seek(long n) public long getFilePointer() public int SkipBytes(int bytes) позволяют соответственно позиционировать указатель в файле, получить значение позиционированного указателя и пропустить в файле заданное количество байтов; метод public FileDescriptor getFD() получает для файла объект FileDescriptor; метод public long length() определяет длину файла; метод public void close() закрывает файл. 2.5.7. Класс StreamTokenizer При чтении символьных потоков очень часто требуется разбивать данные на отдельные слова. Слово – это последовательность символов, отделенная от других слов пробелом или некоторым символом-разделителем. Для решения подобной задачи в Java существует специальный класс StreamTokenizer, конструктору которого в качестве параметра передается ссылка на входной символьный поток. Свойства этого класса определяют следующие типы слов: TT_EOF – конец файла; TT_EOL – конец строки; TT_NUMBER – число; TT_WORD – текстовое поле. Кроме, того, свойство int nval содержит значение текущего слова, если оно число, а свойство String sval содержит значение текущего слова, если оно – строка. Свойство Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 27 Прикладное программирование в ТС Лекция 3-09 int ttype содержит тип только что прочитанного слова. Класс StreamTokenizer включает множество различных методов. Метод void wordChars(int low, int high) задает, какие символы могут входить в состав слов (параметры low и high устанавливают соответственно нижнюю и верхнюю границу диапазона символов). Вызовы wordChars() аддитивны, так что последующие вызовы добавляют символы в разрешенный диапазон. Набор символов слова по умолчанию определяется следующим образом: // Буквы верхнего регистра tokenizer.wordChars('А', 'Z'); // Буквы нижнего регистра tokenizer.wordChars('а', 'z'); // Другие специальные символы, // лежащие за пределами 7-разрядного набора ASCII tokenizer.wordChars (150, 255); При написании программы синтаксического анализа языка Java следует добавить в слово символы '$' и '_' (подчеркивание), поскольку они часто встречаются в идентификаторах. Это можно сделать двумя вызовами: tokenizer.wordChars ('$', '$'); tokenizer.wordChars('_ ', '_'); Одним из разделителей лексем является пробельный символ. Он сам по себе не является лексемой, но определяет, где кончается одна лексема и начинается другая. Например, фраза "Hello hello hello" содержит три лексемы TT_WORD, разделенные пробельными символами. Фраза "Hello, hello, hello" содержит пять лексем, три "hello" и две запятых. В обоих случаях пробельные символы игнорируются. Типичными пробельными символами являются: ' ' – собственно пробел; '\t' – табуляция; '\n' – перевод строки; '\r' – возврат каретки; '\f' – перевод формата; конец файла; Эквиваленты пробела можно определить с помощью метода void whitespaceChars (int low, int high), который также аддитивен, как и метод wordChars(). Набор эквивалентов пробела по умолчанию определяется так: tokenizer.whitespaceChars(' ', ' '); tokenizer.whitespaceChars('\t', '\t') ; tokenizer.whitespaceChars('\n', '\n'); tokenizer.whitespaceChars('\r', '\r') ; tokenizer.whitespaceChars('\f', '\f'); Управляющие символы определены как эквиваленты пробела, т. е. символы Escape или Backspace приравнены к пробелу. Благодаря этому все эквиваленты пробела устанавливаются за один вызов: tokenizer.whitespaceChars(0, ' '); Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 28 Прикладное программирование в ТС Лекция 3-09 Класс StreamTokenizer может обрабатывать комментарии. Для обработки однострочных комментариев, выполненных в стиле C++ (комментариев типа "//") обрабатывались, необходимо передать значение true методу public void slashSlashComments(boolean flag). Для включения комментариев языка C (комментариев типа "/* */") значение true передается методу public void slashStarComments(boolean flag). В дополнение к этим двум методам имеется возможность пометить отдельные символы как символы комментария при помощи метода public void commentChar (int char). Такой символ считается началом однострочного комментария, т. е., когда он встречается, остаток строки игнорируется, а синтаксический анализ продолжается со следующей строки. Метод commentChar() аддитивный, так что можно определять много символов комментария, вызывая этот метод много раз. Можно отменить все специальные назначения символов и диапазонов символов, если вызвать метод public void ordinaryChar() или public void ordinaryChars(int low, int high). Эти методы лишают символы специального смысла. Например, если символы '$' и '_' были указаны как символы слова, их можно снова сделать обычными, вызвав метод ordinaryChar: tokenizer.ordinaryChar('$'); tokenizer.ordinaryChar('_'); Кроме этого, StreamTokenizer распознает символы кавычек. Когда встречается такой символ, все следующие символы, вплоть до ближайших кавычек, помещаются в строковую переменную sval, а символ кавычек возвращается в качестве значения лексемы. Можно приравнять к кавычкам любой символ при помощи метода public void quoteChar(int char). Символами кавычек по умолчанию являются ' и ". Метод public void eolIsSignificant(boolean flag) позволяет указать, следует ли считать признак конца строки разделителем слов (если да, то flag задается значение true, иначе – false). Слова, возвращаемые в TT_WORD, хранятся именно в таком виде, в каком они появились во входном потоке. Если чувствительность лексемы к регистру не требуется, то есть не проводится разница между FOO, Foo и foo, можно задать автоматическое преобразование в нижний регистр, передав значение true методу public void lowerCaseMode(boolean shiftToLower). Метод String toString() возвращает представление текущей лексемы в виде объекта класса String, а метод public void parseNumbers() обеспечивает распознавание чисел с плавающей точкой. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А. - 29 Прикладное программирование в ТС Лекция 3-09 Если его не вызвать, число 3.14159 будет воспринято как последовательность трех лексем: 3, точка, 14159. Этот метод вызывается автоматически конструктором StreamTokenizer, так что вызывать его явно нужно лишь в случае вызова public void resetSyntax(). Этот метод полностью очищает синтаксические таблицы: По завершении настройки объекта StreamTokenizer вся дальнейшая работа сводится к повторяющимся вызовам метода public int nextToken(), осуществляющего выделение из входного символьного потока последовательности отдельных лексем. Метод public void pushBack() вынуждает класс при следующем вызове метода nextToken() осуществить возврат последнего выделенного слова обратно во входной поток, а метод public int lineno() возвращает номер текущей строки. Класс StreamTokenizer можно использовать как основу для создания подпрограмм обработки вводимых из входного потока текстов. Файл: 681450197 Создан: 09.07.2007 Модифицирован: 29.04.2016 Автор: Шонин В.А.