Почему работа с файлами в Java — это не страшно?
Каждый разработчик, даже если он пишет только бэкенд на Java, рано или поздно сталкивается с необходимостью сохранить данные, прочитать конфигурацию или обработать лог. Работа с файлами и файловой системой в Java — это базовый навык, который превращает «игрушечные» консольные приложения в настоящие утилиты. И да, в 2024 году у нас есть не только старый добрый File, но и мощный java.nio.file.
В этой статье мы разберём всё: от открытия блокнота до манипуляции с правами доступа. Поехали!
1. Классический подход: java.io.File
Начнём с дедушки. Класс File появился в Java 1.0 и до сих пор используется. Но будьте честны: он больше про метаданные, чем про содержимое. С его помощью можно проверить, существует ли файл, создать папку, получить список файлов.
import java.io.File;
public class FileExample {
public static void main(String[] args) {
File file = new File("/home/user/docs/notes.txt");
System.out.println("Файл существует: " + file.exists());
System.out.println("Абсолютный путь: " + file.getAbsolutePath());
System.out.println("Размер: " + file.length() + " байт");
// Создаём директорию
File dir = new File("/home/user/new_folder");
if (dir.mkdir()) {
System.out.println("Папка создана");
}
}
}
Минус: File не умеет нормально читать большие файлы — для этого нужны потоки. И он не поддерживает символьные ссылки (soft links) из коробки. Именно поэтому Sun (а потом Oracle) выпустили NIO.
2. Чтение файлов: от InputStream до BufferedReader
Чтение — самая частая операция. Если файл текстовый, используйте BufferedReader — он буферизирует данные и работает быстро.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ReadFile {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Ошибка чтения: " + e.getMessage());
}
}
}
Для бинарных файлов (картинки, архивы) используйте FileInputStream:
import java.io.FileInputStream;
import java.io.IOException;
public class ReadBinary {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("image.png")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
// обрабатываем кусок buffer[0..bytesRead]
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Важно: всегда используйте try-with-resources — это гарантирует закрытие потока, даже если вылетит исключение.
3. Запись в файл: BufferedWriter и FileOutputStream
Запись почти симметрична чтению. Для текста — BufferedWriter, для бинарных данных — FileOutputStream.
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class WriteFile {
public static void main(String[] args) {
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("Привет, мир!");
bw.newLine();
bw.write("Java — это мощно.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Если нужно дописать в конец файла, а не перезаписать его, используйте конструктор FileWriter(file, true).
4. Современный подход: Java NIO.2 (Path и Files)
Начиная с Java 7, появился пакет java.nio.file. Это не просто замена File, а гораздо более удобный и мощный инструмент. Ключевые классы — Path и Files.
4.1. Path — путь без боли
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathExample {
public static void main(String[] args) {
Path path = Paths.get("/home/user", "docs", "notes.txt");
System.out.println("Путь: " + path);
System.out.println("Имя файла: " + path.getFileName());
System.out.println("Родитель: " + path.getParent());
System.out.println("Корень: " + path.getRoot());
}
}
Paths.get() сам подставит правильный разделитель для Windows или Linux. Красота!
4.2. Files — швейцарский нож
Класс Files содержит статические методы для всего: чтение, запись, копирование, перемещение, удаление.
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.List;
public class FilesExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("example.txt");
// Запись строк
List<String> lines = List.of("Строка 1", "Строка 2", "Строка 3");
Files.write(path, lines);
// Чтение всех строк
List<String> content = Files.readAllLines(path);
content.forEach(System.out::println);
// Копирование
Path dest = Paths.get("backup.txt");
Files.copy(path, dest, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
// Удаление
Files.deleteIfExists(dest);
}
}
Совет: Files.readAllLines() удобен для небольших файлов. Для гигантских логов лучше использовать Files.lines(), который возвращает Stream<String> и не грузит всё в память.
5. Обход файловой системы: FileVisitor и walk
Представьте: нужно найти все файлы с расширением .log в папке и её подпапках. Раньше мы писали рекурсивный обход. Теперь есть Files.walk() и Files.walkFileTree().
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.Stream;
public class WalkExample {
public static void main(String[] args) {
Path start = Paths.get("/var/log");
try (Stream<Path> stream = Files.walk(start)) {
stream
.filter(p -> p.toString().endsWith(".log"))
.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Этот код выведет все файлы .log в директории /var/log и всех её поддиректориях. Элегантно, правда?
6. Работа с атрибутами и правами
В Java NIO можно легко читать и менять атрибуты файлов: размер, дата создания, права доступа.
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.IOException;
public class AttributesExample {
public static void main(String[] args) throws IOException {
Path path = Paths.get("test.txt");
// Чтение