Ввод и вывод информации - это то, с чем сталкивается программист на каждом шагу. Это относится как к написанию коммерческих продуктов, так и к решению олимпиадных задач. Сложилось так, что новичку тяжело разобраться в том, как эти действия выполняютя средствами языка Java. В данном тексте я попробую облегчить эту проблему, бегло рассмотрев возможности пакета java.io.

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

Простейший вид потока - тот, который работает с двоичным представением информации, иначе, с байтами. Все бинарные потоки наследуются от двух абстрактных классов - InputStream и OutputStream. Нам важны два потомка - FileInputStream и FileOutputStream соответственно. Вот кусок текста программы, которая копирует содержимое файла a.txt в файл b.txt:

FileInputStream in = null; 
FileOutputStream out = null;
try {
in = new FileInputStream("a.txt");
out = new FileOutputStream("b.txt");
int c;
while ((c = in.read()) != -1) { out.write(c); }
} finally {
if (in != null) { in.close(); }
if (out != null) { out.close(); }
}

Не забывайте закрывать потоки методом close()! Ваши данные могут быть утеряны. Оба класса обладают удобными конструкторами от пути к файлу. Если b.txt не существует, то будет создан. Иначе - переписан. Не забывайте отлавливать исключения (или же указывать их в заголовках методов).

На практике работать с двоичным представлением данных при написании олимпиадных задач не приходится. Небольшим шажком вперед послужат FileReader и FileWriter, которые очень похожи на бинарных братьев. Для нас отличие только в том, что они работают с символами. Новую возможность предоставляют BufferedReader и PrintWriter, которые можно рассматривать, как обертки для уже предыдущей пары классов. Эта возможность - построчное чтение и запись файла. Приведем все тот же пример копирования файла:

BufferedReader in = null; 
PrintWriter out = null;
try {
in = new BufferedReader(new FileReader("a.txt"));
out = new PrintWriter(new FileWriter("b.txt"));
String l;
while ((l = in.readLine()) != null) { out.println(l); }
} finally { ... }

Класс PrintWriter очень близок к стандартным возможностям языка C. Методы print() и println() определены для всех стандартных типов (последний добавляет перевод строки в конец вывода), а метод printf() очень похож на соответсвующую процедуру языка C. За подробностями по теме синтаксиса строки форматирования обращайтесь на соответствующую страницу помощи. BufferedReader радует лишь наличием метода readLine().

Начиная с версии 1.5, язык наконец-то доказал преимущества ООП с точки зрения ввода информации. Это было сязано с появлением класса Scanner в пакете java.util. Для каждого из базовых типов (а также классов длинной арифметики) имеется пара методов: hasNextT() говорит, можно ли далее прочесть элемент типа T, в то время как nextT() этот элемент пытается считать. Методы hasNext() и next() работают отдельными словами (за подробностями обращайтесь к описанию класса). Следующий пример копирует последовательность 32-битных чисел, расположенных в начале файла a.txt в файл b.txt, выписывая их построчно:

Scanner scanner = null; 
PrintWriter writer = null;
try {
scanner = new Scanner(new BufferedReader(new FileReader("a.txt")));
writer = new PrintWriter(new FileWriter("b.txt"));
while (scanner.hasNextInt()) {
writer.println(scanner.nextInt());
}
} finally { ... }

Хотя Scanner и не является потоком, у него тоже обязательно вызывать метод close(), который закроет используемый за основной источник поток. Знатоки скажут, что похожую функциональность предоставляли, начиная с первой версии, DataInputStream и DataOutputStream, однако их возможности беднее, да и работают они не всегда корректно (поэтому пользоваться ими строго не рекомендуется).

Перед тем, как закончить этот обзор, нельзя не упомянуть классе, который решает больную проблему скорости работы Scanner'а. StreamTokenizer создается от наследника класса Reader (примером может служить BufferedReader). Интересующие поля: nval - численное значение последнего прочитанного слова, sval - его представление в качестве строки, ttype - тип этого слова. За тип слова отвечают определенные статические поля (смотрите описание класса). Интересующий нас метод - nextToken().

Остается лишь пожелать успехов и посоветовать иногда заглядывать в документацию вашего любимого языка - вдруг, узнаете что-то полезное! Наконец, привожу таблицу времени считывания (в секундах) чисел типа double с помощью двух рассмотренных методов в зависимости от их количества:

1000 5000 10000 50000 100000 500000 1000000
Scanner 0.069 0.110 0.213 1.062 2.081 10.762 21.210
StreamTokenizer 0.003 0.013 0.025 0.130 0.256 1.247 2.556

Фонарев Антон

Последнее изменение: Суббота, 15 Август 2020, 02:34