JavaRush /Java блог /Java Developer /Введення-виведення в Java. Класи FileInputStream, FileOut...
Автор
Александр Выпирайленко
Java-разработчик в Toshiba Global Commerce Solutions

Введення-виведення в Java. Класи FileInputStream, FileOutputStream, BufferedInputStream

Стаття з групи Java Developer
Привіт! У сьогоднішній лекції продовжимо розмову про потоки введення і виведення в Java, або скорочено – Java I/O («input-output»). Це не перша лекція на цю тему, і далеко не остання :) Так уже вийшло, що Java як мова надає багато можливостей для роботи з введенням-виведенням. Класів, які реалізують цю функціональність досить багато, тому ми розділили їх на кілька лекцій, щоб ти за самого початку не заплутався :) Введення-виведення в Java. Класи FileInputStream, FileOutputStream, BufferedInputStream - 1У минулих лекціях ми торкнулися BufferedReader'a, а також абстрактних класів InputStream & OutputStream і кількох наслідників. Сьогодні розглянемо 3 нові класи: FileInputStream, FileOutputStream и BufferedInputStream.

Клас FileOutputStream

Головне призначення класу FileOutputStream – запис байтів у файл. Нічого складного :) FileOutputStream є однією з реалізацій абстрактного класу OutputStream. У конструкторі об'єкти цього класу приймають або шлях до цільового файлу (в який і потрібно записати байти), або об'єкт класу File. Розглянемо обидва приклади:

public class Main {

   public static void main(String[] args) throws IOException {


       File file = new File("C:\\Users\\Username\\Desktop\\test.txt");
       FileOutputStream fileOutputStream = new FileOutputStream(file);

       String greetings = "Привіт! Ласкаво просимо на JavaRush – найкращий сайт для тих, хто хоче стати програмістом!";

       fileOutputStream.write(greetings.getBytes());

       fileOutputStream.close();
   }
}
Під час створення об'єкта File ми вказали в конструкторі шлях, де він повинен буде знаходитися. Створювати його заздалегідь немає необхідності: якщо він не існує, програма створить його сама. Можна обійтися і без створення зайвого об'єкта, і просто передати рядок з адресою:

public class Main {

   public static void main(String[] args) throws IOException {


       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt");

       String greetings = "Привіт! Ласкаво просимо на JavaRush – найкращий сайт для тих, хто хоче стати програмістом!";

       fileOutputStream.write(greetings.getBytes());

       fileOutputStream.close();
   }
}
Результат в обох випадках буде однаковим. Ми можемо відкрити наш файл і побачити там:
Привіт! Ласкаво просимо на JavaRush – найкращий сайт для тих, хто хоче стати програмістом!
Однак є тут один нюанс. Спробуй запустити код із прикладу вище кілька разів поспіль, а потім зазирни у файл і дай відповідь на запитання: скільки записаних у нього рядків ти бачиш? Лише один. Але ж ти запускав код кілька разів. Однак у такому разі дані, виявляється, щоразу перезаписувалися, замінюючи старі. Що робити, якщо нас це не влаштовує, і потрібен послідовний запис? Що якщо ми хочемо записати наше привітання у файл три рази поспіль? Тут усе просто. Оскільки сама мова не може знати, яка саме поведінка нам потрібна в кожному випадку, у конструктор FileOutputStream ти можеш передати додатковий параметр – boolean append. Якщо його значення true, дані будуть дописані в кінець файлу. Якщо false (а за замовчуванням це значення і є false), старі дані будуть стерті, а нові записані. Давай перевіримо і запустимо наш змінений код тричі:

public class Main {

   public static void main(String[] args) throws IOException {


       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt", true);

       String greetings = "Привіт! Ласкаво просимо на JavaRush – найкращий сайт для тих, хто хоче стати програмістом!\r\n";

       fileOutputStream.write(greetings.getBytes());

       fileOutputStream.close();
   }
}
Результат у файлі:
Привіт! Ласкаво просимо на JavaRush – найкращий сайт для тих, хто хоче стати програмістом! Привіт! Ласкаво просимо на JavaRush – найкращий сайт для тих, хто хоче стати програмістом! Привіт! Ласкаво просимо на JavaRush – найкращий сайт для тих, хто хоче стати програмістом!
Інша справа! Не забувай про цю особливість під час використання класів введення-виведення. Свого часу і мені доводилося годинами сидіти над завданнями, щоб зрозуміти, куди діваються з файлів мої старі дані :) Ну і звичайно, як і у випадку з іншими класами I/O, не забуваємо про звільнення ресурсів через метод close().

Клас FileInputStream

У класу FileInputStream призначення протилежне – читання байтів із файлу. Так само як FileOutputStream успадковує OutputStream, цей клас походить від абстрактного класу InputStream. Запишемо в наш текстовий «test.txt» декілька рядків тексту:
«So close no matter how far Couldn't be much more from the heart Forever trusting who we are And nothing else matters»
Ось як виглядатиме реалізація читання даних із файлу за допомогою FileInputStream:

public class Main {

   public static void main(String[] args) throws IOException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\test.txt");

       int i;

       while((i=fileInputStream.read())!= -1){

           System.out.print((char)i);
       }
   }
}
Ми зчитуємо з файлу по одному байту, перетворюємо зчитані байти на символи і виводимо їх у консоль. А ось і результат у консолі:
So close no matter how far Couldn't be much more from the heart Forever trusting who we are And nothing else matters

Клас BufferedInputStream

Думаю, з огляду на знання з минулих лекцій, ти легко зможеш сказати, навіщо потрібен клас BufferedInputStream і які переваги він має порівняно з FileInputStream:) Ми вже зустрічалися з буферизованими потоками, тому спробуй припустити (або пригадати), перш ніж продовжити читання :) Буферизовані потоки потрібні насамперед для оптимізації введення-виведення. Звернення до джерела даних, наприклад, читання з файлу, – дорога в плані продуктивності операція. І щоразу звертатися до файлу для читання по одному байту марнотратно. Тому BufferedInputStream зчитує дані не по одному байту, а блоками і тимчасово зберігає їх у спеціальному буфері. Це дає нам змогу оптимізувати роботу програми завдяки тому, що ми зменшуємо кількість звернень до файлу. Давай подивимося, який це має вигляд:

public class Main {

   public static void main(String[] args) throws IOException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\test.txt");

       BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream, 200);

       int i;

       while((i = bufferedInputStream.read())!= -1){

           System.out.print((char)i);
       }
   }
}
Тут ми створили об'єкт BufferedInputStream. Він приймає на вхід об'єкт InputStream або будь-якого його наслідника, тож попередній FileInputStream підійде. Як додатковий параметр він приймає розмір буфера в байтах. Тепер завдяки цьому дані будуть зчитуватися з файлу не по одному байту, а по 200! Уяви, наскільки ми скоротили кількість звернень до файлу. Для порівняння продуктивності ти можеш узяти якийсь великий текстовий файл розміром кілька мегабайт і порівняти, скільки займе його читання і виведення в консоль у мілісекундах з використанням FileInputStream і BufferedInputStream. Ось обидва варіанти коду для прикладу:

public class Main {

   public static void main(String[] args) throws IOException {

       Date date = new Date();

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\textBook.rtf");

       BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);

       int i;

       while((i = bufferedInputStream.read())!= -1){

           System.out.print((char)i);
       }

       Date date1 = new Date();

       System.out.println((date1.getTime() - date.getTime()));
   }
}



public class Main {

   public static void main(String[] args) throws IOException {

       Date date = new Date();

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\\26951280.rtf");

      
       int i;

       while((i = fileInputStream.read())!= -1){

           System.out.print((char)i);
       }

       Date date1 = new Date();

       System.out.println((date1.getTime() - date.getTime()));
   }
}
Під час читання файлу розміром 1,5 Мб на моєму комп'ютері FileInputStream виконав роботу за ~3500 мілісекунд, а ось BufferedInputStream – за ~1700 мілісекунд. Як бачиш, буферизований потік оптимізував роботу програми в 2 рази! :) Ми ще продовжимо вивчати класи введення-виведення – до зустрічі!
Коментарі (3)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Viacheslav B. Рівень 19
8 квітня 2024
гарна стаття, Автору вдалось легко передати основну думку
Sava_crosava Рівень 23
26 жовтня 2023
Закликаю робити актив під статтями українською мовою!!! Підозрюю що статті перекладаються в два кліка через перекладач( Але якщо це буде мати попит та відгук від української спільноти, таких статтей буде більше і скоро буде більше оригінальних статтей українською)🤟
Іван Рівень 71 Expert
30 червня 2023
Просто шикарна стаття!!! "По-людські" все описано, подяка тобі! Чекатиму нових статей від тебе)