1. Java IO

Отличительной чертой многих языков программирования является работа с файлами и потоками. В Java основной функционал работы с потоками сосредоточен в классах из пакета java.io.

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

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

Java IO

Объект, из которого можно считать данные, называется потоком ввода, а объект, в который можно записывать данные, - потоком вывода. Например, если надо считать содержание файла, то применяется поток ввода, а если надо записать в файл - то поток вывода.

В основе всех классов, управляющих потоками байтов, находятся два абстрактных класса: InputStream (представляющий потоки ввода) и OutputStream (представляющий потоки вывода)

Но поскольку работать с байтами не очень удобно, то для работы с потоками символов были добавлены абстрактные классы Reader (для чтения потоков символов) и Writer (для записи потоков символов).

Все остальные классы, работающие с потоками, являются наследниками этих абстрактных классов. Основные классы потоков:

Byte Based:

Type

Input

Output

Basic

InputStream

OutputStream

Arrays

ByteArrayInputStream

ByteArrayOutputStream

Files

FileInputStream

FileOutputStream

Files

RandomAccessFile

RandomAccessFile

Pipes

PipedInputStream

PipedOutputStream

Buffering

BufferedInputStream

BufferedOutputStream

Filtering

FilterInputStream

FilterOutputStream

Parsing

PushbackInputStream

Parsing

StreamTokenizer

Data

DataInputStream

DataOutputStream

Data-Formatted

PrintStream

Objects

ObjectInputStream

ObjectOutputStream

Utilities

SequenceInputStream

Character Based:

Type

Input

Output

Basic

Reader

Writer

Basic

InputStreamReader

OutputStreamWriter

Arrays

CharArrayReader

CharArrayWriter

Files

FileReader

FileWriter

Pipes

PipedReader

PipedWriter

Buffering

BufferedReader

BufferedWriter

Filtering

FilterReader

FilterWriter

Parsing

PushbackReader

Parsing

LineNumberReader

Strings

StringReader

StringWriter

Data-Formatted

PrintWriter

1.1. Абстрактный класс 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

1.2. Абстрактный класс 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]

1.2.1. Абстрактный класс Reader

Абстрактный класс 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. Возвращает число успешно пропущенных символов

1.3. Абстрактный класс Writer

Класс 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

2. Интерфейс Closeable

При завершении работы с потоком его надо закрыть с помощью метода close(), который определен в интерфейсе Closeable. Метод close имеет следующее определение:

void close() throws IOException

Этот интерфейс уже реализуется в классах InputStream и OutputStream, а через них и во всех классах потоков.

При закрытии потока освобождаются все выделенные для него ресурсы, например, файл. В случае, если поток окажется не закрыт, может происходить утечка памяти.

Есть два способа закрытия файла:

  • использование блока try..catch..finally

  • использование конструкции try-with-resource

Используем первый (традиционный) способ и считаем данные из файла:

import java.io.FileInputStream;
import java.io.IOException;

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:

import java.io.FileInputStream;
import java.io.IOException;

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(название_класса имя_переменной = конструктор_класса) {
    // code
}

Данная конструкция также не исключает использования блоков catch.

После окончания работы в блоке try у ресурса (в данном случае у объекта FileInputStream) автоматически вызывается метод close().

Если нам надо использовать несколько потоков, которые после выполнения надо закрыть, то мы можем указать объекты потоков через точку с запятой:

try (FileInputStream fin = new FileInputStream("C://SomeDir//Hello.txt");
        FileOutputStream fos = new FileOutputStream("C://SomeDir//Hello2.txt")) {
}

3. Класс File

Класс File, определенный в пакете java.io, не работает напрямую с потоками. Его задачей является управление информацией о файлах и директориях. Хотя на уровне операционной системы файлы и директории отличаются, но в Java они описываются одним классом File.

В зависимости от того, что должен представлять объект File - файл или директория, мы можем использовать один из конструкторов для создания объекта:

  • File(String путь_к_директории)

  • File(String путь_к_директории, String имя_файла)

  • File(File каталог, String имя_файла)

Например:

// создаем объект File для директории
File dir1 = new File("C://SomeDir");
// создаем объекты для файлов, которые находятся в директории
File file1 = new File("C://SomeDir", "Hello.txt");
File file2 = new File(dir1, "Hello2.txt");

Класс File имеет ряд методов, которые позволяют управлять файлами и директориями. Рассмотрим некоторые из них:

  • boolean createNewFile() создает новый файл по пути, который передан в конструктор. В случае удачного создания возвращает true, иначе false

  • boolean delete() удаляет директорию или файл по пути, который передан в конструктор. При удачном удалении возвращает true

  • boolean exists() проверяет, существует ли по указанному в конструкторе пути файл или директория. И если файл или директория существует, то возвращает true, иначе возвращает false

  • String getAbsolutePath() возвращает абсолютный путь для пути, переданного в конструктор объекта

  • String getName() возвращает краткое имя файла или директории

  • String getParent() возвращает имя родительской директории

  • boolean isDirectory() возвращает значение true, если по указанному пути располагается директория

  • boolean isFile() возвращает значение true, если по указанному пути находится файл

  • boolean isHidden() возвращает значение true, если директория или файл являются скрытыми

  • long length() возвращает размер файла в байтах

  • long lastModified() возвращает время последнего изменения файла или директории. Значение представляет количество миллисекунд, прошедших с начала эпохи Unix

  • String[] list() возвращает массив файлов и поддиректорий, которые находятся в определенной директории

  • File[] listFiles() возвращает массив файлов и поддиректорий, которые находятся в определенной директории

  • boolean mkdir() создает новую директорию и при удачном создании возвращает значение true

  • boolean renameTo(File dest) переименовывает файл или директорию

3.1. Работа с директориями

Если объект File представляет директорию, то его метод isDirectory() возвращает true. И поэтому мы можем получить ее содержимое - вложенные поддиректории и файлы с помощью методов list() и listFiles(). Получим все поддиректории и файлы в определенной директории:

import java.io.File;

public class Program {
    public static void main(String[] args) {
        // определяем объект для каталога
        File dir = new File("C://SomeDir");
        // если объект представляет каталог
        if (dir.isDirectory()) {
            // получаем все вложенные объекты в каталоге
            for (File item : dir.listFiles()) {
                if (item.isDirectory()) {
                    System.out.println(item.getName() + "  \t folder");
                } else {
                    System.out.println(item.getName() + "\t file");
                }
            }
        }
    }
}

Теперь выполним еще ряд операций с директорией, как удаление, переименование и создание:

import java.io.File;

public class Program {
    public static void main(String[] args) {
        // определяем объект для каталога
        File dir = new File("C://SomeDir//NewDir");
        boolean created = dir.mkdir();
        if (created) {
            System.out.println("Folder has been created");
        }
        // переименуем каталог
        File newDir = new File("C://SomeDir//NewDirRenamed");
        dir.renameTo(newDir);
        // удалим каталог
        boolean deleted = newDir.delete();
        if (deleted) {
            System.out.println("Folder has been deleted");
        }
    }
}

3.2. Работа с файлами

Работа с файлами аналогична работе с директориями. Например, получим данные по одному из файлов и создадим еще один файл:

import java.io.File;
import java.io.IOException;

public class Program {
    public static void main(String[] args) {
        // определяем объект для каталога
        File myFile = new File("C://SomeDir//notes.txt");
        System.out.println("File name: " + myFile.getName());
        System.out.println("Parent folder: " + myFile.getParent());
        if (myFile.exists()) {
            System.out.println("File exists");
        } else {
            System.out.println("File not found");
        }

        System.out.println("File size: " + myFile.length());
        if (myFile.canRead()) {
            System.out.println("File can be read");
        } else {
            System.out.println("File can not be read");
        }

        if (myFile.canWrite()) {
            System.out.println("File can be written");
        } else {
            System.out.println("File can not be written");
        }
        // создадим новый файл
        File newFile = new File("C://SomeDir//MyFile");
        try {
            boolean created = newFile.createNewFile();
            if (created) {
                System.out.println("File has been created");
            }
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

При создании нового файла метод createNewFile() в случае неудачи выбрасывает исключение IOException, поэтому нам надо его отлавливать, например, в блоке try…​catch, как делается в примере выше.

4. Классы FileOutputStream и FileInputStream

4.1. Запись файлов и класс FileOutputStream

Класс FileOutputStream предназначен для записи байтов в файл. Он является производным от класса OutputStream, поэтому наследует всю его функциональность.

Через конструктор класса FileOutputStream задается файл, в который производится запись. Класс поддерживает несколько конструкторов:

  • FileOutputStream(String filePath)

  • FileOutputStream(File fileObj)

  • FileOutputStream(String filePath, boolean append)

  • FileOutputStream(File fileObj, boolean append)

Файл задается либо через строковый путь, либо через объект File. Второй параметр - append задает способ записи: eсли он равен true, то данные дозаписываются в конец файла, а при false - файл полностью перезаписывается

Например, запишем в файл строку:

import java.io.FileOutputStream;
import java.io.IOException;

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]); // запись первого байта

4.2. Чтение файлов и класс FileInputStream

Для считывания данных из файла предназначен класс FileInputStream, который является наследником класса InputStream и поэтому реализует все его методы.

Для создания объекта FileInputStream мы можем использовать ряд конструкторов:

  • FileInputStream(File file) открывает соединение с файлом, file является Object типа File к считываемому файлу

  • ​​FileInputStream(FileDescriptor fdObj) используя файловый дескриптор fdObj, который представляет существующее соединение с файлом в файловой системе

  • FileInputStream(String name) открывает соединение с файлом, name является путем к считываемому файлу

Если файл не может быть открыт, например, по указанному пути такого файла не существует, то генерируется исключение FileNotFoundException.

Считаем данные из ранее записанного файла и выведем на консоль:

import java.io.FileInputStream;
import java.io.IOException;

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) {
}

Когда в потоке больше нет данных для чтения, метод возвращает число -1.

Затем каждый считанный байт конвертируется в объект типа char и выводится на консоль.

Подобным образом можно считать данные в массив байтов и затем производить с ним манипуляции:

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]);
}

Совместим оба класса и выполним чтение из одного и запись в другой файл:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

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 предназначены прежде всего для записи двоичных файлов, то есть для записи и чтения байтов. И хотя они также могут использоваться для работы с текстовыми файлами, но все же для этой задачи больше подходят другие классы.

5. Классы ByteArrayInputStream и ByteArrayOutputStream

Для работы с массивами байтов - их чтения и записи используются классы ByteArrayInputStream и ByteArrayOutputStream.

5.1. Чтение массива байтов и класс ByteArrayInputStream

Класс ByteArrayInputStream представляет входной поток, использующий в качестве источника данных массив байтов. Он имеет следующие конструкторы:

  • ByteArrayInputStream(byte[] buf)

  • ByteArrayInputStream(byte[] buf, int offset, int length)

В качестве параметров конструкторы используют массив байтов buf, из которого производится считывание, смещение относительно начала массива offset и количество считываемых символов length.

Считаем массив байтов и выведем его на экран:

import java.io.ByteArrayInputStream;

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();
        ByteArrayInputStream byteStream2 = new ByteArrayInputStream(array2, 0, 5); // считываем 5 символов
        int c;
        while ((c = byteStream2.read()) != -1) {
            System.out.println((char) c);
        }
    }
}

В отличие от других классов потоков для закрытия объекта ByteArrayInputStream не требуется вызывать метод close().

5.2. Запись массива байт и класс ByteArrayOutputStream

Класс ByteArrayOutputStream представляет поток вывода, использующий массив байтов в качестве места вывода.

Чтобы создать объект данного класса, мы можем использовать один из его конструкторов:

  • ByteArrayOutputStream()

  • ByteArrayOutputStream(int size)

Первая версия создает массив для хранения байтов длиной в 32 байта, а вторая версия создает массив длиной size.

Рассмотрим применение класса:

import java.io.ByteArrayOutputStream;

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() мы можем вывести массив байт в другой поток. Данный метод в качестве параметра принимает объект OutputStream, в который производится запись массива байт:

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().

6. Классы BufferedInputStream and BufferedOutputStream

Для оптимизации операций ввода-вывода используются буферизуемые потоки. Эти потоки добавляют к стандартным специальный буфер в памяти, с помощью которого повышается производительность при чтении и записи потоков.

6.1. Класс BufferedInputStream

Класс BufferedInputStream накапливает вводимые данные в специальном буфере без постоянного обращения к устройству ввода. Класс BufferedInputStream определяет два конструктора:

  • BufferedInputStream(InputStream inputStream)

  • BufferedInputStream(InputStream inputStream, int bufSize)

Первый параметр - это поток ввода, с которого данные будут считываться в буфер. Второй параметр - размер буфера.

Например, буферизируем считывание данных из потока ByteArrayInputStream:

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;

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.

6.1.1. Класс BufferedOutputStream

Класс BufferedOutputStream аналогично создает буфер для потоков вывода. Этот буфер накапливает выводимые байты без постоянного обращения к устройству. И когда буфер заполнен, производится запись данных.

BufferedOutputStream определяет два конструктора: - BufferedOutputStream(OutputStream outputStream) - BufferedOutputStream(OutputStream outputStream, int bufSize)

outputStream - это поток вывода, который унаследован от OutputStream, а bufSize - размер буфера.

Рассмотрим на примере записи в файл:

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

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 не добавляет много новой функциональности, он просто оптимизирует действие потока вывода.

7. Классы DataOutputStream и DataInputStream

Классы DataOutputStream и DataInputStream позволяют записывать и считывать данные примитивных типов.

7.1. Запись данных и DataOutputStream

Класс DataOutputStream представляет поток вывода и предназначен для записи данных примитивных типов. Для записи каждого из примитивных типов предназначен свой метод:

  • writeBoolean(boolean v) записывает в поток булевое однобайтовое значение

  • writeByte(int v) записывает в поток 1 байт, которые представлен в виде целочисленного значения

  • writeChar(int v) записывает 2-байтовое значение char

  • writeDouble(double v) записывает в поток 8-байтовое значение double

  • writeFloat(float v) записывает в поток 4-байтовое значение float

  • writeInt(int v) записывает в поток целочисленное значение int

  • writeLong(long v) записывает в поток значение long

  • writeShort(int v) записывает в поток значение short

  • writeUTF(String str) записывает в поток строку в кодировке UTF-8

7.2. Считывание данных и DataInputStream

Класс DataInputStream действует противоположным образом - он считывает из потока данные примитивных типов. Соответственно для каждого примитивного типа определен свой метод для считывания:

  • boolean readBoolean() считывает из потока булевое однобайтовое значение

  • byte readByte() считывает из потока 1 байт

  • char readChar() считывает из потока значение char

  • double readDouble() считывает из потока 8-байтовое значение double

  • float readFloat() считывает из потока 4-байтовое значение float

  • int readInt() считывает из потока целочисленное значение int

  • long readLong() считывает из потока значение long

  • short readShort() считывает значение short

  • String readUTF() считывает из потока строку в кодировке UTF-8

  • int skipBytes(int n) пропускает при чтении из потока n байтов

Рассмотрим применение классов на примере:

import java.io.*;

public class Program {
    public static void main(String[] args) {
        Person tom = new Person("Tom", 34, 1.68, false);
        // запись в файл
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
            dos.writeUTF(tom.name);
            dos.writeInt(tom.age);
            dos.writeDouble(tom.height);
            dos.writeBoolean(tom.married);
            System.out.println("File has been written");
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }

        // обратное считывание из файла
        try (DataInputStream dos = new DataInputStream(new FileInputStream("data.bin"))) {
            String name = dos.readUTF();
            int age = dos.readInt();
            double height = dos.readDouble();
            boolean married = dos.readBoolean();
            System.out.printf("Name: %s  Age: %d  Height: %f  Married: %b", name, age, height, married);
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}
class Person {
    public String name;
    public int age;
    public double height;
    public boolean married;

    public Person(String n, int a, double h, boolean m) {
        this.name = n;
        this.height = h;
        this.age = a;
        this.married = m;
    }
}

Здесь мы последовательно записываем в файл данные объекта Person.

Объект DataOutputStream в конструкторе принимает поток вывода: DataOutputStream(OutputStream out). В данном случае в качестве потока вывода используется объект FileOutputStream, поэтому вывод будет происходить в файл. И с помощью выше рассмотренных методов типа writeUTF() производится запись значений в бинарный файл.

Затем происходит чтение ранее записанных данных. Объект DataInputStream в конструкторе принимает поток для чтения: DataInputStream(InputStream in). Здесь таким потоком выступает объект FileInputStream.

8. Классы PrintStream и PrintWriter

8.1. Класс PrintStream

Класс PrintStream - это именно тот класс, который используется для вывода на консоль. Когда мы выводим на консоль некоторую информацию с помощью вызова System.out.println(), то тем самым мы задействует PrintStream, так как переменная out в классе System как раз и представляет объект класса PrintStream, а метод println() - это метод класса PrintStream.

Но PrintStream полезен не только для вывода на консоль. Мы можем использовать данный класс для записи информации в поток вывода. Для этого PrintStream определяет ряд конструкторов:

  • PrintStream(OutputStream outputStream)

  • PrintStream(OutputStream outputStream, boolean autoFlushingOn)

  • PrintStream(OutputStream outputStream, boolean autoFlushingOn, String charSet) throws UnsupportedEncodingException

  • PrintStream(File outputFile) throws FileNotFoundException

  • PrintStream(File outputFile, String charSet) throws FileNotFoundException, UnsupportedEncodingException

  • PrintStream(String outputFileName) throws FileNotFoundException

  • PrintStream(String outputFileName, String charSet) throws FileNotFoundException, UnsupportedEncodingException

Параметр outputStream - это объект OutputStream, в который производится запись. Параметр autoFlushingOn при значении true позволяет автоматически записывать данные в поток вывода. По умолчанию этот параметр равен false. Параметр charSet позволяет указать кодировку символов.

В качестве источника для записи данных вместо OutputStream можно использовать объект File или строковый путь, по которому будет создаваться файл.

Для вывода информации в выходной поток PrintStream использует следующие методы:

  • println() вывод строковой информации с переводом строки

  • print() вывод строковой информации без перевода строки

  • printf() форматированный вывод

Например, запишем информацию в файл:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

public class Program {
    public static void main(String[] args) {
        String text = "Привет мир!"; // строка для записи
        try (FileOutputStream fos = new FileOutputStream("C://SomeDir//notes3.txt");
             PrintStream printStream = new PrintStream(fos)) {
            printStream.println(text);
            System.out.println("Запись в файл произведена");
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

В данном случае применяется форма конструктора PrintStream, которая в качестве параметра принимает поток вывода: PrintStream (OutputStream out). Кроме того, мы могли бы использовать ряд других форм конструктора, например, указывая названия файла для записи: PrintStream (string filename)

В качестве потока вывода используется объект FileOutputStream. С помощью метода println() производится запись информации в выходной поток - то есть в объект FileOutputStream. (В случае с выводом на консоль с помощью System.out.println() в качестве потока вывода выступает консоль)

Кроме того, как и любой поток вывода и наследник класса OutputStream он имеет метод write():

import java.io.IOException;
import java.io.PrintStream;

public class Program {
    public static void main(String[] args) {
        try (PrintStream printStream = new PrintStream("notes3.txt")) {
            printStream.print("Hello World!");
            printStream.println("Welcome to Java!");
            printStream.printf("Name: %s Age: %d \n", "Tom", 34);
            String message = "PrintStream";
            byte[] messageToBytes = message.getBytes();
            printStream.write(messageToBytes);
            System.out.println("The file has been written");
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

После выполнения этой программы получится файл со следующим содержанием:

Hello World!Welcome to Java!
Name: Tom Age: 34
PrintStream

8.2. PrintWriter

На PrintStream похож другой класс PrintWriter. Его можно использовать как для вывода информации на консоль, так и в файл или любой другой поток вывода. Данный класс имеет ряд конструкторов:

  • PrintWriter(File file) автоматически добавляет информацию в указанный файл

  • PrintWriter(File file, String csn) автоматически добавляет информацию в указанный файл с учетом кодировки csn

  • PrintWriter(OutputStream out) для вывода информации используется существующий объект OutputStream, автоматически сбрасывая в него данные

  • PrintWriter(OutputStream out, boolean autoFlush) для вывода информации используется существующий объект OutputStream, второй параметр указывает, надо ли автоматически добавлять в OutputStream данные

  • PrintWriter(String fileName) автоматически добавляет информацию в файл по указанному имени

  • PrintWriter(String fileName, String csn) автоматически добавляет информацию в файл по указанному имени, используя кодировку csn

  • PrintWriter(Writer out) для вывода информации используется существующий объект Writer, в который автоматически идет запись данных

  • PrintWriter(Writer out, boolean autoFlush) для вывода информации используется существующий объект Writer, второй параметр указывает, надо ли автоматически добавлять в Writer данные

PrintWriter реализует интерфейсы Appendable, Closable и Flushable, и поэтому после использования представляемый им поток надо закрывать.

Для записи данных в поток он также используется методы printf() и println().

Например, применим данный класс для вывода на консоль:

try (PrintWriter pw = new PrintWriter(System.out)) {
    pw.println("Hello world!");
}

В качестве потока вывода здесь применяется System.out, а на консоль будет выведена строка Hello world!

9. Класс FileWriter и FileReader

Хотя с помощью ранее рассмотренных классов можно записывать текст в файлы, однако они предназначены прежде всего для работы с бинарными потоками данных, и их возможностей для полноценной работы с текстовыми файлами недостаточно. И для этой цели служат совсем другие классы, которые являются наследниками абстрактных классов Reader и Writer.

9.1. Запись файлов. Класс FileWriter

Класс FileWriter является производным от класса Writer. Он используется для записи текстовых файлов.

Чтобы создать объект FileWriter, можно использовать один из следующих конструкторов:

  • FileWriter(File file)

  • FileWriter(File file, boolean append)

  • FileWriter(FileDescriptor fd)

  • FileWriter(String fileName)

  • `FileWriter(String fileName, boolean append) `

Так, в конструктор передается либо путь к файлу в виде строки, либо объект File, который ссылается на конкретный текстовый файл. Параметр append указывает, должны ли данные дозаписываться в конец файла (если параметр равен true), либо файл должен перезаписываться.

Запишем в файл какой-нибудь текст:

import java.io.FileWriter;
import java.io.IOException;

public class Program {
    public static void main(String[] args) {
        try (FileWriter writer = new FileWriter("notes3.txt", false)) {
            String text = "Hello Gold!";
            writer.write(text); // запись всей строки
            writer.append('\n'); // запись одного символа
            writer.append('E');
            writer.flush();
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

В конструкторе использовался параметр append со значением false - то есть файл будет перезаписываться. Затем с помощью методов, определенных в базовом классе Writer производится запись данных.

9.1.1. Чтение файлов. Класс FileReader

Класс FileReader наследуется от абстрактного класса Reader и предоставляет функциональность для чтения текстовых файлов.

Для создания объекта FileReader мы можем использовать один из его конструкторов:

  • FileReader(String fileName)

  • FileReader(File file)

  • FileReader(FileDescriptor fd)

А используя методы, определенные в базом классе Reader, произвести чтение файла:

import java.io.FileReader;
import java.io.IOException;

public class Program {
    public static void main(String[] args) {
        try (FileReader reader = new FileReader("notes3.txt")) {
            // читаем посимвольно
            int c;
            while ((c = reader.read()) != -1) {
                System.out.print((char) c);
            }
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

Также мы можем считывать в промежуточный буфер из массива символов:

import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;

public class Program {
    public static void main(String[] args) {
        try (FileReader reader = new FileReader("notes3.txt")) {
            char[] buf = new char[256];
            int c;
            while ((c = reader.read(buf)) > 0) {
                if (c < 256) {
                    buf = Arrays.copyOf(buf, c);
                }
                System.out.print(buf);
            }
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

В данном случае считываем последовательно символы из файла в массив из 256 символов, пока не дойдем до конца файла в этом случае метод read() возвратит число -1.

Поскольку считанная порция файла может быть меньше 256 символов (например, в файле всего 73 символа), и если количество считанных данных меньше размера буфера (256), то выполняем копирование массива с помощью метода Arrays.copyOf(). То есть фактически обрезаем массив buf, оставляя в нем только те символы, которые считаны из файла.

10. Классы BufferedWriter and BufferedReader

10.1. Запись текста через буфер и BufferedWriter

Класс BufferedWriter записывает текст в поток, предварительно буферизируя записываемые символы, тем самым снижая количество обращений к физическому носителю для записи данных.

Класс BufferedWriter имеет следующие конструкторы:

  • BufferedWriter(Writer out)

  • BufferedWriter(Writer out, int sz)

В качестве параметра он принимает поток вывода, в который надо осуществить запись. Второй параметр указывает на размер буфера.

Например, осуществим запись в файл:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Program {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("notes4.txt"))) {
            String text = "Hello  World!\nHey! Teachers! Leave them kids alone.";
            bw.write(text);
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

10.2. Чтение текста и BufferedReader

Класс BufferedReader считывает текст из символьного потока ввода, буферизируя прочитанные символы. Использование буфера призвано увеличить производительность чтения данных из потока.

Класс BufferedReader имеет следующие конструкторы:

  • BufferedReader(Reader in)

  • BufferedReader(Reader in, int sz)

Второй конструктор, кроме потока ввода, из которого производится чтение, также определяет размер буфера, в который будут считываться символы.

Так как BufferedReader наследуется от класса Reader, то он может использовать все те методы для чтения из потока, которые определены в Reader. И также BufferedReader определяет свой собственный метод readLine(), который позволяет считывать из потока построчно.

Рассмотрим применение BufferedReader:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Program {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("notes4.txt"))) {
            // чтение посимвольно
            int c;
            while ((c = br.read()) != -1) {
                System.out.print((char) c);
            }
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

Также можно считать текст построчно:

try (BufferedReader br = new BufferedReader(new FileReader("notes4.txt"))) {
    //чтение построчно
    String s;
    while ((s = br.readLine()) != null) {
        System.out.println(s);
    }
} catch (IOException ex) {
    System.out.println(ex.getMessage());
}

10.3. Считывание с консоли в файл

Соединим оба класса BufferedReader и BufferedWriter для считывания с консоли в файл. Для этого определим следующий код программы:

import java.io.*;

public class Program {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
             BufferedWriter bw = new BufferedWriter(new FileWriter("notes5.txt"))) {
            // чтение построчно
            String text;
            while (!(text = br.readLine()).equals("ESC")) {
                bw.write(text + "\n");
                bw.flush();
            }
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }
}

Здесь объект BufferedReader устанавливается для чтения с консоли с помощью объекта new InputStreamReader(System.in). В цикле while считывается введенный текст. И пока пользователь не введет строку ESC, объект BufferedWriter будет записывать текст файл.

11. Сериализация

Сериализация представляет процесс записи состояния объекта в поток, соответственно процесс извлечения или восстановления состояния объекта из потока называется десериализацией. Сериализация очень удобна, когда идет работа со сложными объектами.

Serialization

11.1. Интерфейс Serializable

Сразу надо сказать, что сериализовать можно только те объекты, которые реализуют интерфейс Serializable. Этот интерфейс не определяет никаких методов, просто он служит указателем системе, что объект, реализующий его, может быть сериализован.

11.2. Сериализация. Класс ObjectOutputStream

Для сериализации объектов в поток используется класс ObjectOutputStream. Он записывает данные в поток.

Для создания объекта ObjectOutputStream в конструктор передается поток, в который производится запись:

ObjectOutputStream(OutputStream out)

Для записи данных ObjectOutputStream использует ряд методов, среди которых можно выделить следующие:

  • void close() закрывает поток

  • void flush() очищает буфер и сбрасывает его содержимое в выходной поток

  • void write(byte[] buf) записывает в поток массив байтов

  • void write(int val) записывает в поток один младший байт из val

  • void writeBoolean(boolean val) записывает в поток значение boolean

  • void writeByte(int val) записывает в поток один младший байт из val

  • void writeChar(int val) записывает в поток значение типа char, представленное целочисленным значением

  • void writeDouble(double val) записывает в поток значение типа double

  • void writeFloat(float val) записывает в поток значение типа float

  • void writeInt(int val) записывает целочисленное значение int

  • void writeLong(long val) записывает значение типа long

  • void writeShort(int val) записывает значение типа short

  • void writeUTF(String str) записывает в поток строку в кодировке UTF-8

  • void writeObject(Object obj) записывает в поток отдельный объект

Эти методы охватывают весь спектр данных, которые можно сериализовать.

Например, сохраним в файл один объект класса Person:

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Program {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {
            Person p = new Person("Sam", 33, 178, true);
            oos.writeObject(p);
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }
}

class Person implements Serializable {
    private String name;
    private int age;
    private double height;
    private boolean married;

    Person(String n, int a, double h, boolean m) {
        name = n;
        age = a;
        height = h;
        married = m;
    }

    String getName() {
        return name;
    }

    int getAge() {
        return age;
    }

    double getHeight() {
        return height;
    }

    boolean getMarried() {
        return married;
    }
}

11.3. Десериализация. Класс ObjectInputStream

Класс ObjectInputStream отвечает за обратный процесс - чтение ранее сериализованных данных из потока. В конструкторе он принимает ссылку на поток ввода:

ObjectInputStream(InputStream in)

Функционал ObjectInputStream сосредоточен в методах, предназначенных для чтения различных типов данных. Рассмотрим основные методы этого класса:

  • void close() закрывает поток

  • int skipBytes(int len) пропускает при чтении несколько байт, количество которых равно len

  • int available() возвращает количество байт, доступных для чтения

  • int read() считывает из потока один байт и возвращает его целочисленное представление

  • boolean readBoolean() считывает из потока одно значение boolean

  • byte readByte() считывает из потока один байт

  • char readChar() считывает из потока один символ char

  • double readDouble() считывает значение типа double

  • float readFloat() считывает из потока значение типа float

  • int readInt() считывает целочисленное значение int

  • long readLong() считывает значение типа long

  • short readShort() считывает значение типа short

  • String readUTF() считывает строку в кодировке UTF-8

  • Object readObject() считывает из потока объект

Например, извлечем выше сохраненный объект Person из файла:

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class Program {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) {
            Person p = (Person) ois.readObject();
            System.out.printf("Name: %s \t Age: %d \n", p.getName(), p.getAge());
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }
}

Теперь совместим сохранение и восстановление из файла на примере списка объектов:

import java.io.*;
import java.util.ArrayList;

public class Program {
    //@SuppressWarnings("unchecked")
    public static void main(String[] args) {
        String filename = "people.dat";
        // создадим список объектов, которые будем записывать
        ArrayList<Person> people = new ArrayList<Person>();
        people.add(new Person("Tom", 30, 175, false));
        people.add(new Person("Sam", 33, 178, true));

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
            oos.writeObject(people);
            System.out.println("File has been written");
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }

        // десериализация в новый список
        ArrayList<Person> newPeople = new ArrayList<Person>();
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
            newPeople = ((ArrayList<Person>) ois.readObject());
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }

        for (Person p : newPeople) {
            System.out.printf("Name: %s \t Age: %d \n", p.getName(), p.getAge());
        }
    }
}
class Person implements Serializable {
    private String name;
    private int age;
    private double height;
    private boolean married;

    public Person(String n, int a, double h, boolean m) {
        this.name = n;
        this.age = a;
        this.height = h;
        this.married = m;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public double getHeight() {
        return this.height;
    }

    public boolean getMarried() {
        return this.married;
    }
}

11.4. Исключение данных из сериализации

По умолчанию сериализуются все переменные объекта. Однако, возможно, мы хотим, чтобы некоторые поля были исключены из сериализации. Для этого они должны быть объявлены с модификатором transient. Например, исключим из сериализации объекта Person переменные height и married:

import java.io.Serializable;

class Person implements Serializable {
    private String name;
    private int age;
    private transient double height;
    private transient boolean married;

    public Person(String n, int a, double h, boolean m) {
        this.name = n;
        this.age = a;
        this.height = h;
        this.married = m;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public double getHeight() {
        return this.height;
    }

    public boolean getMarried() {
        return this.married;
    }
}

12. Классы ZipOutputStream и ZipInputStream

Кроме общего функционала для работы с файлами Java предоставляет функциональность для работы с таким видом файлов как zip-архивы. Для этого в пакете java.util.zip определены два класса - ZipInputStream и ZipOutputStream.

12.1. ZipOutputStream. Запись архивов

Для создания архива используется класс ZipOutputStream. Для создания объекта ZipOutputStream в его конструктор передается поток вывода:

ZipOutputStream(OutputStream out)

Для записи файлов в архив для каждого файла создается объект ZipEntry, в конструктор которого передается имя архивируемого файла. А чтобы добавить каждый объект ZipEntry в архив, применяется метод putNextEntry().

Создадим архив:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class Program {
    public static void main(String[] args) {
        String filename = "C:\\SomeDir\\notes.txt";
        try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream("C:\\SomeDir\\output.zip"));
             FileInputStream fis = new FileInputStream(filename);) {
            ZipEntry entry1 = new ZipEntry("notes.txt");
            zout.putNextEntry(entry1);
            // считываем содержимое файла в массив byte
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            // добавляем содержимое к архиву
            zout.write(buffer);
            // закрываем текущую запись для новой записи
            zout.closeEntry();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }
}

После добавления объекта ZipEntry в поток нам также надо добавить в него и содержимое файла. Для этого используется метод write(), записывающий в поток массив байтов: zout.write(buffer);. В конце надо закрыть ZipEntry с помощью метода closeEntry(). После этого можно добавлять в архив новые файлы - в этом случае все вышеописанные действия для каждого нового файла повторяются.

12.2. Чтение архивов. ZipInputStream

Для чтения архивов применяется класс ZipInputStream. В конструкторе он принимает поток, указывающий на zip-архив:

ZipInputStream(InputStream in)

Для считывания файлов из архива ZipInputStream использует метод getNextEntry(), который возвращает объект ZipEntry. Объект ZipEntry представляет отдельную запись в zip-архиве. Например, считаем какой-нибудь архив:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class Program {
    public static void main(String[] args) {
        try (ZipInputStream zin = new ZipInputStream(new FileInputStream("C:\\SomeDir\\output.zip"))) {
            ZipEntry entry;
            String name;
            long size;
            while ((entry = zin.getNextEntry()) != null) {
                name = entry.getName(); // получим название файла
                size = entry.getSize();  // получим его размер в байтах
                System.out.printf("File name: %s \t File size: %d \n", name, size);
                // распаковка
                FileOutputStream fout = new FileOutputStream("C:\\somedir\\new" + name);
                for (int c = zin.read(); c != -1; c = zin.read()) {
                    fout.write(c);
                }
                fout.flush();
                zin.closeEntry();
                fout.close();
            }
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }
}

ZipInputStream в конструкторе получает ссылку на поток ввода. И затем в цикле выводятся все файлы и их размер в байтах, которые находятся в данном архиве.

Затем данные извлекаются из архива и сохраняются в новые файлы, которые находятся в той же папке и которые начинаются с new.

13. Класс Console

Специально для работы с консолью в Java определен класс Console, который хранится в пакете java.io. Он не получает консольный ввод-вывод сам по себе, а использует уже имеющиеся потоки System.in и System.out. Но в то же время Console значительно упрощает ряд операций, связанных с консолью.

Для получения объекта консоли надо вызвать статический метод System.console():

Console console = System.console();

Основные методы класса Console:

  • flush() выводит на консоль все данные из буфера

  • format() выводит на консоль строку с использованием форматирования

  • printf() выводит на консоль строку с использованием форматирования (фактически то же самое, что и предыдущий метод)

  • String readLine() считывает с консоли введенную пользователем строку

  • char[] readPassword() считывает с консоли введенную пользователем строку, при этом символы строки не отображаются на консоли

Используем класс Console:

import java.io.Console;

public class Program {
    public static void main(String[] args) {
        // получаем консоль
        Console console = System.console();
        if (console != null) {
            // считываем данные с консоли
            String login = console.readLine("Введите логин:");
            char[] password = console.readPassword("Введите пароль:");
            console.printf("Введенный логин: %s \n", login);
            console.printf("Введенный пароль: %s \n", new String(password));
        }
    }
}

Важно, что доступ к консоли мы можем получить только из самой консоли. При запуске, например, в IntelliJ IDEA вызов System.console() будет возвращать значение null. Поэтому при работе с консолью желательно проверять полученное значение на null. Ну а если мы запустим программу в командной строке, то естественно у нас все будет работать.