Professor Hans Noodles
41 уровень

Ввод-вывод в Java. Классы FileInputStream, FileOutputStream, BufferedInputStream

Статья из группы Java Developer
Привет! В сегодняшней лекции продолжим разговор о потоках ввода и вывода в Java, или сокращенно — Java I/O («input-output»). Это не первая лекция на данную тему, и далеко не последняя :) Ввод-вывод в Java. Классы FileInputStream, FileOutputStream, BufferedInputStream - 1Так уж получилось, что Java как язык предоставляет много возможностей по работе с вводом-выводом. Классов, которые реализуют эту функциональность довольно много, поэтому мы разделили их на несколько лекций, чтобы ты поначалу не запутался :) В прошлых лекциях мы коснулись 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»
Ввод-вывод в Java. Классы FileInputStream, FileOutputStream, BufferedInputStream - 2 Вот как будет выглядеть реализация чтения данных из файла при помощи 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 раза! :) Мы еще продолжим изучать классы ввода-вывода — до встречи!
Комментарии (233)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Пахопол Юлия Уровень 28, Молдова
29 августа 2022
объясните плз эту строку while((i = fileInputStream.read())!= -1){ зачем нужен -1
Алексей Уровень 30, Санкт-Петербург, Россия
26 августа 2022
Такс, давайте проясним кое какой момент. Класс FileOutputStream, как и FileInputStream не будут работать со строками, а точнее с чарами, они же только для цифр, а точнее байт!!! Для этих целей нужно использовать FileReader и FileWriter. То есть код из первых примеров не будет работать!
Зета Медведя Уровень 33, Санкт-Петербург, Russian Federation
24 августа 2022
Почему эта лекция в дополнительных? Мы же как раз работали с этими классами в задачах, вот перед ними и нужно было подробно объяснить.
Гофф Уровень 20 Master
20 августа 2022
Разница между операциями без буфера и операциями с буфером значительно больше, чем в 2-2,5 раза. Она может превышать тысячу раз! Так что буфер надо использовать всегда. Я использовал другой код, который прогоняет один и тот же файл с разными размерами буфера по три раза. В тесте используется только FileInputStream.read(buffer), меняется лишь размер этого buffer. Тестировал на SSD и на 5-м рейде из трёх дисков, только с чтением файла или с чтением-записью в другой файл. Тестировал на бинарном (mp3, АИГЕЛ "Пыяла") файле размером в 11 мегабайт. Код теста:

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

    String pathToFile = "/tmp/test"; // файл на  SSD
//  String pathToFile = "/home/goff/Документы/temp/test"; // Файл на RAID5

    int[] buffSize = {1048576, 1024, 256, 32, 16, 4, 2, 1}; // размеры тестируемых буферов ;-)

    for (int i = 0; i < buffSize.length; i++) {

        byte[] buffer = new byte[buffSize[i]];

        for (int j = 0; j < 3; j++) { // по три раза на каждый буфер

            FileInputStream fileInputStream = new FileInputStream(pathToFile);
            FileOutputStream fileOutputStream = new FileOutputStream(pathToFile + ".out");

            Date dateStart = new Date();
            while (fileInputStream.available() > 0) { // время вызова метода available протестил, оно  нулевое и не влияет на результат
                int readBytes = fileInputStream.read(buffer);
                fileOutputStream.write(buffer, 0, readBytes); // эта строка комментилась для without output
            }
            Date dateStop = new Date();
            System.out.println(buffer.length + "   " + (dateStop.getTime() - dateStart.getTime()));

            fileInputStream.close();
            fileOutputStream.close();

        }
    }
}
Яков Мануилов Уровень 36, Москва, Russian Federation
14 августа 2022
Еще бы в лекциях так объясняли цены вам не было) А то сижу такой: Баффер - это же топовый инвестор, значит очень эффективно считает 😁
Niko Myan Уровень 19, Yerevan, Армения
4 августа 2022
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\textBook.rtf"); FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\textBook.rtf",200); я один момент не понял,если писать цифр значит он каждый раз будет читать сразу так много byte (например 200). а если не писать никакой цифр ? тогда он будет считать 1 байт каждый раз или будет читать все байти файла
Sergey Paleny Уровень 27, Ставрополь, Россия
25 июля 2022
"...поэтому мы разделили их на несколько лекций, чтобы ты поначалу не запутался :)" Я не запутался, у меня просто каша в голове пока 😀
Ripgor Уровень 2, Новосибирск, Russian Federation
8 июля 2022
А какой максимальный размер буфера? Ограничивается оперативной памятью? А если я эти 1,5 Мб ему так и скормлю в байтах? И есть ли какой-то "универсальный" размер, который чаще всего используют в работе с большими файлами? Так сказать "Стандарт" наряду с, например, Java Code Style? UPD: Про размер буфера по умолчанию нашел в комментах, он равен 8192 б если кто вопросом задался
Матвей Маслобоев Уровень 3, Санкт-Петербург, Russian Federation
20 июня 2022
Блин спасибо чел, очень круто обьяснил!
Юлия Уровень 20, Минск, Belarus
6 июня 2022
Весь 16ур в задачах было: не используйте классы File, FileInputStream, FileOutputStream, по тексту лекций было, что класс File устарел. Доп.лекция про них от 2018 г. Так используются они сейчас или уже устарели? Есть ли смысл в этой доп.лекции? Что-то я уже запуталась :(