You are on page 1of 10

Потоки введення-виведення

В Java основний функціонал роботи з потоками зосереджений в класах з пакета


java.io.
Потік пов'язаний з реальним фізичним пристроєм за допомогою системи
введення-виведення Java. Може бути визначений потік, який пов'язаний з файлом
і через який можна вести читання або запис файлу. Це також може бути потік,
пов'язаний з мережевим сокетом, за допомогою якого можна отримати або
відправити дані в мережі.
В основі всіх класів, що керують потоками байтів, знаходяться два абстрактних
класи: InputStream (представляє потоки введення) і OutputStream (представляє
потоки виведення)
Для роботи з потоками символів існують абстрактні класи Reader (для читання
потоків символів) і Writer (для запису потоків символів).
Інші класи потоків є спадкоємцями цих абстрактних класів.
Потоки байтів
Клас InputStream
Клас InputStream є базовим для всіх класів, що керують байтовими потоками
введення. Основні методи:
 int available(): повертає кількість байтів, доступних для читання в потоці
 void close(): закриває потік
 int read(): повертає цілочисельне значення наступного байта в потоці. Коли
в потоці не залишиться доступних для читання байтів, даний метод
поверне число -1.
 int read(byte[ ] buffer): зчитує байти з потоку в масив buffer. Після читання
повертає число зчитаних байтів. Якщо жодного байта не було зчитано, то
повертається число -1
 int read(byte[ ] buffer, int offset, int length): зчитує length байтів з потоку в
масив buffer. При цьому зчітані байти поміщаються в масиві, починаючи зі
зсуву offset, тобто з елемента buffer[offset]. Метод повертає число успішно
прочитаних байтів.
 long skip(long number): пропускає в потоці при читанні кількість байт, що
дорівнює number
Клас OutputStream
Клас OutputStream є базовим класом для всіх класів, які працюють з бінарними
потоками запису. Свою функціональність він реалізує через такі методи:
 void close(): закриває потік
 void flush(): очищає буфер виведення, записуючи все його вміст
 void write(int b): записує у вихідний потік один байт, який представлений
цілочисельним параметром b
 void write(byte[ ] buffer): записує у вихідний потік масив байтів buffer.
 void write(byte[ ] buffer, int offset, int length): записує у вихідний потік
кількість байтів, що дорівнює length, з масиву buffer, починаючи зі зсуву
offset, тобто з елемента buffer[offset].

Потоки символів. Абстрактні класи Reader і Writer


Абстрактний клас Reader надає функціонал для читання текстової інформації.
Розглянемо його основні методи:
 absract void close(): закриває потік введення
 int read(): повертає цілочисельне уявлення наступного символу в потоці.
Якщо таких символів немає, і досягнутий кінець файлу, то повертається
число -1
 int read(char[ ] buffer): зчитує в масив buffer з потоку символи, кількість
яких дорівнює довжині масиву buffer. Повертає кількість успішно
прочитаних символів. При досягненні кінця файлу повертає -1
 int read(CharBuffer buffer): зчитує в об'єкт CharBuffer з потоку символи.
Повертає кількість успішно лічених символів. При досягненні кінця файлу
повертає -1
 absract int read(char[ ] buffer, int offset, int count): зчитує в масив buffer,
починаючи зі зсуву offset, з потоку символи, кількість яких одно count
 long skip(long count): пропускає кількість символів, рівна count. Повертає
число успішно пропущених символів
Клас Writer визначає функціонал для всіх символьних потоків виведення. Його
основні методи:
 Writer append(char c): додає в кінець вихідного потоку символ c. Повертає
об'єкт Writer
 Writer append(CharSequence chars): додає в кінець вихідного потоку набір
символів chars. Повертає об'єкт Writer
 abstract void close(): закриває потік
 abstract void flush(): очищає буфери потоку
 void write(int c): записує в потік один символ
 void write(char[ ] buffer): записує в потік масив символів
 absract void write(char[ ] buffer, int off, int len): записує в потік тільки кілька
символів з масиву buffer. Причому кількість символів дорівнює len, а відбір
символів з масиву починається з індексу off
 void write(String str): записує в потік рядок
 void write(String str, int off, int len): записує в потік з рядка деяку кількість
символів, що дорівнює len, причому відбір символів з рядка починається з
індексу off
Функціонал, описаний класами Reader і Writer, успадковується безпосередньо
класами символьних потоків, зокрема класами FileReader і FileWriter відповідно,
призначеними для роботи з текстовими файлами.
Читання і запис файлів. FileInputStream і FileOutputStream
Запис файлів і клас FileOutputStream
Клас FileOutputStream призначений для запису байтів в файл, похідний від класу
OutputStream.
Через конструктор класу FileOutputStream задається файл:
FileOutputStream(String filePath)
FileOutputStream(File fileObj)
FileOutputStream(String filePath, boolean append)
FileOutputStream(File fileObj, boolean append)
Файл задається або через шлях до файлу у вигляду рядка (строки), або через
об'єкт File. Другий параметр - append задає спосіб запису: true - дані
дозаписуються в кінець файлу, false - файл повністю перезаписується
Приклад 1
Наприклад, запишемо в файл рядок:
import java.io. *;
public class Program {
public static void main(String[] args) {
String text = "Hello world!"; //рядок для запису
try (FileOutputStream fos = new
FileOutputStream("C://SomeDir//notes.txt")) {
byte[] buffer = text.getBytes();//новий рядок в байти
fos.write(buffer, 0, buffer.length);
}
catch(IOException ex){ System.out.println(ex.getMessage());}
System.out.println ("The file has been written");
}
}
Для створення об'єкта FileOutputStream використовується конструктор, що
приймає як параметр шлях до файлу. Якщо такого файлу немає, то він
автоматично створюється при записі. Так як тут записуємо рядок, то його треба
спочатку перевести в масив байтів. І за допомогою методу write рядок записується
в файл.
Для автоматичного закриття файлу і звільнення ресурсу об'єкт FileOutputStream
створюється за допомогою спеціальної форми конструкції try ... catch.
Необов'язково записувати весь масив байтів. Використовуючи перевантажений
метод write(), можна записати і одиночний байт:
fos.write(buffer[0]); // запис першого байту

Читання файлів і клас FileInputStream


Для зчитування даних з файлу призначений клас FileInputStream, який є
спадкоємцем класу InputStream.
Для створення об'єкта FileInputStream ми можемо використовувати ряд
конструкторів. Найбільш використовувана версія конструктора в якості параметра
приймає шлях до файлу:
FileInputStream(String fileName) throws FileNotFoundException
Якщо файл не може бути відкритий, наприклад, за вказаним шляхом такого файлу
не існує, то генерується виключення FileNotFoundException.
Приклад 2
Зчитаємо дані з раніше записаного файлу і виведемо на консоль:
import java.io.*;
public class Program {
  public static void main(String[] args) {
    try(FileInputStream fin=new
FileInputStream("C://SomeDir//notes.txt")) {
     System.out.printf("File size:%d bytes\n",fin.available());
     int i=-1;
     while((i=fin.read())!=-1){ System.out.print((char)i); }  
    }
    catch(IOException ex){
      System.out.println(ex.getMessage());
    }
  }
}
В даному випадку ми зчитуємо кожен окремий байт в змінну i:
while ( (i = fin.read()) != -1) {
Коли в потоці більше немає даних для читання, метод read() повертає число -1.
Потім кожен зчитаний байт конвертується в об'єкт типу char і виводиться на
консоль.
Подібним чином можна зчитати дані в масив байтів і потім робити з ним
маніпуляції:
Приклад 2а
byte[] buffer = new byte[fin.available()];
fin.read(buffer,0,fin.available());//читаємо файл в буфер
System.out.println("File data:");
for (int i = 0; i < buffer.length; i++) {
System.out.print((char) buffer[i]);
}
Приклад 2б
Сумістимо попередні приклади і виконаємо читання з одного і запис в інший файл:
import java.io.*;
public class Program {
public static void main(String[] args) {
try( FileInputStream fin = new
FileInputStream("C://SomeDir//notes.txt");
FileOutputStream fos = new
FileOutputStream("C://SomeDir//notes_new.txt") ) {
byte[] buffer = new byte[fin.available()];
fin.read(buffer,0,buffer.length); // зчитуємо в буфер
fos.write(buffer,0,buffer.length);//пишемо з буфера в файл
}
catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}
Класи FileInputStream і FileOutputStream призначені насамперед для запису
двійкових файлів, тобто для запису і читання байтів. І хоча вони також можуть
використовуватися для роботи з текстовими файлами, але все ж для цього
завдання більше підходять інші класи.
Закриття потоків
При завершенні роботи з потоком його треба закрити за допомогою методу
close(), який визначений в інтерфейсі Closeable. Метод close має наступне
визначення в інтерфейсі:
void close() throws IOException
Цей інтерфейс вже реалізується в класах InputStream і OutputStream, а через них
і в усіх класах потоків.
При закритті потоку звільняються всі виділені для нього ресурси, наприклад, файл.
У разі, якщо потік виявиться не закритим, може відбуватися витік пам'яті.
Є два способи закриття файлу. Перший традиційний полягає в використанні блоку
try..catch..finally. Наприклад, читаємо дані з файлу:
Приклад 3
import java.io. *;
public class Program {
public static void main(String[] args) {
FileInputStream fin = null;
try {
fin = new FileInputStream("C://SomeDir//notes.txt");
int i = -1;
while ( (i = fin.read()) != -1 ) {
System.out.print ((char)i);
}
}
catch (IOException ex) {
System.out.println (ex.getMessage());
}
finally {
try {
if (fin! = null) fin.close();
}
catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}
}
Оскільки при відкритті або зчитуванні файлу може статися помилка введення-
виведення, то код зчитування поміщається в блок try. І щоб бути впевненим, що
потік в будь-якому випадку закриється, навіть якщо при роботі з ним виникне
помилка, виклик методу close() поміщається в блок finally. І, так як метод close()
також в разі помилки може генерувати виключення IOException, то його виклик
також поміщається у вкладений блок try..catch
Починаючи з Java 7 можна використовувати ще один спосіб, який автоматично
викликає метод close. Цей спосіб полягає у використанні конструкції try-with-
resources (try-з-ресурсами). Дана конструкція працює з об'єктами, які реалізують
інтерфейс AutoCloseable. Так як всі класи потоків реалізують інтерфейс
Closeable, який в свою чергу успадковується від AutoCloseable, то їх також можна
використовувати в даній конструкції
Отже, перепишемо попередній приклад з використанням конструкції try-with-
resources:
Приклад 3а
import java.io.*;
public class Program {
  public static void main(String[] args) {
    try(FileInputStream fin=new
FileInputStream("C://SomeDir//notes.txt")) {
      int i=-1;
      while((i=fin.read())!=-1){ System.out.print((char)i); }  
    }
    catch(IOException ex){System.out.println(ex.getMessage());}
  }
}
Синтаксис конструкції наступний:
try (назва_класа ім'я_змінної = конструктор_класса).
Дана конструкція також не виключає використання блоків catch.
Після закінчення роботи в блоці try у ресурса (в даному випадку у об'єкта
FileInputStream) автоматично викликається метод close().
Якщо нам треба використовувати кілька потоків, які після виконання треба
закрити, то ми можемо вказати об'єкти потоків через крапку з комою:
try(FileInputStream fin=new
FileInputStream("C://SomeDir//Hello.txt");
    FileOutputStream fos = new
FileOutputStream("C://SomeDir//Hello2.txt")) {
    //..................
}
Класи ByteArrayInputStream і ByteArrayOutputStream
Для роботи з масивами байтів - їх читання і запису використовуються класи
ByteArrayInputStream і ByteArrayOutputStream.
Читання масиву байтів і клас ByteArrayInputStream
Клас ByteArrayInputStream представляє вхідний потік, який використовує в якості
джерела даних масив байтів. Він має наступні конструктори:
ByteArrayInputStream(byte[] buf)
ByteArrayInputStream(byte[] buf, int offset, int length)
Як параметри конструктори використовують масив байтів buf, з якого проводиться
зчитування, зміщення відносно початку масиву offset і кількість зчитувальних
байтів length.
Зчитаємо масив байтів і виведемо його на екран:
Приклад 4
import java.io.*;
public class Program {
public static void main(String[] args) {
byte[] array1 = new byte[] {1, 3, 5, 7};
ByteArrayInputStream byteStream1 = new
ByteArrayInputStream(array1);
int b;
while ( (b = byteStream1.read()) != -1) {
System.out.println(b);
}
String text = "Hello world!";
byte[] array2 = text.getBytes();
// зчитуємо 5 символів
ByteArrayInputStream byteStream2 =
new ByteArrayInputStream(array2, 0, 5);
int c;
while ((c = byteStream2.read())! = - 1) {
System.out.println((char)c);
}
}
}
На відміну від інших класів потоків для закриття об'єкта ByteArrayInputStream не
потрібно викликати метод close.

Запис масиву байт і клас ByteArrayOutputStream


Клас ByteArrayOutputStream представляє потік виведення, який використовує
масив байтів в якості місця виведення.
Щоб створити об'єкт даного класу, ми можемо використовувати один з його
конструкторів:
ByteArrayOutputStream()
ByteArrayOutputStream(int size)
Перша версія створює масив для зберігання байтів довжиною в 32 байта, а друга
версія створює масив довжиною size.
Розглянемо застосування класу:
Приклад 5
import java.io. *;
public class Program {
public static void main (String [] args) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String text = "Hello Wolrd!";
byte[] buffer = text.getBytes();
try {
baos.write(buffer);
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}
// перетворюємо масив байтів в рядок
System.out.println(baos.toString());
// отримуємо масив байтів і виводимо посимвольно
byte[] array = baos.toByteArray();
for (byte b: array) {
System.out.print((char)b);
}
System.out.println();
}
}
Як і в інших потоках виведення в класі ByteArrayOutputStream визначено метод
write, який записує в потік дані. В даному випадку ми записуємо в потік масив
байтів. Цей масив байтів записується в об'єкті ByteArrayOutputStream в захищене
поле buf, яке представляє собою масив байтів (protected byte[ ] buf).
Так як метод write може згенерувати виключення, то виклик цього методу
поміщається в блок try..catch.
Використовуючи методи toString() і toByteArray(), можна отримати масив байтів
buf у вигляді тексту або безпосередньо у вигляді масиву байт.
За допомогою методу writeTo ми можемо вивести масив байт із об’єкта
ByteArrayOutputStream в інший потік. Метод в якості параметра приймає об'єкт
OutputStream, в який проводиться запис масиву байт:
Приклад 5а
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String text = "Hello Wolrd!";
byte[] buffer = text.getBytes();
try{
    baos.write(buffer);
}
catch(Exception ex){
    System.out.println(ex.getMessage());
}
try(FileOutputStream fos = new FileOutputStream("hello.txt")){
    baos.writeTo(fos);
}
catch(IOException e){
    System.out.println(e.getMessage());
}
Після виконання цієї програми в папці з програмою з'явиться файл hello.txt, який
буде містити рядок "Hello Wolrd!".
На закінчення також треба сказати, що як і для об'єктів ByteArrayInputStream, для
ByteArrayOutputStream не треба явно закривати потік за допомогою методу
close.
Буферізовані потоки BufferedInputStream і BufferedOutputStream
Для оптимізації операцій введення-виведення використовуються буферизовані
потоки. Ці потоки додають до стандартних спеціальний буфер в пам'яті, за
допомогою якого підвищується продуктивність при читанні і запису потоків.
Клас BufferedInputStream
Клас BufferedInputStream накопичує дані, що вводяться в спеціальному буфері
без постійного звернення до пристрою введення. Клас BufferedInputStream
визначає два конструктора:
BufferedInputStream(InputStream inputStream)
BufferedInputStream(InputStream inputStream, int bufSize)
Перший параметр - це потік введення, з якого дані будуть зчитуватися в буфер.
Другий параметр - розмір буфера.
Наприклад, буферизуємо зчитування даних з потоку ByteArrayInputStream:
Приклад 6
import java.io. *;
public class Program {
public static void main (String [] args) {
String text = "Hello world!";
byte[] buffer = text.getBytes();
ByteArrayInputStream in = new ByteArrayInputStream(buffer);
try (BufferedInputStream bis = new BufferedInputStream(in)){
int c;
while ((c=bis.read()) != -1){System.out.print((char)c);}
}
catch (Exception e){System.out.println (e.getMessage());}
System.out.println();
}
}
Клас BufferedInputStream в конструкторі приймає об'єкт InputStream. В даному
випадку таким об'єктом є екземпляр класу ByteArrayInputStream.
Як і всі потоки введення BufferedInputStream має метод read(), який зчитує дані. І
тут ми зчитуємо за допомогою методу read кожен байт з масиву buffer.
Фактичні все те ж саме можна було зробити і за допомогою одного
ByteArrayInputStream, не вдаючись до буферизованого потоку. Клас
BufferedInputStream просто оптимізує продуктивність при роботі з потоком
ByteArrayInputStream. Природно замість ByteArrayInputStream може
використовуватися будь-який інший клас, який успадкований від InputStream.
Клас BufferedOutputStream
Клас BufferedOutputStream аналогічно створює буфер для потоків виведення.
Цей буфер накопичує байти, що виводяться, без постійного звернення до
пристрою. І коли буфер заповнений, проводиться запис даних.
BufferedOutputStream визначає два конструктора:
BufferedOutputStream(OutputStream outputStream)
BufferedOutputStream(OutputStream outputStream, int bufSize)
Перший параметр - це потік виводу, який успадкований від OutputStream, а другий
параметр - розмір буфера.
Розглянемо на прикладі запису в файл:
Приклад 7
import java.io.*;
public class Program {
public static void main (String [] args) {
String text = "Hello world!"; // рядок для запису
try (FileOutputStream out = new
FileOutputStream("notes.txt");
BufferedOutputStream bos = new
BufferedOutputStream(out)) {
//новий рядок в байти
byte[] buffer = text.getBytes();
bos.write(buffer, 0, buffer.length);
}
catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
}
Клас BufferedOutputStream в конструкторі приймає як параметр об'єкт
OutputStream - в даному випадку це файловий потік виведення FileOutputStream.
І також робиться запис в файл. Знову ж BufferedOutputStream не додає багато
нових функцій, він просто оптимізує дію потоку виведення.

You might also like