Доброго времени суток, мой виртуальный друг!
В прошлый раз мы рассмотрели байтовые потоки.
Но, к сожалению, люди пока не научились читать информацию в виде байтов, а до сих пор, по неизвестной причине, предпочитают информацию в текстовом виде. Странные они...
Поэтому, байты байтами, но работать приходится чаще всего именно с текстом.
К счастью, Java и тут нас не бросит, из коробки предоставляя такие средства работы с текстом, как символьные потоки. Рассмотрим же их поближе.
Если же вы хотите каждый раз дописывать информацию - нужно немного изменить код:
В прошлый раз мы рассмотрели байтовые потоки.
Но, к сожалению, люди пока не научились читать информацию в виде байтов, а до сих пор, по неизвестной причине, предпочитают информацию в текстовом виде. Странные они...
Поэтому, байты байтами, но работать приходится чаще всего именно с текстом.
К счастью, Java и тут нас не бросит, из коробки предоставляя такие средства работы с текстом, как символьные потоки. Рассмотрим же их поближе.
Символьные потоки ввода
Для работы с символьными потоками ввода существует класс java.io.Reader. Его методы:
/**
* Метод читает символы и сохраняет их в символьном буфере target.
*
* @param target буфер для хранения символов
* @return число сохраненных в буфере символов или -1, если символы в источнике закончились
*
* @throws IOException если произошла ошибка ввода-вывода
* @throws NullPointerException если target равен null
* @throws java.nio.ReadOnlyBufferException если target предназначен только для чтения
*/
public int read(CharBuffer target) throws IOException;
/**
* Метод читает из потока код одного символа.
*
* @return возвращает код одного символа в виде целого числа или -1,
* если символов больше нет во входном потоке
*
* @exception IOException при возникновении ошибки ввода-вывода
*/
public int read() throws IOException;
/**
* Метод читает символы и сохраняет их в массив.
*
* @param cbuf массив, в котором будут сохранены символы
*
* @return число прочитанных символов или -1,
* если символов больше нет во входном потоке
*
* @exception IOException при возникновении ошибки ввода-вывода
*/
public int read(char cbuf[]) throws IOException;
/**
* Читает символы и сохраняет их в определенную область массива.
*
* @param cbuf массив, в котором будут сохранены символы
* @param off позиция относительно начала массива,
* в которую будут сохраняться прочитанные символы
* @param len максимальное число символов, которые будут прочитаны
*
* @return число прочитанных символов или -1,
* если символов больше нет во входном потоке
*
* @exception IOException при возникновении ошибки ввода-вывода
*/
public int read(char cbuf[], int off, int len) throws IOException;
/**
* Метод пропускает символы в потоке.
*
* @param n число символов, которое нужно пропустить
*
* @return число реально пропущенных символов
*
* @exception IllegalArgumentException если n - отрицательное число
* @exception IOException при возникновении ошибки ввода-вывода
*/
public long skip(long n) throws IOException;
/**
* Готов ли поток к чтению данных.
*
* @return true если поток готов к чтению
*
* @exception IOException при возникновении ошибки ввода-вывода
*/
public boolean ready() throws IOException;
/**
* Метод для определения, поддерживает ли данный поток механизм меток.
*
* @return true, если метки поддерживаются и false - если нет
*/
public boolean markSupported();
/**
* Метод ставит метку на текущей позиции в потоке,
* чтобы иметь возможность потом к ней вернуться.
* Не все потоки поддерживают механизм меток.
*
* @param readAheadLimit максимальное количество символов,
* прочитанных после установки метки,
* чтобы метка оставалась актуальной.
* Если будет прочитано большее количество символов -
* метка становится неактуальной
* и метод reset завершится с ошибкой.
*
* @exception IOException если механизм меток не поддерживается или
* в случае любой другой ошибки ввода-вывода
*/
public void mark(int readAheadLimit) throws IOException;
/**
* Перемещает чтение потока в позицию, ранее помеченную меткой.
*
* @exception IOException Если метка не была установлена или
* если метка стала неактуальной или,
* если поток не поддерживает метод reset() или
* при возникновении ошибки ввода-вывода
*/
public void reset() throws IOException;
/**
* Закрывает поток.
*
* @exception IOException при возникновении ошибки ввода-вывода
*/
public void close() throws IOException;
Как и в случае байтовых потоков, класс Reader - абстрактный класс, поэтому для создания конкретных потоков используются его подклассы. Наиболее популярные из них:
/**
* Класс для чтения текста из файла.
*/
java.io.FileReader;
/**
* Класс, превращающий входной поток байтов во входной поток символов.
*/
java.io.InputStreamReader;
/**
* Класс, который добавляет возможность буферизации и чтения строками.
java.io.BufferedReader;
Символьные потоки вывода
Теперь познакомимся с символьными потоками вывода. Базовым классом для всех потоков вывода в Java является класс java.io.Writer. Его методы:
/**
* Метод записывает в поток один символ.
*
* @param c целочисленное представление символа
*
* @throws IOException при возникновении ошибки ввода-вывода
*/
public void write(int c) throws IOException;
/**
* Записывает массив символов в поток.
*
* @param cbuf массив символов
*
* @throws IOException при возникновении ошибки ввода-вывода
*/
public void write(char cbuf[]) throws IOException;
/**
* Записывает область массива символов в поток.
*
* @param cbuf массив символов
* @param off начало области массива, которую надо записать в поток
* @param len количество символов, которые надо записать в поток
*
* @throws IOException при возникновении ошибки ввода-вывода
*/
public void write(char cbuf[], int off, int len) throws IOException;
/**
* Записывает строку в поток.
*
* @param str строка
*
* @throws IOException при возникновении ошибки ввода-вывода
*/
public void write(String str) throws IOException;
/**
* Записывает часть строки в поток
*
* @param str строка
* @param off индекс первого символа подстроки, которую надо записать
* @param len число символов, которые надо записать
*
* @throws IndexOutOfBoundsException если off < 0 или
len < 0 или off + len < 0 или
off + len > str.length()
* @throws IOException при возникновении ошибки ввода-вывода
*/
public void write(String str, int off, int len) throws IOException;
/**
* Дописывает символьную последовательность в поток.
*
* @param csq символьная последовательность
*
* @return текущий объект Writer
*
* @throws IOException при возникновении ошибки ввода-вывода
*/
public Writer append(CharSequence csq) throws IOException;
/**
* Дописывает в поток часть символьной последовательности.
*
* @param csq символьная последовательность, часть которой нужно дописать в поток
*
* @param start индекс первого символа, который нужно записать в поток
* @param end индекс последнего символа, который нужно записать в поток
*
* @return текущий объект Writer
*
* @throws IndexOutOfBoundsException если start - отрицательное число или
* end отрицательное число или
* start > end или
* end > csq.length()
* @throws IOException при возникновении ошибки ввода-вывода
*/
public Writer append(CharSequence csq, int start, int end) throws IOException;
/**
* Дописывает в поток один символ.
*
* @param c символ, который нужно дописать в поток
*
* @return текущий объект Writer
*
* @throws IOException при возникновении ошибки ввода-вывода
*
*/
public Writer append(char c) throws IOException;
/**
* Метод записывает всю информацию из буфера в приемник.
*
* @throws IOException при возникновении ошибки ввода-вывода
*/
public void flush() throws IOException;
/**
* Закрывает поток, предварительно записав в приемник всю информацию из буфера.
*
* @throws IOException при возникновении ошибки ввода-вывода
*/
public void close() throws IOException;
Чаще всего используются следующие подклассы класса Writer:
/**
* Класс, для записи текста в файл.
*/
java.io.FileWriter;
/**
* Класс, который добавляет буферизацию при записи информации в поток.
*/
java.io.BufferedWriter;
/**
* Класс, который предоставляет множество методов для удобной записи различной информации в поток.
*/
java.io.PrintWriter;
Особая роль PrintStream
В пакете java.io есть удивительный класс PrintStream, который является наследником OutputStream, но, при этом, имеет множество методов записи текста в поток. Если сравнить класс PrintWriter и PrintStream, можно заметить, что методы для записи чисел, символов и т.п. - просто совпадают.
Зачем же наследовать от класса OutputStream, отвечающего за байтовый вывод, класс, который отвечает за символьный вывод?
Ответ на самом деле довольно прост. Класс PrintStream был добавлен в Java в версии 1.0, а класс Writer - в версии 1.1. То есть на момент добавления PrintStream базовых классов, отвечающих за символьный ввод и вывод просто не существовало.
Но существует и более интересная причина такой долгой жизни класса PrintStream и она связана с так называемыми стандартными потоками.
Стандартные потоки
Стандартные потоки - это потоки данных, которые всегда открываются при запуске процесса в операционной системе. Таких потоков обычно три:
- Стандартный поток ввода - stdin - предназначен для чтения команд пользователя. По умолчанию связан с устройством ввода текстовой информации - клавиатурой;
- Стандартный поток вывода - stdout - предназначен для отображения текстовой информации пользователю. По умолчанию связан с устройством вывода текстовой информации - монитором;
- Стандартный поток вывода ошибок - stderr - предназначен для вывода ошибок и диагностической информации. Обычно связан с тем же устройством, что и stdout, но, в отличии от stdout, вывод в stderr не буферизируется.
Java по умолчанию всегда автоматически инициализирует и открывает стандартные потоки:
- Стандартный поток ввода - System.in - объект класса InputStream;
- Стандартный поток вывода - System.out - объект класса PrintStream;
- Стандартный поток вывода ошибок - System.err - объект класса PrintStream;
Теперь понятно, что поскольку стандартный поток вывода и ошибок имеют класс PrintStream, от этого класса нельзя избавиться, не разрушив обратной совместимости.
К тому же, аргументом в пользу PrintStream является то, что стандартные потоки не всегда оперируют символьной информацией.
Иногда - хотя и довольно редко - возникает необходимость передать через стандартные потоки массивы байтов. Тут и пригодится тот факт, что PrintStream является наследником OutputStream и способен одинаково хорошо работать и с текстом и с байтами.
Утилитный класс Scanner
Для того, чтобы сделать чтение текста удобнее в Java 1.5 в пакет java.util был добавлен класс Scanner. Он имеет множество удобных методов для чтения не только текста, но и чисел, логических констант и многого другого.
Для демонстрации возможностей этого класса, напишем небольшую программу, которая запрашивает у пользователя два целых числа, складывает их, и выдает пользователю результат:
import java.util.Scanner;
public class IntSum {
public static void main(String[] args) {
//Используем Scanner для чтения информации введенной пользователем через консоль
//В данном случае закрывать scanner не нужно, так как он
//связан со стандартным потоком ввода System.in, которым управляет сама Java
Scanner scanner = new Scanner(System.in);
System.out.println("Введите целое число: ");
//Читаем первое число. Если пользователь введет не число - возникнет ошибка
int value = scanner.nextInt();
System.out.println("Введите еще одно целое число: ");
//Читаем второе число. Если пользователь введет не число - возникнет ошибка
int value2 = scanner.nextInt();
int result = value + value2;
//Выводим результат в консоль, используя метод printf,
//который умеет распознавать шаблоны (%d, %s и т.п.) и заменять их числами, строками и т.п.
System.out.printf("Сумма чисел %d и %d равна %d", value, value2, result);
}
}
Теперь, когда мы основательно изучили теорию - рассмотрим несколько примеров.
Пример 1 - из консоли в файл
Пусть мы хотим всю информацию, введенную пользователем в консоль - сохранять в файл.
Главное, о чем нужно помнить при работе с текстом - это кодировка символов. В этой программе - мы читаем и пишем в кодировке UTF-8:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Scanner;
public class LogMe {
public static void main(String[] args) throws IOException {
//Файл, куда будем писать все введенное с клавиатуры
File file = new File("D:/log.txt");
//Используем Scanner для чтения того, что введет пользователь
//В данном случае закрывать scanner не нужно, так как он
//связан со стандартным потоком ввода System.in, которым управляет сама Java
Scanner scanner = new Scanner(System.in, "UTF-8");
//Используем PrintWriter для того, чтобы писать в файл
try(PrintWriter writer = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(file), "UTF-8"))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if(line != null) {
//Если пользователь ввел exit - завершаем программу
if("exit".equals(line)) {
return;
}
//Пишем в файл то, что ввел пользователь
writer.println(line);
}
}
}
}
}
Если вы запустите программу и введете пару строк, то после ввода exit - в файл запишется все, что вы ввели. Единственный нюанс в том, что если вы запустите программу несколько раз - она будет каждый раз перезаписывать содержимое файла.Если же вы хотите каждый раз дописывать информацию - нужно немного изменить код:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Scanner;
public class LogMe {
public static void main(String[] args) throws IOException {
//Файл, куда будем писать все введенное с клавиатуры
File file = new File("D:/log.txt");
//Используем Scanner для чтения того, что введет пользователь.
//В данном случае закрывать scanner не нужно, так как он
//связан со стандартным потоком ввода System.in, которым управляет сама Java
Scanner scanner = new Scanner(System.in, "UTF-8");
//Используем PrintWriter для того, чтобы писать в файл
try(PrintWriter writer = new PrintWriter(
new OutputStreamWriter(
//Второй параметр true - значит не перезаписываем содержимое файла,
//а дописываем в него
new FileOutputStream(file, true), "UTF-8"))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if(line != null) {
//Если пользователь ввел exit - завершаем программу
if("exit".equals(line)) {
return;
}
//Пишем в файл то, что ввел пользователь
writer.println(line);
}
}
}
}
}
Пример 2 - из файла в консоль
Вместо заключения
Рассмотрим обратную задачу - прочитаем содержимое файла и выведем его в консоль:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class FileToConsole {
public static void main(String[] args) throws FileNotFoundException {
//Файл, содержимое которого мы будем читать
File file = new File("D:/1.txt");
//Используем Scanner, чтобы построчно читать содержимое файла в кодировке UTF-8
//В данном случае - обязательно закрыть за собой поток, так как мы сами его и открыли
try(Scanner scanner = new Scanner(file, "UTF-8")) {
//Читаем файл построчно до тех пор, пока есть строки
while (scanner.hasNextLine()) {
//Читаем очередную строку из файла
String line = scanner.nextLine();
if(line != null) {
//Распечатываем строку из файла
System.out.println(line);
}
}
}
}
}
Думаете это все? О, нет, конечно не все! Нас впереди ждет еще одна очень увлекательная тема - непотоковая работа с данными.
Информативно.
ОтветитьУдалить