У багатьох випадках потрібно виводити результати на принтер, у файл, базу даних або передавати по мережі. Вхідні дані теж часто приходиться завантажувати із файла, бази даних або із мережі. Для того щоб абстрагуватись від особливостей конкретних пристроїв введення/виведення, в Java використовується поняття потоку (stream). Вважається, що у програму іде вхідний поток (input stream) символів Unicode або просто байтів, що сприймається в програмі методами read(). Із програми методами write() або print (), println() виводиться вихідний потік (output stream) символів або байтів. При цьому не має значення куди направлений потік: на консоль, на принтер, у файл або в мережу, методи write() і print() нічого про це не знають.
Можна уявити собі потік як трубу, по якій в одному напрямку послідовно "течуть" символи або байти, один за одним. Методи read() , write() , print(), println() взаємодіють з одним кінцем труби, другий кінець з’єднується з джерелом або приймачем даних - конструкторами класів, в яких реалізовані ці методи. Звичайно, повне ігнорування особливостей пристроїв введення/виведення сильно сповільнює передачу інформації. Тому в Java виділяється файлове введення/виведення, виведення на друк, виведення у мережевий потік.
Три потоки визначені у класі System статичними полями in, out і err. Їх можна використовувати без будь-яких додаткових визначень. Вони називаються відповідно стандартним введенням (stdin), стандартним виведенням (stdout) і стандартним виведенням повідомлень (stderr). Ці стандартні потоки можуть бути зєднані з різними конкретними присторями введення/виведення. Потоки out і err — це екземпляри класу Printstream, який організовує вихідний потік байтів. Ці екземпляри виводять інформацію на консоль методами print(), println() i write(), яких в класі Printstream є близько двадцати для різних типів аргументів.
Потік err призначений для виведення системних повідомлень програми: трасування, повідомлень про помилки або про виконання певних етапів програми. Такі дані звичайно заносяться в спеціальні журнали, log-файли, а не виводяться на консоль. В Java є засоби перепризначення потоку, наприклад, з консолі у файл.
Потік in — це екземпляр класу inputstream. Він призначений для клавіатурного введення з консолі методами read(). Клас inputstream є абстрактним, тому реально використовується хтось із його підкласів.
Поняття потоку виявилось настільки зручним у програмуванні введення/виведення, що в Java передбачена можливістьсть створення потоків, які направляють символи або байти не на зовнішній пристрій, а в масив або із масиву, тобто які зв’язують програму з областю оперативної пам’яті. Більше того, можна створити потік, зв’язаний з рядком типу string, що знаходиться в оперативній памяті. Крім того, можна створити канал (pipe) обміну інформацією між підпроцесами.
Ще один вид потоку — потік байтів, який складає об’єкт Java. Його можна направити у файл або передати по мережі, потім відновити в оперативній пам’яті. Ця операція називається серіалізацією (serialization) об’єктів.
Методи організації потоків зібрані у класи пакета java.io. Крім класів, які організують потік, в пакет java.io входять класи з методами перетворення потоку, наприклад, можна перетворити потік байтів, які утворюють цілі числа, в потік цих чисел. Ще одна можливість, представлена класами пакета java.io, — злити декілька потоків в один потік.
Отже, в Java присутні чотири ієрархії класів для створення, перетворення і злиття потоків, які безпосередньо розширюють клас object:
· Reader — абстрактний клас, в якому зібрані найзагальніші методи символьного введення;
· Writer — абстрактний клас, в якому зібрані найзагальніші методи символьного виведення;
· Inputstream — абстрактний клас з загальними методами байтового введення;
· Outputstream — абстрактний клас з загальними методами байтового виведення.
Класи вхідних потоків Reader і Inputstream визначають по три методи введення:
· read () — повертає один символ або байт, взятий із вхідного потоку, у вигляді цілого значення типу int; якщо потік уже закінчився, повертає -1;
· read (char[] buf) — заповнює заздалегідь визначений масив buf символами із вхідного потоку; в класі inputstream масив типу byte[] і він заповнюється байтами; метод повертає фактичну кількість взятих із потоку елементів або -1, якщо потік уже закінчився;
· read (char[] buf, int offset, int len) — заповнює частину символьного або байтового масиву buf, починаючи з індекса offset, кількість взятих із потоку елементів дорівнює len; метод повертає фактичну кількість взятих із потока елементів або -1.
Ці методи видають IOException, якщо відбулася помилка введення/виведення. Четвертий метод skip (long n) "промотує" потік з поточної позиції на n символів або байтів вперед. Ці елементи потоку не вводяться методами read(). Метод повертає реальну кількість пропущених елементів, яка може відрізнятися від n, наприклад потік може закінчиться. Поточний елемент потоку можна помітити методом mark (int n), а потім повернутися до поміченого елементу методом reset(), але не більше ніж через n елементів. Не всі підкласи реалізують ці методи, тому перед розстановкою поміток треба звернутися до логічного методу marksupported(), який повертає true, якщо реалізовані методи розстановки і повернення до поміток.
Класи вихідних потоків writer і outputstream визначають по три майже однакових методи введення:
· write (char[] buf) — виводить масив у вихідний потік, в класі Outputstream масив має тип byte[];
· write (char[] buf, int offset, int len) — виводить len елементів масиву buf, починаючи з злемента із індексом offset;
· write (int elem) в класі Writer - виводить 16, а в класі Outputstream 8 молодших бітів аргумента elem у вихідний потік.
У класі Writer присутні ще два методи:
· write (string s) — виводить рядок s у вихідний потік;
· write (String s, int offset, int len) — виводить len символів рядка s, починаючи із символа з номером offset.
Багато підкласів класів Writer і Outputstream здійснюють буферизовае виведення. При цьому елементи спочатку нагромаджуються у буфері, в оперативній пам’яті, і виводяться у вихідний потік тільки після того, як буфер заповниться. Це зручно для вирівнювання швидкостей виведення із програми і виведення потоку, але часто потрібно вивести інформацію у потік ще до заповнення буферу. Для цього передбачений метод flush(). Даний метод зразу ж виводить весь вміст буфера у потік. По завершені роботи з потоком його необхідно закрити методом closed. Класи, що входять в ієрархії потоків введення/виведення, показані на рис. 18.1 и 18.2.
Рис. 1.1. Ієрархія символьних потоків
Рис. 1.2. Класи байтових потоків
Всі класи пакета java.io можна розділити на дві групи: класи, що створюють потік (data sink), і класи, що керують потоком (data processing). Класи, що створюють потоки, у свою чергу, можна розділити на пять груп:
· класи, що створюють символьні потоки, звя’зані з рядком:
StringReader, StringWriter
· класи, що створюють байтові потоки із об’єктів Java:
ObjectlnputStream, ObjectOutputStream
Зліва перераховані класи символьних потоків, справа — класи байтових потокив. Класи, які керують потоком, отримують у своїх конструкторах уже наявний потік і створюють новий, перетворений потік. Можна собі їх уявляти як "перехідне кільце", після якого іде труба іншого діаметру. Чотири класи створені спеціально для перетворення потоків:
Самі по собі ці класи не мають жодної користі — вони виконують тотожне перетворення. Їх портрібно розширювати, перевизначаючи методи введення/виведення. Але для байтових фільтрів є корисні розширення, яким відповідають деякі символьні класи. Перерахуємо їх.
· Чотири класи виконують буферизоване введення/виведення: BufferedReader, BufferedlnputStream, BufferedWriter, BufferedOutputStream
· Два класи перетворюють потік байтів, які утворюють вісім простих типів Java, у ці самі типи: DatalnputStream, DataOutputStream
· Два класи містять методи, які дозволяють повернути декілька символів або байтів у вхідний потік: PushbackReader, PushbacklnputStream
· Два класи пов’язані з виведенням на рядкові пристрої — екран дисплея, принтер: PrintWriter, PrintStream
· Два класса зв’язують байтовий і символьний потоки:
o InputstreamReader — перетворює вхідний байтовий потік у символьний потік;
o Outputstreamwriter — перетворює вихідний символьний потік у байтовий потік.
· Клас streamTokenizer дозволяє розібрати вхідний символьний потік на окремі елементи (tokens).
· Із керуючих класів виділяється клас sequenceinputstream, який зливає декілька потоків, заданих у конструкторі, в один потік, і клас LineNumberReader, який "вміє" читати вихідний символьний потік порядково. Рядки у потоці розділяються символами '\n' і/або '\г'.
Тепер перейдемо до розгляду реальних ситуацій.
Варіант 15:
Створити програму для підрахунку частоти повторення заданого слова в текстовому файлі з використанням класу StreamTokenizer. Компоненти графічного вікна: напис "Пошук кількості повторень слова в текстовому файлі" у області North, у області Center розміщений об'єкт класу JFileChooser, у області South розміщені напис "Слово пошуку:", текстове поле для введення слова пошуку, прапорець з написом "Облік регістра", кнопка "Пошук", напис "Частота повторення:" і текстове поле для виведення частоти повторення слова. рядків. Виведення частоти повторення слова виконується при натисненні кнопки.
Під час виконання лабораторної роботи я одержав навики роботи із потоками вводу виводу, ознайомився з принципами роботи StreamTokenizer та написав програму для підрахунку частоти повторення заданого слова в текстовому файлі з використанням класу StreamTokenizer.