Работа с потоками ввода-вывода и файловой системой

1 Теоретическая часть

1.1 Файловые потоки ввода-вывода

1.1.1 Общие концепции

Классы, осуществляющие файловый ввод и вывод, а также другие действия с потоками, расположенные в пакете java.io. Классы этого пакета предлагают ряд методов для создания таких потоков, чтения, записи и т.д. Существует два подмножества классов – соответственно для работы с текстовыми и бинарными (двоичными) файлами.

Непосредственную работу с текстовыми файлами осуществляют объекты классов FileReader и FileWriter. Потоки, предназначенные для работы с текстовой информацией, называются потокам символов.

Важный элемент работы с файловыми потоками – это буферизация. Буферизация предусматривает создание в оперативной памяти специальной области (буфера), в которую данные загружаются из файла для дальнейшего поэлементного чтения либо поэлементно записываются данные с последующим переписыванием на диск. Объекты класса BufferedReader осуществляют такое буферизированное чтение. Для буферизированного вывода применяют объекты класса BufferedWriter.

Непосредственный форматированный вывод осуществляется методами print() и println() объекта класса PrintWriter. Вся работа с потоками, кроме стандартного потока System.out, должна предусматривать перехват исключений, связанных с вводом-выводом. Это IOException и его потомки – FileNotFoundException, ObjectStreamException и другие.

Очень важно закрыть все файлы, взаимодействие с которыми имело место. При закрытии файлов осуществляется переписывание данных, оставшихся в буфере, освобождение буфера и других ресурсов, связанных с файлом. Закрыть поток можно с помощью метода close(). Например, для потока in:

in.close();

Если программа, которая требует файлового ввода, загружается в среде IntelliJ IDEA, необходимые для чтения файлы следует разместить в папке проекта (не в папке пакета). Именно в папке проекта можно найти результирующие файлы, которые появляются после завершения выполнения программы, включающей файловый вывод.

В программе можно одновременно открыть несколько потоков ввода и несколько потоков вывода.

1.1.2 Работа с потоками символов

В следующем примере из файла с именем data.txt осуществляется чтение одного целого и одного вещественного значения, их сумма записывается в файл results.txt.

package ua.inf.iwanoff.files;

import java.io.*;
import java.util.StringTokenizer;

public class FileTest {

    void readWrite() {
        try {
            FileReader fr = new FileReader("data.txt");
            BufferedReader br = new BufferedReader(fr);
            String s = br.readLine();
            int x;
            double y;
            try {
                StringTokenizer st = new StringTokenizer(s);
                x = Integer.parseInt(st.nextToken());
                y = Double.parseDouble(st.nextToken());
            }
            finally {
                br.close();
            }     
            double z = x + y;
            FileWriter fw = new FileWriter("results.txt");
            PrintWriter pw = new PrintWriter(fw);
            pw.println(z);
            pw.close();
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new FileTest().readWrite();
    }

}    

Для открытия файла создается объект класса FileReader, в конструкторе которого указывается строка – имя файла. Ссылка на созданный объект передается в конструктор класса BufferedReader. Чтение из файла осуществляется с помощью метода readLine(), который возвращает ссылку на строку символов, или null, если достигнут конец файла.

Переменная s типа String ссылается на строку, содержащую два числа. Для выделения из этой строки отдельных лексем используют объект класса StringTokenizer, в конструктор которого передается строка. Ссылки на отдельные части строки постепенно получают с помощью метода nextToken(). Эти ссылки могут быть использованы непосредственно, либо используются для преобразования данных в числовые значения (статические методы parseDouble() и parseInt() классов Double и Integer соответственно).

Для чтения из файла можно использовать уже знакомый класс Scanner. Фактическим параметром конструктора может быть файловый поток. Предыдущий пример можно реализовать с помощью класса Scanner. Можно также сократить код путем исключения ненужных переменных. Кроме того, целесообразно воспользоваться конструкцией try () { } Java 7, которая для классов, реализующих интерфейс AutoCloseable, обеспечивает гарантированное закрытие потоков:

package ua.inf.iwanoff.files;

import java.io.*;
import java.util.Scanner;

public class FileTest {

    void readWrite() {
        try (Scanner scanner = new Scanner(new FileReader("data.txt"))) {
            try (PrintWriter pw = new PrintWriter("results.txt")) {
                pw.println(scanner.nextInt() + scanner.nextDouble());
            }
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new FileTest().readWrite();
    }

}

Преимуществом такого подхода является возможность произвольного расположения входных данных (не обязательно в одной строке). Как видно из приведенного примера, несколько блоков try () могут использовать один блок catch (). Альтернативой является размещение нескольких утверждений внутри скобок:

  try (Scanner scanner = new Scanner(new FileReader("data.txt"));
               PrintWriter pw = new PrintWriter("results.txt")) {
      pw.println(scanner.nextInt() + scanner.nextDouble());
  }
  catch (IOException ex) {
      ex.printStackTrace();
  }

При работе с классом Scanner можно определить дополнительные параметры, например, установить символ-разделитель (или последовательность символов). При этом можно использовать регулярные выражения. Например, можно перед чтением данных добавить следующую строку:

scanner.useDelimiter(",");

Теперь объект-сканер будет воспринимать запятые как разделители (вместо пробелов).

1.1.3 Работа с бинарными потоками (потоками байтов)

Для работы с нетекстовыми (бинарными) файлами используют потоки, имена которых вместо "Writer" содержат "Stream", например InputStream, FileInputStream, OutputStream, FileOutputStream т.п. Такие потоки называются потоками байтов. В следующем примере осуществляется копирования двоичного файла FileCopy.class в папку проекта с новым именем:

package ua.inf.iwanoff.files;

import java.io.*;

public class FileCopy {

    public static void copy(String inFile, String outFile) {
        byte[] buffer = new byte[1024]; // Буфер байтов
        try (InputStream input = new FileInputStream(inFile);
                         OutputStream output = new FileOutputStream(outFile)) {
            int bytesRead;
            while ((bytesRead = input.read(buffer)) >= 0) {
                output.write(buffer, 0, bytesRead);
            }
        } 
        catch (IOException ex) {
            ex.printStackTrace();
        }    
    }  

    public static void main(String[] args) {
        copy("out/production/FileCopy/ua/inf/iwanoff/files/FileCopy.class", "FileCopy.copy");
    }

}

Как видно из приведенного примера, Java позволяет использовать обычную черту (/) вместо обратной. Это – более универсальный подход, приемлемый для различных операционных систем. Кроме того, обратную черту необходимо было бы записать дважды (\\).

Для работы с бинарными файлами существуют дополнительные возможности – использование потоков данных и потоков объектов. Так называемые потоки данных (data streams) поддерживают бинарный ввод/вывод значений примитивных типов данных (boolean, char, byte, short, int, long, float и double), а также значений типа String. Все потоки данных реализуют интерфейсы – DataInput или DataOutput. Для большинства задач достаточно стандартных реализации этих интерфейсов – DataInputStream и DataOutputStream. Данные в файле хранятся в таком виде, в котором они представлены в оперативной памяти. Для записи строк используют метод writeUTF(). В следующем примере осуществляется запись данных:

package ua.inf.iwanoff.files;

import java.io.*;

public class DataStreamDemo {

    public static void main(String[] args) {
        double x = 4.5;
        String s = "all";
        int[] a = { 1, 2, 3 };
        try (DataOutputStream out = new DataOutputStream(new FileOutputStream("data.dat"))) {
            out.writeDouble(x);
            out.writeUTF(s);
            for (int k : a) {
                out.writeInt(k);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Теперь данные можно прочитать в другой программе:

package ua.inf.iwanoff.files;

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

public class DataReadDemo {

    public static void main(String[] args) {
        try (DataInputStream in = new DataInputStream(new FileInputStream("data.dat"))) {
            double x = in.readDouble();
            String s = in.readUTF();
            List<Integer> list = new ArrayList<>();
            try {
                while (true) {
                    int  k = in.readInt();
                    list.add(k);
                }
            }
            catch (Exception e) {
            }
            System.out.println(x);
            System.out.println(s);
            System.out.println(list);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

}

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

Для чтения и записи данных может быть также использован класс java.io.RandomAccessFile. Объект этого класса позволяет свободно перемещаться по файлу в прямом и обратном направлении. Основным преимуществом класса RandomAccessFile является возможность читать и записывать данные в произвольное место файла.

Для того чтобы создать объект класса RandomAccessFile, необходимо вызвать его конструктор с двумя параметрами: именем файла для ввода/вывода и режимом доступа к файлу. В качестве режима можно использовать строки "r" (для чтения), "rw" (для чтения и записи), "rws" (с синхронизацией файла) или "rwd" (с синхронизацией файла и метаданных). Так может выглядеть открытие файла данных:

RandomAccessFile file1 = new RandomAccessFile("file1.dat", "r");  // для чтения 
RandomAccessFile file2 = new RandomAccessFile("file2.dat", "rw"); // для чтения и записи 

После того как файл открыт, можно использовать методы readDouble(), readInt(), readUTF() и т. д. для чтения или writeDouble(), writeInt(), writeUTF() и т. д. для вывода.

В основе управления файлом лежит текущий указатель на текущую позицию, где происходит чтение или запись данных. В момент создания объекта класса RandomAccessFile указатель устанавливается в начало файла и имеет значение 0. Вызовы методов read...() и write...() смещают позицию текущего указателя на количество прочитанных или записанных байтов. Для произвольного сдвига указателя на некоторое количество байтов можно использовать метод skipBytes(), или же установить указатель в определенное место файла вызовом метода seek(). Для того чтобы узнать текущую позицию, в которой находится указатель, нужно вызвать метод getFilePointer(). Например, в одной программе мы записываем данные в новый файл:

RandomAccessFile fileOut = new RandomAccessFile("new.dat", "rw");
int a = 1, b = 2;
fileOut.writeInt(a);
fileOut.writeInt(b);
fileOut.close();

В другой программе мы читаем второе целое число:

RandomAccessFile fileIn = new RandomAccessFile("new.dat", "rw");
fileIn.skipBytes(4); // перемещаем файловый указатель ко второму числу 
int c = fileIn.readInt();
System.out.println(c);
fileIn.close();

Узнать длину файла в байтах можно с помощью функции length().

1.2 Двоичная сериализация

Для записи и чтения объектов используют потоки ObjectInputStream и ObjectOutputStream. Наиболее естественным является использование этих потоков для сериализации и десериализации. Механизм сериализации (serialization, размещение в последовательном порядке) предусматривает запись объектов в поток битов для хранения в файле или для передачи через компьютерные сети. Десериализация предполагает чтение потока битов, создание хранимых объектов и воспроизведение их состояния на момент сохранения. Для того, чтобы объекты определенного класса можно было сериализовать, класс должен реализовывать интерфейс java.io.Serializable. Этот интерфейс не определяет методов, его наличие лишь указывает, что объекты этого класса можно сериализовать. Однако гарантированная сериализация и десериализация требует наличия в таких классах специального статического поля serialVersionUID, которое обеспечивает уникальность класса.

В среде IntelliJ IDEA статическое поле serialVersionUID, можно сгенерировать автоматически, предварительно включив в установках File | Settings | Editor | Inspections | Java | Serialization issues опцию Serializable class without 'serialVersionUID'. После этого, находясь в редакторе, добавив реализацию интерфейса и выбрав соответствующий класс, можно воспользоваться контекстной подсказкой Add 'serialVersionUID' field.

Классы ObjectOutputStream и ObjectInputStream позволяют осуществлять сериализацию и десериализацию. Они реализуют интерфейсы ObjectOutput и ObjectInput соответственно. Механизмы сериализации и десериализации рассмотрим на следующем примере. Предположим, описан класс Point:

package ua.inf.iwanoff.files;

import java.io.Serializable;

public class Point implements Serializable {
    private static final long serialVersionUID = -3861862668546826739L;
    private double x, y;

    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

}

Также создан класс Line:

package ua.inf.iwanoff.files;

import java.io.Serializable;

public class Line implements Serializable {
    private static final long serialVersionUID = -4909779210010719389L;
    private Point first = new Point(), second = new Point();

    public void setFirst(Point first) {
        this.first = first;
    }

    public Point getFirst() {
        return first;
    }

    public Point getSecond() {
        return second;
    }

    public void setSecond(Point second) {
        this.second = second;
    }

}

В приведенной ниже программе (в том же пакете) осуществляется создание объектов с последующей сериализацией:

package ua.inf.iwanoff.files;

import java.io.*;

public class SerializationTest {

    public static void main(String[] args) {
        Line line = new Line();
        line.getFirst().setX(1);
        line.getFirst().setY(2);
        line.getSecond().setX(3);
        line.getSecond().setY(4);
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp.dat"))) {
            out.writeObject(line);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

В другой программе можно осуществить десериализацию:

package ua.inf.iwanoff.files;

import java.io.*;

public class DeserializationTest {

    public static void main(String[] args) throws ClassNotFoundException {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("temp.dat"))) {
            Line line = (Line) in.readObject();
            System.out.println(line.getFirst().getX() + " " + line.getFirst().getY() + " " +
                               line.getSecond().getX() + " " + line.getSecond().getY());
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

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

package ua.inf.iwanoff.files;

import java.io.*;

class Pair implements Serializable {
    private static final long serialVersionUID = 6802552080830378203L;
    double x, y;

    public Pair(double x, double y) {
        this.x = x;
        this.y = y;
    }

}

class ArrayOfPairs implements Serializable {
    private static final long serialVersionUID = 5308689750632711432L;
    Pair[] pairs;

    public ArrayOfPairs(Pair[] pairs) {
        this.pairs = pairs;
    }

} 

public class ArraySerialization {

    public static void main(String[] args) {
        Pair[] points = { new Pair(1, 2), new Pair(3, 4), new Pair(5, 6) };
        ArrayOfPairs arrayOfPoints = new ArrayOfPairs(points);
        try (ObjectOutputStream out = 
             new ObjectOutputStream(new FileOutputStream("temp.dat"))) {
            out.writeObject(arrayOfPoints);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Теперь можно осуществить десериализацию:

package ua.inf.iwanoff.files;

import java.io.*;

public class ArrayDeserialization {

    public static void main(String[] args) throws ClassNotFoundException {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("temp.dat"))) {
            ArrayOfPairs arrayOfPairs = (ArrayOfPairs) in.readObject();
            for (Pair p : arrayOfPairs.pairs) {
                System.out.println(p.x + " " + p.y);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Некоторые поля класса, значения которых не влияют на состояние объекта, можно описать с модификатором transient. например:

class SomeClass implements Serializable {
    transient int someUnnecessaryField;
}

Такие поля не будут сохраняться в потоке при сериализации и не будут воспроизведены при десериализации.

Сериализовать можно также объекты обобщенных классов. При десериализации необходимо дополнительно обрабатывать исключение ClassNotFoundException. Кроме того, необходимо подавлять предупреждение "unchecked".

1.3 Работа с архивами

Пакет java.util.zip предоставляет возможности работы со стандартными файлами ZIP и GZIP форматов.

Для записи в архив применяют класс ZipOutputStream. С помощью функции setMethod() этого класса можно определить метод архивации – ZipOutputStream.DEFLATED (с компрессией) или ZipOutputStream.STORED (без компрессии). Метод setLevel() определяет уровень компрессии (вд 0 до 9, по умолчанию Deflater.DEFAULT_COMPRESSION, обычно максимальная компрессия). Метод setComment() позволяет добавить комментарий к архиву.

Для каждого файла, который нужно поместить в zip-файл, создается объект ZipEntry. Предполагаемое имя файла передается конструктору ZipEntry. В нем можно отдельно установить аналогичные параметры. Далее с помощью метода putNextEntry() класса ZipOutputStream "раскрывается" соответствующая точка входа в архив. С помощью средств работы с файловыми потоками осуществляется запись данных в архив, затем следует закрыть объект ZipEntry посредством вызова closeEntry().

В следующем примере создается архив Source.zip, к которому прилагается содержимое исходного файла ZipCreator.java:

package ua.inf.iwanoff.files;

import java.io.*;
import java.util.zip.*;

public class ZipCreator {

    public static void main(String[] args) {
        try (ZipOutputStream zOut = new ZipOutputStream(new FileOutputStream("Source.zip"))) {
            ZipEntry zipEntry = new ZipEntry("src/ua/inf/iwanoff/files/ZipCreator.java");
            zOut.putNextEntry(zipEntry);
            try (FileInputStream in = new FileInputStream("src/ua/inf/iwanoff/files/ZipCreator.java")) {
                byte[] bytes = new byte[1024];
                int length;
                while ((length = in.read(bytes)) >= 0) {
                    zOut.write(bytes, 0, length);
                }
            } 
            zOut.closeEntry();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

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

ZipEntry zipEntry = new ZipEntry("ZipCreator.java");

Для того, чтобы прочитать данные из архива, необходимо воспользоваться классом ZipInputStream. В каждом таком архиве всегда нужно просматривать отдельные записи (entries). Метод getNextEntry() возвращает следующую ссылку на объект типа ZipEntry. Метод read() класса ZipInputStream возвращает -1 вконце текущей записи (а не только в конце Zip-файла). Далее вызывается метод closeEntry() для получения возможности перехода к считыванию следующей записи. В следующем примере осуществляется чтение записи ZipCreator.java из ранее созданного архива и вывод его содержимого в консольное окно:

package ua.inf.iwanoff.files;

import java.io.*;
import java.util.zip.*;

public class ZipExtractor {

    public static void main(String[] args) {
        try (ZipInputStream zIn = new ZipInputStream(new FileInputStream("Source.zip"))) {
            ZipEntry entry;
            byte[] buffer = new byte[1024];
            while ((entry = zIn.getNextEntry()) != null) {
                int bytesRead;
                System.out.println("------------" + entry.getName() + "------------");
                while ((bytesRead = zIn.read(buffer)) >= 0) {
                    System.out.write(buffer, 0, bytesRead);
                }
                zIn.closeEntry();
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Аналогично осуществляется работа с архивами формата GZIP. Соответствующие потоки чтения и записи – GZIPInputStream і GZIPOutputStream.

1.4 Работа с файловой системой

1.4.1 Общие концепции

Java предоставляет возможность работы не только с содержанием файлов, а также с файловой системой в целом. Файловая система – это способ организации данных, используемый операционной системой для хранения информации в виде файлов на носителях информации. Также этим понятием обозначают совокупность файлов и каталогов (папок), которые размещаются на логическом или физическом устройстве.

К типичным функциям взаимодействия с файловой системой относятся:

  • проверка существования файла или каталога
  • получение списка файлов и подкаталогов заданного каталога
  • создание файлов и ссылок на файлы
  • копирование файлов
  • переименование и перемещение файлов
  • управление атрибутами файлов
  • удаление файлов
  • обход дерева подкаталогов
  • отслеживание изменений файлов

Для работы с файловой системой Java предоставляет два подхода:

  • использование класса java.io.File;
  • использование средств пакета java.nio.file.

1.4.2 Использование класса File

В версиях Java до 6 включительно для работы с файловой системой предоставлены средства, реализованные классом java.io.File. Для создания объекта этого класса в качестве параметра конструктора следует определить полный или относительный путь к файлу. Например:

File dir = new File("C:\\Users");
File currentDir = new File("."); // Папка проекта (текущая)

Класс File содержит методы для получения списка файлов определенной папки (list(), listFiles()), получения и модификации атрибутов файлов (setLastModified(), setReadOnly(), isHidden(), isDirectory() и т.д.), создания нового файла (createNewFile(), createTempFile()), создания папок (mkdir()), удаления файлов и папок (delete()) и многие другие. Работу некоторых из этих методов можно продемонстрировать на следующем примере:

package ua.inf.iwanoff.files;

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

public class FileTest {

    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Введите имя папки, которую вы хотите создать:");
        String dirName = scanner.next();
        File dir = new File(dirName);
    // Создаем новую папку:
        if (!dir.mkdir()) {
            System.out.println("Нельзя создать папку!");
            return;
        }
    // Создаем новый файл внутри новой папки: 
        File file = new File(dir + "\\temp.txt");
        file.createNewFile();
    // Показываем список файлов папки:
        System.out.println(Arrays.asList(dir.list()));
        file.delete(); // Удаляем файл
        dir.delete();  // Удаляем папку
    }

}

Функция list() без параметров позволяет получить массив строк – всех файлов и подкаталогов папки, определенной при создании объекта типа File. Выводятся относительные имена файлов (без пути). В следующем примере мы получаем список файлов и подкаталогов папки, имя которой вводится с клавиатуры:

package ua.inf.iwanoff.files;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Scanner;

public class ListOfFiles {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Введите имя папки:");
        String dirName = scanner.next();
        File dir = new File(dirName);
        if (!dir.isDirectory()) {
            System.out.println("Неправильное имя папки!");
            return;
        }
        String[] list = dir.list();
        for(String name : list) {
            System.out.println(name);
        }
    }

}

В отличие от list(), функция listFiles() возвращает массив объектов типа File. Это дает дополнительные возможности – получение имен файлов с полным путем, проверка значений атрибутов файлов, отдельная работа с папками и т.д. Эти дополнительные возможности покажем на следующем примере:

    File[] list = dir.listFiles();
 // Выводятся данные о файлах в форме по умолчанию:
    for(File file : list) {
        System.out.println(file);
    }
 // Выводится полный путь:
    for(File file : list) {
      System.out.println(file.getCanonicalPath());
    }
 // Выводятся только подкаталоги:
    for(File file : list) {
        if (file.isDirectory()) {
            System.out.println(file.getCanonicalPath());
        }
    }

Функция getCanonicalPath(), используемая в данном примере, позволяет получить полный путь к файлу. В отличие от getAbsolutePath(), исключаются элементы относительного пути типа "\..\.\".

Для определения маски-фильтра необходимо создать объект класса, который реализует интерфейс FilenameFilter. В следующем примере мы получаем список файлов и подкаталогов, имена которых начинаются с буквы s:

    String[] list = dir.list(new FilenameFilter() {
        @Override
        public boolean accept(File dir, String name) {
            return name.toLowerCase().charAt(0) == 's';
        }
    });
    for(String name : list) {
        System.out.println(name);
    }

Аналогичный параметр типа FilenameFilter применим к функции listFiles().

1.4.3 Работа с пакетом java.nio

Пакет java.nio, появившийся в JDK 1.4, первоначально включал альтернативные средства ввода-вывода. По сравнению с традиционными потоками ввода-вывода, java.nio обеспечивает более высокую эффективность операций ввода-вывода. Это достигается за счет того, что традиционные средства ввода-вывода работают с данными в потоках, в то время как java.nio работает с данными в блоках. Центральными объектами в java.nio являются "Канал" (Channel) и "Буфер" (Buffer). Каналы аналогичны потокам в пакете java.io. Буфер – это контейнерный объект. Все данные, которые передаются в канал, должны быть сначала помещены в буфер. Любые данные, которые считываются из канала, считываются в буфер. Средства java.nio эффективны при работе с двоичными файлами.

Версия Java 7 предоставляет альтернативный подход к работе с файловой системой – набор классов, описанных в пакете java.nio.files. Пакет java.nio.files предоставляет класс Path, обеспечивающий представление пути в файловой системе. Отдельные составляющие этого пути можно представить некоторой коллекцией имен промежуточных подкаталогов и имени самого файла (подкаталога). Получить объект класса Path можно с помощью метода get() класса Paths. Методу get() передается строка – путь:

Path path = Paths.get("c:/Users/Public");

Теперь можно получить информацию про путь:

System.out.println(path.toString());      // c:\Users\Public
System.out.println(path.getFileName());   // Public
System.out.println(path.getName(0));      // Users
System.out.println(path.getNameCount());  // 2
System.out.println(path.subpath(0, 2));   // Users\Public
System.out.println(path.getParent());     // c:\Users
System.out.println(path.getRoot());       // c:\

После того, как объект класса Path создан, можно использовать в качестве аргумента статических функций класса java.nio.files.Files. Для проверки наличия (отсутствия) файла используют соответственно функции exists() и notExists():

Path dir = Paths.get("c:/Windows");
System.out.println(Files.exists(dir));    // скорее всего, true
System.out.println(Files.notExists(dir)); // скорее всего, false

Наличие двух отдельных функций связано с возможностью получения неопределенного результата (запрещен доступ к файлу).

Чтобы убедиться, что программа может получить необходимый доступ к файлу, можно использовать методы isReadable(Path), isWritable(Path) и isExecutable(Path). Допустим, создан объект file типа Path и задан путь к файлу. Следующий фрагмент кода проверяет, существует ли конкретный файл, и можно ли загрузить его на выполнение:

boolean isRegularExecutableFile = Files.isRegularFile(file) & 
                                  Files.isReadable(file) & Files.isExecutable(file);

Для получения метаданных (данных о файлах и каталогах) класс Files предоставляет ряд статических методов:

Методы Объяснение
size(Path) Возвращает размер указанного файла в байтах
isDirectory(Path, LinkOption...) Возвращает true, если указанный Path определяет файл, который является каталогом
isRegularFile(Path, LinkOption...) Возвращает true, если указанный Path указывает на обычный файл
isHidden(Path) Возвращает true, если указанный Path указывает на скрытый файл
getLastModifiedTime(Path, LinkOption...) setLastModifiedTime(Path, FileTime) Возвращает или устанавливает время последнего изменения указанного файла
getOwner(Path, LinkOption...) setOwner(Path, UserPrincipal) Возвращает или устанавливает владельца файла
getAttribute(Path, String, LinkOption...)
setAttribute(Path, String, Object, LinkOption...)
Возвращает или устанавливает значение атрибута файла

Для ОС Windows различных версий строка атрибута должна начинаться с префикса "dos:". Например, так можно установить необходимые атрибуты некоторому файлу:

Path file = ...
Files.setAttribute(file, "dos:archive", false);
Files.setAttribute(file, "dos:hidden", true);
Files.setAttribute(file, "dos:readonly", true);
Files.setAttribute(file, "dos:system", true);

Чтение необходимых атрибутов может также осуществляться методом readAttributes(). Его второй параметр – метаданные о возвращаемом типе, которые могут быть получены через значение поля class (метаданные типов будут рассмотрены позже). Наиболее подходящий тип результата – класс java.nio.file.attribute.BasicFileAttributes. Например, так можно получить некоторые данные о файле:

package ua.inf.iwanoff.files;

import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Scanner;

public class Attributes {

    public static void main(String[] args) throws Exception {
        System.out.println("Введите имя файла или каталога:");
        Path path = Paths.get(new Scanner(System.in).nextLine());
        BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
        System.out.println("Время создания: " + attr.creationTime());
        System.out.println("Время последнего доступа: " + attr.lastAccessTime());
        System.out.println("Время последнего изменения: " + attr.lastModifiedTime());
        System.out.println("Каталог: " + attr.isDirectory());
        System.out.println("Обычный файл: " + attr.isRegularFile());
        System.out.println("Размер: " + attr.size());
    }

}

Класс DosFileAttributes, производный от BasicFileAttributes, предоставляет также функции isReadOnly(), isHidden(), isArchive() и isSystem().

В отличие от ранее созданных средств для работы с файловой системой, класс java.nio.files.Files предоставляет функцию copy() для копирования файлов. Например:

Files.copy(Paths.get("c:/autoexec.bat"), Paths.get("c:/Users/autoexec.bat"));
Files.copy(Paths.get("c:/autoexec.bat"), 
           Paths.get("c:/Users/autoexec.bat"), StandardCopyOption.REPLACE_EXISTING);

Существуют также опции StandardCopyOption.ATOMIC_MOVE и StandardCopyOption.COPY_ATTRIBUTES. Опции можно перечислять через запятую.

Для перемещения файлов используют функцию move() (с аналогичными атрибутами или без них). Переименование выполняется той же функцией:

Files.move(Paths.get("c:/Users/autoexec.bat"), Paths.get("d:/autoexec.bat"));// перемещение
Files.move(Paths.get("d:/autoexec.bat"), Paths.get("d:/unnecessary.bat"));// переименование

Создание новых каталогов осуществляется с помощью функции createDirectory() класса Files. Параметр функции имеет тип Path.

Path dir = Paths.get("c:/NewDir");
Files.createDirectory(dir);

Для создания каталога нескольких уровней в глубину, когда один или несколько родительских каталогов, возможно, еще не существует, можно использовать метод createDirectories():

Path dir = Paths.get("c:/NewDir/1/2");
Files.createDirectories(dir);

Для получения списка файлов подкаталога можно воспользоваться классом DirectoryStream.

package ua.inf.iwanoff.files;

import java.io.IOException;
import java.nio.file.*;

public class FileListDemo {

    public static void main(String[] args) {
        Path dir = Paths.get("c:/Windows");
        try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
            for (Path p : ds) {
                System.out.println(p.getFileName());
           }
        } 
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Удаление файлов и папок осуществляется с помощью функций delete() и deleteIfExists():

Files.delete(Paths.get("d:/unnecessary.bat"));
Files.deleteIfExists(Paths.get("d:/unnecessary.bat"));

Для обхода дерева каталогов пакет java.nio.files предоставляет средства, не требующие реализации рекурсивных алгоритмов. Существует метод walkFileTree() класса Files, обеспечивающий обход дерева подкаталогов. В качестве параметров необходимо указать начальный каталог (объект типа Path), , а также объект, реализующий обобщенный интерфейс FileVisitor.

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

Для реализации интерфейса FileVisitor нужно определить методы preVisitDirectory(), postVisitDirectory(), visitFile() и visitFileFailed(). Результат этих функций – перечисление типа FileVisitResult. Возможные значения этого перечисления – CONTINUE (продолжать поиск), TERMINATE (продолжать поиск), SKIP_SUBTREE (пропустить поддерево) и SKIP_SIBLINGS (пропустить элементы того же уровня).

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

package ua.inf.iwanoff.files;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Scanner;

public class FindAllFiles {

    private static class Finder extends SimpleFileVisitor<Path> {

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                                                            throws IOException {
            System.out.println(file);
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                                                            throws IOException {
            System.out.println("----------------" + dir + "----------------");
            return FileVisitResult.CONTINUE;
        }

    }

    public static void main(String[] args) {
        String dirName = new Scanner(System.in).nextLine();
        try {
            Files.walkFileTree(Paths.get(dirName), new Finder()); // Текущий каталог
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Для поиска файлов можно пользоваться масками (так называемые "glob"-маски), активно применяемых во всех операционных системах. Примеры таких масок – "a*.*" (имена файлов начинаются на букву a), "*.txt" (файлы с расширением *.txt) и т. д. Допустим, строка pattern содержит такую маску. Далее создается объект PathMatcher:

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);

В следующем примере в заданном каталоге осуществляется поиск файлов по указанной маске:

package ua.inf.iwanoff.files;

import java.io.IOException;
import java.nio.file.*;
import java.util.Scanner;

public class FindMatched {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String dirName = scanner.nextLine();
        String pattern = scanner.nextLine();
        Path dir = Paths.get(dirName);
        PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
        try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
            for (Path file : ds) {
                if (matcher.matches(file.getFileName())) {
                    System.out.println(file.getFileName());
                }
            }
        } 
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Маски могут сочетаться с обходом дерева каталогов.

Одна из задач файловой системы – отслеживание состояния указанного каталога. Например, программа должна обновлять данные о файлах и подкаталогах некоторого каталога, если другие процессы или потоки управления обусловили появление, изменение, удаление файлов и папок и т. д. Пакет java.nio.files предоставляет средства для регистрации таких каталогов и отслеживания их состояния. Для отслеживания изменений можно реализовать интерфейс WatchService. Подходящую реализацию можно получить с помощью функции FileSystems.getDefault().newWatchService(). Класс StandardWatchEventKinds предоставляет необходимые константы для возможных событий.

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

package ua.inf.iwanoff.files;

import java.nio.file.*;
import java.util.Scanner;
import static java.nio.file.StandardWatchEventKinds.*;

public class WatchDir {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws Exception {
        System.out.println("Введите имя каталога:");
        Path dir = Paths.get(new Scanner(System.in).nextLine());
    // Создаем объект WatchService
        WatchService watcher = FileSystems.getDefault().newWatchService();
    // Регистрируем отслеживаемые события:
        WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        while (true) { // бесконечный цикл
            key = watcher.take(); // ожидаем следующий набор событий
            for (WatchEvent<?> event: key.pollEvents()) {
                WatchEvent<Path> ev = (WatchEvent<Path>)event;
                System.out.printf("%s: %s\n", ev.kind().name(), dir.resolve(ev.context()));
            }
            key.reset(); // сбрасываем состояние набора событий
        }
    }

}

Библиотека java.nio.files поддерживает работу как с символьными ссылками (symlinks, soft links), так и с жесткими ссылками (hard links). Метод createSymbolicLink(новая_ссылка, существующий_объект) класса Files создает символьную ссылку, метод createLink(новая_ссылка, существующий_файл) создает жесткую ссылку. Метод isSymbolicLink() возвращает true, если переданный ему объект – символьная ссылка. Метод readSymbolicLink() позволяет найти объект, на который ссылается символьная ссылка.

2 Примеры программ

2.1 Построчное копирование текстовых файлов

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

package ua.inf.iwanoff.files;

import java.io.*;

public class TextFileCopy {

    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println("Нужны аргументы!");
            return;
        }
        try (BufferedReader in = new BufferedReader(new FileReader(args[0]));
             PrintWriter out = new PrintWriter(new FileWriter(args[1]))) {
            String line;
            while ((line = in.readLine()) != null) {
                out.println(line);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

2.2 Сериализация и десериализация данных

Предположим, необходимо создать классы Страна (Country) и Континент (Continent), создать объект типа Continent, осуществить его сериализацию и десериализацию. Класс Country будет таким:

package ua.inf.iwanoff.files;

import java.io.Serializable;

public class Country implements Serializable {
    private static final long serialVersionUID = -6755942443306500892L;
    private String name;
    private double area;
    private int population;

    public Country(String name, double area, int population) {
        this.name = name;
        this.area = area;
        this.population = population;
   }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getArea() {
        return area;
    }

    public void setArea(double area) {
        this.area = area;
    }

    public int getPopulation() {
        return population;
    }

    public void setPopulation(int population) {
        this.population = population;
    }

}

Класс Continent может быть таким:

package ua.inf.iwanoff.files;

import java.io.Serializable;

public class Continent implements Serializable {
    private static final long serialVersionUID = 8433147861334322335L;
    private String name;
    private Country[] countries;

    public Continent(String name, Country... countries) {
        this.name = name;
        this.countries = countries;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Country[] getCountries() {
        return countries;
    }

    public void setCountries(Country[] countries) {
        this.countries = countries;
    }

}

Приведенная ниже программа осуществляет создание и сериализацию объекта Continent:

package ua.inf.iwanoff.files;

import java.io.*;

public class DataSerialization {

    public static void main(String[] args) {
        Continent c = new Continent("Европа", 
            new Country("Украина", 603700, 46314736),
            new Country("Франция", 547030, 61875822),
            new Country("Германия", 357022, 82310000)
        );
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Countries.dat"))) {
            out.writeObject(c);
        }
        catch (IOException e) {
            e.printStackTrace();
        };
    }

}

Так можно осуществить десериализацию:

package ua.inf.iwanoff.files;

import java.io.*;

public class DataDeserialization {

    public static void main(String[] args) throws ClassNotFoundException {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("Countries.dat"))) {
            Continent continent = (Continent) in.readObject();
            for (Country c : continent.getCountries()) {
                System.out.println(c.getName() + " " + c.getArea() + " " + c.getPopulation());
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        };
    }

}

2.3 Сериализация и десериализация объектов обобщенных классов

Допустим, мы создали обобщенный класс Triple (тройка).

package ua.inf.iwanoff.files;

import java.io.*;

class Triple<T> implements Serializable {
    private static final long serialVersionUID = 7512336951571111736L;
    T x, y, z;

    Triple(T x, T y, T z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Можно создать объекты с различными параметрами обобщения и осуществить сериализацию:

package ua.inf.iwanoff.files;

import java.io.*;

public class GenericsSerialization {

    public static void main(String[] args) {
        Triple<Integer> t1 = new Triple<>(1, 2, 3);
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Integers.dat"))) {
            out.writeObject(t1);
        }
        catch (IOException e) {
            e.printStackTrace();
        };
        Triple<String> t2 = new Triple<>("A", "B", "C");
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Strings.dat"))) {
            out.writeObject(t2);
        }
        catch (IOException e) {
            e.printStackTrace();
        };
    }

}

Можно осуществить десериализацию:

package ua.inf.iwanoff.files;

import java.io.*;

public class GenericsDeserialization {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws ClassNotFoundException {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("Integers.dat"))) {
            Triple<Integer> t1 = (Triple<Integer>) in.readObject();
            System.out.printf("%d %d %d%n", t1.x, t1.y, t1.z);
        }
        catch (IOException e) {
            e.printStackTrace();
        };
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("Strings.dat"))) {
            Triple<String> t2 = (Triple<String>) in.readObject();
            System.out.printf("%s %s %s%n", t2.x, t2.y, t2.z);
        }
        catch (IOException e) {
            e.printStackTrace();
        };
    }

}

Как видно из примера, для сериализации и десериализации можно использовать классы с пакетной видимостью.

2.4 Работа с архивом

Данные об объектах из примера 2.2 можно сохранить в архиве. Приведенная ниже программа осуществляет создание объекта Continent и сохранение данных в архиве. Каждой стране соответствует своя точка входа ZipEntry:

package ua.inf.iwanoff.files;

import java.io.*;
import java.util.zip.*;

public class StoreToZip {

    public static void main(String[] args) {

        Continent continent = new Continent("Европа", 
            new Country("Украина", 603700, 46314736),
            new Country("Франция", 547030, 61875822),
            new Country("Германия", 357022, 82310000)
        );
        try (ZipOutputStream zOut = new ZipOutputStream(new FileOutputStream("Continent.zip"));
            DataOutputStream out = new DataOutputStream(zOut)) {
            for (Country country : continent.getCountries()) {
                ZipEntry zipEntry = new ZipEntry(country.getName());
                zOut.putNextEntry(zipEntry);    
                out.writeDouble(country.getArea());
                out.writeInt(country.getPopulation());
                zOut.closeEntry();
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Так можно осуществить чтение из архива:

package ua.inf.iwanoff.files;

import java.io.*;
import java.util.zip.*;

public class ReadFromZip {

    public static void main(String[] args) {
        try (ZipInputStream zIn = new ZipInputStream(new FileInputStream("Continent.zip"));
                                  DataInputStream in = new DataInputStream(zIn)) {
            ZipEntry entry;
            while ((entry = zIn.getNextEntry()) != null) {
                System.out.println("Страна: " + entry.getName());
                System.out.println("Территория: " + in.readDouble());
                System.out.println("Население: " + in.readInt());
                System.out.println();
                zIn.closeEntry();
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

2.5 Обход дерева файлов с использованием средств класса File

Предположим, необходимо создать программу, которая осуществляет поиск файлов с длиной не меньше заданной во всех подкаталогах, начиная с некоторого каталога. Можно использовать класс java.io.File. Программа будет иметь следующий вид:

package ua.inf.iwanoff.files;

import java.io.File;
import java.io.IOException;
import java.util.Scanner;

public class FindWithIO {

    public static void showList(File dir, int len) throws IOException {
        for (File f : dir.listFiles()) {
            if (!f.isDirectory()) {
                if (f.length() > len) {
                    System.out.println(f.getCanonicalPath() + "   " + f.length());
                }
            }
        }
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                showList(f, len);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Введите имя папки:");
        String dirName = scanner.next();
        File dir = new File(dirName);
        if (!dir.isDirectory()) {
            System.out.println("Неправильное имя папки!");
            return;
        }
        System.out.print("Введите минимальную длину файла:");
        int len = scanner.nextInt();
        showList(dir, len);
    }

}

2.6 Обход дерева файлов с использованием средств пакета java.nio.file

Предыдущую задачу можно решить с использованием средств пакета java.nio.file. Программа будет иметь следующий вид:

package ua.inf.iwanoff.files;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Scanner;

public class FindWithNIO {

    private static class Finder extends SimpleFileVisitor<Path> {
        private int len;

        Finder(int len) {
            this.len = len;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                                                      throws IOException {
            if (Files.size(file) >= len) {
                System.out.println(file + "   " + Files.size(file));
            }
            return FileVisitResult.CONTINUE;
        }

    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Введите имя папки:");
        String dirName = scanner.nextLine();
        System.out.print("Введите минимальную длину файла:");
        int len = scanner.nextInt();
        try {
            Files.walkFileTree(Paths.get(dirName), new Finder(len)); // Текущий каталог
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

3 Задания на самостоятельную работу

3.1 Работа с текстовыми файлами

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

3.2 Текстовые файлы с числовыми данными

Разработать программу, которая осуществляет чтение из текстового файла действительных значений (до конца файла), находит произведение модулей ненулевых элементов и выводит в другой текстовый файл.

3.3 Работа с несколькими файлами*

Разработать программу, которая осуществляет чтение из текстового файла целых чисел и сохранение в двух файлах данных (DataOutputStream) соответственно четных и нечетных чисел. В другой программе прочитать данные из двух файлов данных (DataInputStream) и записать их в новый текстовый файл в порядке убывания. Использовать PriorityQueue для обеспечения упорядоченности чисел.

3.4 Реализация сериализации и десериализации*

Описать классы Студент и Академическая группа (с массивом студентов в качестве поля). Создать объекты, осуществить их сериализацию и десериализацию.

3.5 Сериализация и десериализация объектов обобщенных классов*

Описать классы "Учебное заведение" и "Массив" (обобщенный класс). Создать массив объектов "Учебное заведение", осуществить сериализацию и десериализацию.

3.6 Работа с ZIP-архивом*

Описать классы Студент и Академическая группа (с массивом студентов в качестве поля). Создать объекты Студент и Академическая группа, осуществить запись данных о студентах академической группы в архив (ZIP). В другой программе осуществить чтение из архива.

3.7 Архивация нескольких файлов*

Реализовать две программы, одна из которых осуществляет архивацию нескольких указанных файлов, а другая – извлечение их из архива.

3.8 Реализация алгоритма Хаффмана (задача повышенной трудности)

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

3.9 Работа с классом File*

Создать новый файл в корневой папке проекта. Вывести список файлов и папок корневой папки проекта. Удалить только что созданный файл. Использовать класс java.io.File.

3.10 Получение информации об атрибутах файлов*

Разработать программу, которая для заданного файла (каталога) выдает информацию об его атрибутах. Использовать класс DosFileAttributes.

3.11 Копирование файлов*

До начала выполнения программы создать каталог с несколькими файлами. В программе создать новый каталог и скопировать туда файлы из ранее созданного. Удалить ранее созданный каталог. Использовать класс java.io.File.

3.12 Перемещение файлов

Решить предыдущую задачу с использованием средств перемещения (переименования) файлов.

3.13 Обход дерева каталогов*

Ввести имя подкаталога и осуществить поиск всех скрытых файлов во всех подкаталогах, начиная с некоторого каталога. Реализовать два подхода – с использованием класса java.io.File и с использованием средств пакета java.nio.file.

3.14 Отслеживание состояния каталога*

Реализовать программу отслеживания появления, изменения и удаления в заданном каталоге файлов с расширением .txt. Использовать средства пакета java.nio.file.

4 Контрольные вопросы

  1. В чем отличие текстовых и двоичных файлов?
  2. Чем отличаются потоки байтов от потоков символов по области применения?
  3. В чем смысл явного закрытия файлов?
  4. Можно ли одновременно открыть несколько потоков ввода/вывода?
  5. Каким образом можно обеспечить автоматическое закрытие потоков?
  6. Какие классы обеспечивают работу с текстовыми файлами и бинарными файлами?
  7. В чем преимущества использования класса RandomAccessFile?
  8. Каково использование файлов данных DataOutputStream и DataInputStream? Какие у них преимущества и недостатки?
  9. В чем назначение сериализации?
  10. В чем есть преимущества и недостатки сериализации?
  11. Какие функции следует определить для реализации интерфейса java.io.Serializable?
  12. Для чего используют модификатор transient?
  13. Как в Java осуществляется работа с архивами?
  14. Можно ли создать архив с несколькими файлами внутри?
  15. Как определить понятие "файловая система"?
  16. Какие можно назвать типовые функции для работы с файловой системой?
  17. Какие средства предоставляет Java для работы с файловой системой?
  18. Как получить атрибуты файла с помощью средств класса java.io.File?
  19. Чем отличаются функции list() и listFiles()?
  20. Как осуществить копирование файлов?
  21. Как осуществить переименование и перемещение файлов?
  22. Как осуществить управление атрибутами файлов?
  23. Как осуществить удаление файлов?
  24. Как осуществить поиск файлов?
  25. Как осуществить обход дерева каталогов?
  26. Как осуществить отслеживание изменений каталогов и файлов?

 

up