InputStreamReader и OutputStreamWriter
Оглавление
Класс InputStream.. 3
Класс OutputStream.. 4
Файловый ввод/вывод.. 5
Буферизованные потоки. 5
Класс File. 6
Класс RandomAccessFile. 9
Класс StreamTokenizer. 10
InputStreamReader и OutputStreamWriter. 13
Пример для разбора. 14
Варианты заданий. 17
Базовым пакетом для обеспечения операций ввода/вывода в Java является java.io. В нём определяется несколько абстрактных классов, которые затем расширяются, и на их основе создаются некоторые полезные типы. Потоки почти всегда являются парными: если существует FileInputStream, то есть и FileOutputStream.
Класс InputStream
В абстрактном классе InputStream объявляются методы для чтения из заданного источника. InputStream является базовым классом для большинства входных потоков в java.io и содержит следующие методы, которые (кроме конструктора) при обнаружении ошибки во входном потоке возбуждают исключение IOException:
· public InputStream()
Класс InputStream содержит только безаргументный конструктор.
· public abstract int read() throws IOException
Читает из потока один байт данных и возвращает прочитанное значение, лежащее в диапазоне от 0 до 255 (не от –128 до 127). При достижении конца потока возвращается флаг –1. Метод блокирует работу программы до появления значения на входе.
· public int read(byte[] buf)
Читает данные в массив байтов. Метод блокирует работу программы до появления вводимого значения, после чего заполняет buf всеми прочитанными байтами, в количестве не более buf.length. Метод возвращает фактическое количество прочитанных байтов или –1 при достижении конца потока.
· public int read(byte[] buf, int off, int len)
Читает данные в байтовый подмассив. Метод блокирует работу программы до начала ввода, после чего заполняет часть массива buf, начиная со смещения off, в количестве до len байтов, если не встретится конец массива buf.
· public long skip(long count)
Пропускает до count байтов во входном потоке. Количество пропущенных байтов может быть меньше count из-за достижения конца потока. Возвращает фактическое количество пропущенных байтов.
· public int available()
Возвращает количество байтов, которые могут быть прочитаны без блокировки работы программы.
· public void close()
Закрывает входной поток. Метод должен вызываться для освобождения любых ресурсов (например, файловых дескрипторов), связанных с потоком. Если не сделать это, то ресурсы будут считаться занятыми, пока сборщик мусора не вызовет метод finalize данного потока.
Класс OutputStream
Абстрактный класс OutputStream во многих отношениях напоминает InputStream; он абстрагирует поток байтов, направляемых в приемник. Класс содержит следующие методы, которые (кроме конструктора) при обнаружении ошибки в выходном потоке возбуждают аналогичное предыдущему классу исключение:
· public OutputStream()
Класс OutputStream содержит только безаргументный конструктор.
· public abstract void write(int b)
Записывает в поток байт b. Байт передается в виде значения int, поскольку он часто является результатом арифметической операции над байтами. Выражения, в которых входят данные типа byte, имеют тип int, так что параметр типа int позволяет использовать результат без преобразования в byte. Тем не менее, обратите внимание на то, что передаются только младшие 8 бит значения int — старшие 24 бита при этом теряются. Метод блокирует работу программы до завершения записи байта.
· public void write(byte[] buf)
Записывает в поток содержимое массива байтов. Метод блокирует работу программы до завершения записи.
· public void write(byte[] buf, int offset, int len)
Записывает в поток часть массива байтов, которая начинается с buf [offset] и насчитывает до count байтов, если ранее не будет встречен конец массива.
· public void flush() throws IOException
Очищает поток, то есть направляет в него все байты, находящиеся в буфере.
· public void close() throws IOException
Закрывает поток. Метод должен вызываться для освобождения любых ресурсов, связанных с потоком.
Файловый ввод/вывод
Файловый ввод/вывод в Java представлен двумя потоками — FileInputStream и FileOutputStream. Объекты каждого из этих типов создаются одним из трех конструкторов:
· Конструктор с параметром типа String, содержащим имя файла.
· Конструктор с параметром типа File.
· Конструктор с параметром типа FileDescriptor.
Файловый дескриптор FileDescriptor представляет собой системно-зависимый объект, служащий для описания открытого файла. Он может быть получен вызовом метода getFD для любого объекта класса File или RandomAccessFile. Объекты FileDescriptor позволяют создавать новые потоки File или RandomAccessFile для тех же файлов, что и другие потоки, но при этом не требуется знание имени файлов. Необходимо соблюдать осторожность и следить за тем, чтобы различные потоки не пытались одновременно совершать с файлом различные операции. Например, невозможно предсказать, что случится, когда два потока попытаются одновременно записать информацию в один и тот же файл с использованием двух разных объектов FileDescriptor.
Метод flush класса FileOutputStream гарантирует лишь сброс содержимого буфера в файл. Он не гарантирует, что данные будут записаны на диск — файловая система может осуществлять свою собственную буферизацию.
Буферизованные потоки
Объекты классов BufferedInputStream и BufferedOutputStream обладают свойством буферизации, благодаря чему удается избежать вызова операций чтения/записи при каждом новом обращении к потоку. Эти классы часто используются в сочетании с файловыми потоками — работа с файлом на диске происходит сравнительно медленно, и буферизация позволяет сократить количество обращений к физическому носителю.
При создании буферизованного потока можно явно задать размер буфера или положиться на значение, принятое по умолчанию. Буферизованный поток использует массив типа byte для промежуточного хранения байтов, проходящих через поток.
Если метод read вызывается для пустого потока BufferedInputStream, он выполняет следующие действия: обращается к методу read потока-источника, заполняет буфер максимально возможным количеством байтов и возвращает запрошенные данные из буфера.
Аналогично ведет себя и BufferedOutputStream. Когда очередной вызов write приводит к заполнению буфера, вызывается метод write потока-приемника, направляющий содержимое буфера в поток.
Буферизованный выходной поток, используемый для записи данных в файл, создается следующим образом:
OutputStream bufferedFile(String path)
throws IOExceptioon
{
OutputStream out = new FileOutputStream(path);
return new BufferedOutputStream(out);
}
Сначала для указанного пути создается FileOutputStream, затем порождается BufferedOutputStream и возвращается полученный буферизованный объект-поток. Подобная схема позволяет буферизовать вывод, предназначенный для занесения в файл.
Чтобы пользоваться методами объекта FileOutputStream, необходимо сохранить ссылку на него, поскольку для фильтрующих потоков не существует способа получить объект, следующий за данным объектом-потоком в цепочке. Перед тем как работать со следующим потоком, необходимо очистить буфер, иначе данные в буфере не достигнут следующего потока.
Класс File
Класс File содержит стандартные средства для работы с именами файлов. Его методы позволяют разделять полные имена, включающие путь, на составные части и запрашивать у файловой системы информацию о файлах.
Объект File обычно связан с полным именем файла, причем необязательно существующего. Например, чтобы выяснить, представляет ли некоторое имя существующий в системе файл, следует сначала создать объект File для данного имени, после чего вызвать для этого объекта метод exists.
Следует отличать полное имя файла от имени объекта класса файл. Оно делится на две части: одна определяет каталог (или папку), а другая — собственно файл. Для разделения компонентов пути служит символ, хранящийся как значение типа char в статическом поле separatorChar или значение типа String в статическом поле separator. Последнее вхождение этого символа в путь отделяет каталог от имени файла.
Объекты File создаются одним из трех конструкторов:
1. public File(String path)
Создает объект File для работы с заданным файлом path. Если параметр равен null, возбуждается исключение NullPointerException.
2. public File(String dirName, String name)
Создает объект File для работы с файлом name, находящимся в каталоге dirName. Если параметр dirName равен null, используется только компонент name. В противном случае вызов конструктора эквивалентен следующему:
File(dirname + File.separator + name)
3. public File(File fileDir, String name)
Создает объект File для заданного объекта-каталога fileDir типа File и файла с именем name. Вызов конструктора эквивалентен следующему:
File(fileDir.getPath(), name)
Четыре метода get предназначены для получения информации о компонентах полного имени объекта File. В приведенном ниже фрагменте программы вызывается каждый из этих методов:
File src = new File("ok", "FileMethods");
System.out.println("getName() = " + src.getName());
System.out.println("getPath() = " + src.getPath());
System.out.println("getAbsolutePath() = "
+ src.getAbsolutePath());
System.out.println("getParent() = " + src.getParent());
Вот как выглядит результат работы программы:
getName() = FileMethods
getPath() = ok/FileMethods
getAbsolutePath() = /vob/java_prog/src/ok/FileMethods
getParent() = ok
Несколько методов возвращают логические значения, которые характеризуют файл, представленный объектом File:
· exists: возвращает true, если файл существует в файловой системе.
· canRead: возвращает true, если файл существует и доступен для чтения.
· canWrite: возвращает true, если файл существует и доступен для записи.
· isFile: возвращает true, если файл существует и является обычным (то есть не каталогом или файлом особого типа).
· isDirectory: возвращает true, если файл является каталогом.
· isAbsolute: возвращает true, если путь представляет собой полное имя файла.
Класс File содержит и другие полезные методы:
· public long lastModified()
Возвращает дату последней модификации файла. Возвращаемое значение должно использоваться только для сравнения дат последних модификаций различных файлов и для выяснения того, редактировался ли некоторый файл после другого, а также для проверки того, модифицировался ли файл вообще. Временем последней модификации файла не следует пользоваться для других целей.
· public long length()
Возвращает длину файла в байтах.
· public boolean mkdir()
Создает каталог и возвращает true в случае успеха.
· public boolean mkdirs()
Создает все каталоги, входящие в заданный путь, и возвращает true в случае успеха. Метод обеспечивает возникновение конкретного каталога, даже если для этого потребуется создать другие, не существующие в данный момент каталоги, которые находятся выше в иерархии.
· public boolean renameTo(File new_name)
Переименовывает файл и возвращает true в случае успеха.
· public boolean delete()
Удаляет файл или каталог, представленный объектом File, и возвращает true в случае успеха.
· public String[] list()
Возвращает список файлов в каталоге. Если объект File представляет собой не каталог, а нечто иное, передается null; в противном случае возвращается массив с именами файлов. Список содержит все файлы каталога, за исключением эквивалентов “.” и “..” (текущий и родительский каталоги соответственно).
· public String[] list(FilenameFilter filter)
Использует заданный фильтр для составления списка файлов в каталоге (см. ниже раздел “Интерфес Filename Filter”).
Переопределенный метод File.equals заслуживает особого упоминания. Два объекта File считаются равными в том случае, если совпадают их полные имена, а не в том, если они представляют один и тот же файл в системе. Метод File.equals не может применяться для выяснения того, соответствуют ли два объекта File одному и тому же файлу.
Для создания файлов используются объекты классов FileOutputStream или RandomAccessFile, а не объекты класса File.
Наконец, остается упомянуть, что символ File.pathSeparatorChar и строка File.pathSeparator представляют символ, разделяющий каталоги или файлы в полном имени. Например, в системе UNIX для разделения компонентов полного имени используется двоеточие: “.:/bin:/usr/bin”. Следовательно, в системе UNIX символ pathSepar a torChar представляет собой двоеточие.
Полное имя файла хранится в защищенном строковом поле с именем String. Подклассы File могут при необходимости непосредственно обращаться к этому полю или модифицировать его.
Класс RandomAccessFile
Класс RandomAccessFile предоставляет более совершенный механизм для работы с файлами, чем файловые потоки. Он не является расширением Input Stream или OutputStream, поскольку может осуществлять любую из операций чтения/записи или оба действия сразу. Режим работы с файлом указывается в качестве параметра для различных конструкторов. Класс Random AccessFile реализует оба интерфейса Data InputStream и DataOutput Stream, поэтому он может применяться для чтения/записи встроенных типов Java.
Хотя класс RandomAccessFile не является расширением входных и выходных потоковых классов, имена и сигнатуры содержащихся в нем методов совпадают с вызовами read и write. Хотя это означает, что вам не придется учить новый набор имен и семантик для выполнения той же самой задачи, объекты класса RandomAccessFile не могут использоваться там, где требуется присутствие объектов InputStream или OutputStream. Тем не менее, вы можете использовать объекты RandomAccessFile вместо объектов-потоков DataInput или DataOutput.
Класс RandomAccessFile содержит три конструктора:
· public RandomAccessFile(String name, String mode)
Создает объект RandomAccessFile для заданных имени файла и режима. Режим указывается в виде “r” или “rw” для доступа по чтению или чтению/записи соответственно. Любое другое значение режима приводит к возбуждению IOException.
· public RandomAccessFile(File file, String mode)
Создает объект RandomAccessFile для заданного объекта класса File и режима.
· public RandomAccessFile(FileDescriptor fd)
Создает объект RandomAccessFile для заданного объекта fd типа File Descriptor (см. раздел “Файловые потоки и FileDescriptor”).
Термин “произвольный доступ” (random access), вынесенный в название типа, обозначает возможность установки файлового указателя чтения/записи в любую позицию внутри файла с последующим выполнением нужной операции. Эта возможность обеспечивается следующими методами:
· public long getFilePointer()
Возвращает текущее смещение (в байтах) от начала файла.
· public void seek(long pos)
Устанавливает файловый указатель в заданную позицию (в байтах). Следующий считанный или записанный байт будет иметь смещение pos.
· public long length() throws IOException
Возвращает длину файла.
Класс StreamTokenizer
Разделение входного потока на отдельные лексемы встречается довольно часто, поэтому пакет java.io содержит специальный класс StreamTokenizer для выполнения простейшего лексического анализа. В настоящее время этот класс в полной мере работает лишь с младшими 8 битами Unicode, составляющими подмножество символов Latin-1, поскольку внутренний массив класса, хранящий информацию о категориях символов, состоит только из 256 элементов. Символы, превышающие \u00ff, считаются алфавитными. Хотя в подавляющем большинстве случаев это действительно так (собственно, большая часть символов относится к алфавитным), вы, например, не сможете назначить в качестве ограничителя символ ‘?‘ (\u270D). Даже с учетом этого условия выделение лексем во многих случаях происходит нормально.
Чтобы выделить лексемы в потоке, следует создать объект StreamTokenizer на основе объекта InputStream и затем установить параметры анализа. Цикл сканирования вызывает метод nextToken, который возвращает тип следующей лексемы в потоке. С некоторыми типами лексем связываются значения, содержащиеся в полях объекта StreamTokenizer.
Когда метод nextToken распознает следующую лексему, он возвращает ее тип и присваивает это же значение полю ttype. Имеются четыре типа лексем:
· TT_WORD: обнаружено слово. Найденное слово помещается в поле sval типа String.
· TT_NUMBER: обнаружено число. Найденное число помещается в поле nval типа double. Распознаются только десятичные числа с плавающей точкой (с десятичной точкой или без нее). Анализатор не распознает 3.4e79 как число с плавающей точкой, или 0xffff как шестнадцатеричное число.
· TT_EOL: обнаружен конец строки.
· TT_EOF: обнаружен конец файла.
Символы входного потока делятся на специальные и ординарные. Специальными считаются символы, которые особым образом обрабатываются в процессе анализа, — пробелы, символы, образующие числа и слова, и так далее. Все остальные символы относятся к ординарным. Если следующий символ потока является ординарным, то тип лексемы совпадает с символом. Например, если в потоке встречается символ ‘В‘ и он не является специальным, то тип лексемы (и поле ttype) равен эквиваленту символа ‘В‘ в типе int.
Анализатор распознает несколько категорий символов, которые определяются следующими методами:
· public void wordChars(int low, int hi)
Символы в этом диапазоне образуют слова; они могут входить в лексему типа TT_WORD. Допускается многократный вызов этого метода с разными диапазонами. Слово состоит из одного или нескольких символов, входящих в любой их допустимых диапазонов.
· public void whitespaceChars(int low, int hi)
Символы в этом диапазоне являются разделителями. При анализе они игнорируются; их единственное назначение заключается в разделении лексем — например, двух последовательных слов. Как и в случае wordChars, можно вызывать этот метод несколько раз, при этом объединение всех диапазонов определяет набор символов-разделителей.
· public void ordinaryChar (int ch)
Символ ch является ординарным. Ординарный символ при анализе потока возвращается сам по себе, а не в виде лексемы.
· public void ordinaryChars (int low, int hi)
Символы в диапазоне являются ординарными.
· public void commentChar (int ch)
Символ ch начинает однострочный комментарий — символы от ch до ближайшего конца строки считаются одним длинным разделителем.
· public void quoteChar (int ch)
Пары символов ch являются ограничителями для строковых констант. Когда в потоке распознается строковая константа, символ ch возвращается в качестве лексемы, а поле sval содержит тело строки (без символов-ограничителей). При чтении строковых констант обрабатываются некоторые стандартные символы Java в записи с \ (например, \t), но не все. Строки, воспринимаемые StreamTokenizer, представляют собой подмножество строк Java. Особенно жесткий запрет накладывается на использование \xxxx, \’, \" или (к сожалению) \Q, где символ Q совпадает с символом-ограничителем ch. В потоке могут присутствовать несколько разных символов-ограничителей, но строки должны начинаться и заканчиваться одним и тем же ограничителем. Другими словами, строка, которая начинается одним символом-ограничителем, продолжается до следующего вхождения того же символа; если в середине строки встречается другой символ-ограничитель, то он просто считается частью строки.
· public void parseNumbers()
Указывает на необходимость выделения чисел из потока. StreamTokenizer выдает числа с плавающей точкой двойной точности и возвращает тип лексемы TT_NUMBER, а значение лексемы помещается в поле nval. Просто отказаться от поиска чисел невозможно — для этого придется либо вызвать ordinaryChars для всех символов, входящих в состав числа (не забудьте о десятичной точке и знаке “минус”), либо вызвать resetSyntax.
· public void resetSyntax()
Сбрасывает синтаксическую таблицу, в результате чего все символы становятся ординарными. Если вы вызовете resetSyntax и затем начнете читать поток, то nextToken всегда будет выдавать следующий символ потока, как будто вы используете метод InputStream.read.
Не существует методов для определения категории заданного символа или для добавления новых категорий. Ниже приведены значения параметров по умолчанию для только что созданного объекта StreamTokenizer:
1. wordChars(‘a’, ‘z’);
2. wordChars(‘A’, ‘Z’);
3. wordChars(128 + 32, 255);
4. whitespaceChars(0, ‘ ‘);
5. commentChar(‘/’);
6. quoteChar(‘"’);
7. quoteChar(‘\’’);
8. parseNumbers();
Остальные методы управляют поведением анализатора:
· public void eolIsSignificant(boolean flag)
Если значение flag равно true, то конец строки является существенным, и nextToken может возвращать TT_EOL. В противном случае концы строк считаются символами-разделителями и TT_EOL никогда не возвращается. Значение по умолчанию равно false.
· public void slashStarComments(boolean flag)
Если значение flag равно true, анализатор распознает комментарии вида /*...*/. Значение по умолчанию равно false.
· public void slashSlashComments(boolean flag)
Если значение flag равно true, анализатор распознает комментарии от // до конца строки. Значение по умолчанию равно false.
· public void lowerCaseMode(boolean flag)
Если значение flag равно true, все символы в лексемах типа TT_WORD преобразуются в нижний регистр, если имеется соответствующий эквивалент (то есть к слову применяется метод String.toLowerCase). Значение по умолчанию равно false.
Имеется также несколько методов общего назначения:
· public void pushBack()
Заносит предыдущую лексему обратно в поток. Следующий вызов nextToken снова вернет ту же самую лексему. Глубина отката ограничивается одной лексемой; несколько последовательных вызовов pushBack эквивалентны одному вызову.
· public int lineno()
Возвращает текущий номер строки. Обычно это бывает полезно для вывода сообщений о найденных ошибках.
· public String toString()
Возвращает строковое представление последней возвращенной лексемы, включающее номер строки.
InputStreamReader и OutputStreamWriter
Пакет java.io предоставляет классы, которые позволяют выполнять конвертацию между символьными потоками Unicode и байтовыми потоками не-Unicode текста. При помощи класса InputStreamReader существует возможность преобразовывать байтовые потоки в символьные потоки. Для перевода символьных потоков в байтовые потоки используется класс OutputStreamWriter.
При создании объектов InputStreamReader и OutputStreamWriter, задается байтовая кодировка, в которую необходимо выполнить преобразование. Например, чтобы перевести текстовый файл в кодировке UTF-8 в Unicode, необходимо сделать следующее:
FileInputStream fis = new FileInputStream("test.txt");
InputStreamReader isr = new InputStreamReader(fis, "UTF8");
Если не указать идентификатор кодировки, InputStreamReader и OutputStreamWriter будут обращаться к кодировке по умолчанию. Вы можете определить кодировку, используемую InputStreamReader или OutputStreamWriter, вызвав метод getEncoding, таким образом:
InputStreamReader defaultReader = new InputStreamReader(fis);
String defaultEncoding = defaultReader.getEncoding();
Пример для разбора
Пусть необходимо разработать программу, подсчитывающую количество определенного слова в файле. В качестве параметров в командной строке передаются:
1. имя файла
2. кодировка файла
3. слово для подсчета
import java.io.*;
import java.util.Scanner;
public class Reader
{
private static String _encoding;
private static String _string;
public static void main(String[] args)
{
if (args.length != 3)
{
System.out.println("Wrong number of input arguments");
return;
}
File testFile = new File(args[0]);
_encoding = args[1];
_string1 = args[2];
String contents = GetContents(testFile);
System.out.print("File contents:\r\n" + contents);
System.out.println("--------------------------------------------");
Analyze(contents);
}
static public String GetContents(File file)
{
StringBuilder contents = new StringBuilder();
try
{
if (file == null)
{
throw new IllegalArgumentException("File should not be null.");
}
if (!file.exists())
{
throw new FileNotFoundException();
}
if (!file.canRead())
{
throw new IllegalArgumentException("File cannot be written: " + file);
}
if (!file.isFile())
{
throw new IllegalArgumentException("Should not be a directory: " + file);
}
FileInputStream fis = new FileInputStream(file);
InputStreamReader in = new InputStreamReader(fis, _encoding);
BufferedReader input = new BufferedReader(in);
try
{
String line = null;
while ((line = input.readLine()) != null)
{
contents.append(line + "\r\n");
}
}
finally
{
input.close();
}
}
catch (FileNotFoundException ex)
{
System.out.println("File does not exist: " + file);
}
catch(IllegalArgumentException ex)
{
System.out.println(ex.getMessage());
}
catch (Exception ex)
{
ex.printStackTrace();
}
return contents.toString();
}
private static void Analyze(String contents)
{
Scanner scanner = new Scanner(contents);
int stringCount = 0;
while (scanner.hasNextLine())
{
Scanner s = new Scanner(scanner.nextLine());
while (s.hasNext())
{
String word = s.next();
if (word.contentEquals(_string))
stringCount++;
}
s.close();
}
scanner.close();
System.out.println(_string + " — " + stringCount);
}
}
Варианты заданий
1. Есть файл в определенной кодировке. Содержимое файла: n-строк. Необходимо упорядочить строки по алфавиту (упорядочить порядок строк в файле, а не символов в этих строках) и записать их в новый файл.
2. Есть файл в определенной кодировке. Содержимое файла: n-строк. Необходимо заменить все символы табуляции в файле на пробел; удалить множественные пробелы, оставив лишь один. Записать всё в новый файл.
3. Создать список всех файлов и папок, находящихся внутри какой-то директории (в том числе и вложенных файлов\папок вложенных в эту директорию), путь к которой указывается как параметр программе. Список записать в файл.
4. Создать программу шифратор, которая записывает каждый новый символ в файл в разных кодировках (последовательность которых, к примеру, повторяются каждые 10 символов). Аналогично сделать возможность дешифровки созданного файла с записью расшифрованного содержимого в новый файл.