До этого мы работали с данными в памяти программы. Но в реальных приложениях нужно читать файлы, записывать логи, обрабатывать конфигурации, работать с сетью. Давай разберёмся, как Java работает с внешним миром.
В Java есть два поколения API для работы с вводом/выводом:
💡 Современный подход: для работы с файлами используй NIO.2 (Path, Files). Для работы с сетью и высоконагруженных систем — NIO с каналами и буферами. В этой теме сосредоточимся на NIO.2 для файлов — это то, что тебе нужно в 95% случаев.
Прежде чем читать файл, нужно указать, где он находится. Для этого в Java используется интерфейс Path.
import java.nio.file.Path;
import java.nio.file.Paths;
// Способ 1: Path.of() — современный (Java 11+)
Path path1 = Path.of("folder", "file.txt");
// Способ 2: Paths.get() — старый (Java 7+)
Path path2 = Paths.get("folder", "file.txt");
// Способ 3: из строки
Path path3 = Path.of("/home/vika/documents/report.txt");
Path path4 = Path.of("C:\\Users\\Vika\\Documents\\report.txt"); // Windows
// Способ 4: объединение путей
Path base = Path.of("/home/vika");
Path full = base.resolve("documents/report.txt"); // /home/vika/documents/report.txtPath path = Path.of("/home/vika/documents/report.txt");
System.out.println(path.getFileName()); // "report.txt"
System.out.println(path.getParent()); // "/home/vika/documents"
System.out.println(path.getRoot()); // "/"
System.out.println(path.getNameCount()); // 4 (home, vika, documents, report.txt)
System.out.println(path.getName(0)); // "home"
System.out.println(path.subpath(1, 3)); // "vika/documents"
// Расширение файла (нет встроенного метода, но можно так):
String fileName = path.getFileName().toString();
String extension = fileName.substring(fileName.lastIndexOf('.') + 1); // "txt"
// Абсолютный и относительный пути
Path relative = Path.of("documents/report.txt");
Path absolute = relative.toAbsolutePath(); // /текущая/директория/documents/report.txt
Path normalized = path.normalize(); // убирает . и ..
// Проверки
System.out.println(path.isAbsolute()); // true
System.out.println(path.startsWith("/home")); // true
System.out.println(path.endsWith(".txt")); // true
// Преобразование между Path и File
File file = path.toFile(); // Path → File
Path pathBack = file.toPath(); // File → Path| Характеристика | File (старое) | Path (новое) |
|---|---|---|
| Пакет | java.io | java.nio.file |
| Появилось в | Java 1.0 | Java 7 (NIO.2) |
| Методы | Ограниченные | Богатый API |
| Символические ссылки | Плохая поддержка | ✅ Полная поддержка |
| Метаданные файла | Ограниченно | ✅ Files.readAttributes() |
| Рекомендация | ❌ Избегай | ✅ Используй |
💡 Правило: в новом коде всегда используй Path, а не File. File оставлен для обратной совместимости.
Класс Files предоставляет статические методы для работы с файлами:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
Path path = Path.of("report.txt");
// Проверки
boolean exists = Files.exists(path);
boolean notExists = Files.notExists(path);
boolean isFile = Files.isRegularFile(path);
boolean isDirectory = Files.isDirectory(path);
boolean isReadable = Files.isReadable(path);
boolean isWritable = Files.isWritable(path);
boolean isExecutable = Files.isExecutable(path);
long size = Files.size(path); // размер в байтах
// Чтение атрибутов
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("Размер: " + attrs.size());
System.out.println("Создан: " + attrs.creationTime());
System.out.println("Изменён: " + attrs.lastModifiedTime());
System.out.println("Это файл: " + attrs.isRegularFile());
System.out.println("Это директория: " + attrs.isDirectory());Path file = Path.of("newfile.txt");
Path dir = Path.of("newdir");
// Создание файла
Files.createFile(file);
// Создание директории
Files.createDirectory(dir);
// Создание вложенных директорий (как mkdir -p)
Files.createDirectories(Path.of("a/b/c/d"));
// Копирование
Files.copy(
Path.of("source.txt"),
Path.of("destination.txt"),
StandardCopyOption.REPLACE_EXISTING // перезаписать, если существует
);
// Перемещение (переименование)
Files.move(
Path.of("old.txt"),
Path.of("new.txt"),
StandardCopyOption.REPLACE_EXISTING
);
// Удаление файла
Files.delete(file); // бросает исключение, если файл не найден
Files.deleteIfExists(file); // не бросает исключение
// Удаление директории (должна быть пуста!)
Files.delete(dir);Files.readString() — весь файл в строку (Java 11+)Самый простой способ для небольших файлов:
String content = Files.readString(Path.of("file.txt"));
System.out.println(content);
// С указанием кодировки
String contentUtf8 = Files.readString(Path.of("file.txt"), StandardCharsets.UTF_8);Files.readAllLines() — все строки в списокList<String> lines = Files.readAllLines(Path.of("file.txt"));
for (String line : lines) {
System.out.println(line);
}Files.lines() — поток строк (ленивое чтение)Для больших файлов — читает по одной строке, не загружая всё в память:
try (Stream<String> lines = Files.lines(Path.of("bigfile.txt"))) {
lines
.filter(line -> !line.isBlank())
.map(String::toUpperCase)
.forEach(System.out::println);
}BufferedReader — построчное чтение (классический способ)try (BufferedReader reader = Files.newBufferedReader(Path.of("file.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}Files.readAllBytes() — весь файл в массив байтДля бинарных файлов (изображения, аудио и т.д.):
byte[] bytes = Files.readAllBytes(Path.of("image.png"));| Способ | Когда использовать | Размер файла |
|---|---|---|
Files.readString() | Небольшой текстовый файл | Маленький (< 10 МБ) |
Files.readAllLines() | Нужен список строк | Маленький |
Files.lines() | Обработка через Stream API | Любой (ленивое) |
BufferedReader | Построчная обработка | Любой |
Files.readAllBytes() | Бинарные файлы | Маленький |
Files.writeString() — записать строку (Java 11+)Files.writeString(Path.of("output.txt"), "Привет, Вика!\n");
// Добавить в конец файла (append)
Files.writeString(
Path.of("output.txt"),
"Вторая строка\n",
StandardOpenOption.APPEND
);
// С кодировкой
Files.writeString(
Path.of("output.txt"),
"Текст",
StandardCharsets.UTF_8
);Files.write() — записать список строкList<String> lines = List.of("Первая строка", "Вторая строка", "Третья строка");
Files.write(Path.of("output.txt"), lines);
// С опциями
Files.write(
Path.of("output.txt"),
lines,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND
);BufferedWriter — построчная записьtry (BufferedWriter writer = Files.newBufferedWriter(Path.of("output.txt"))) {
writer.write("Первая строка");
writer.newLine(); // перенос строки (универсальный)
writer.write("Вторая строка");
}Files.write() — записать байтыbyte[] data = {72, 101, 108, 108, 111}; // "Hello" в ASCII
Files.write(Path.of("binary.dat"), data);StandardOpenOption)| Опция | Описание |
|---|---|
| CREATE | Создать файл, если не существует |
| CREATE_NEW | Создать новый файл, бросить исключение, если уже есть |
| TRUNCATE_EXISTING | Очистить файл при открытии (по умолчанию) |
| APPEND | Добавлять в конец файла |
| READ | Открыть для чтения |
| WRITE | Открыть для записи |
Path dir = Path.of("my_folder");
// Способ 1: list() — только верхний уровень (ленивый)
try (Stream<Path> entries = Files.list(dir)) {
entries.forEach(System.out::println);
}
// Способ 2: walk() — рекурсивно (ленивый)
try (Stream<Path> entries = Files.walk(dir)) {
entries
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".txt"))
.forEach(System.out::println);
}
// Способ 3: find() — рекурсивно с фильтром (ленивый)
try (Stream<Path> entries = Files.find(dir, 3, (path, attrs) ->
attrs.isRegularFile() && path.toString().endsWith(".java"))) {
entries.forEach(System.out::println);
}
// Способ 4: newDirectoryStream() — старый способ
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt")) {
for (Path entry : stream) {
System.out.println(entry);
}
}// Временный файл в системной директории
Path tempFile = Files.createTempFile("prefix-", ".txt");
// /tmp/prefix-1234567890.txt
// Временный файл в конкретной директории
Path tempInDir = Files.createTempFile(Path.of("/home/vika"), "log-", ".txt");
// Временная директория
Path tempDir = Files.createTempDirectory("myapp-");
// Не забудь удалить после использования!
Files.deleteIfExists(tempFile);Хотя для файлов удобнее NIO.2, важно понимать основы потоков — они используются в сети, консоли, сжатии и т.д.
Потоки байт (для бинарных данных):
InputStream → FileInputStream, ByteArrayInputStream, BufferedInputStream
OutputStream → FileOutputStream, ByteArrayOutputStream, BufferedOutputStream
Потоки символов (для текста):
Reader → FileReader, BufferedReader, InputStreamReader
Writer → FileWriter, BufferedWriter, OutputStreamWriter// Запись байт
try (OutputStream out = new FileOutputStream("data.bin")) {
out.write(72); // 'H'
out.write(101); // 'e'
out.write(new byte[]{108, 108, 111}); // "llo"
}
// Чтение байт
try (InputStream in = new FileInputStream("data.bin")) {
int b;
while ((b = in.read()) != -1) {
System.out.print((char) b);
}
}// Запись текста
try (Writer writer = new FileWriter("text.txt");
BufferedWriter buffered = new BufferedWriter(writer)) {
buffered.write("Привет, мир!");
buffered.newLine();
buffered.write("Вика учит Java");
}
// Чтение текста
try (Reader reader = new FileReader("text.txt");
BufferedReader buffered = new BufferedReader(reader)) {
String line;
while ((line = buffered.readLine()) != null) {
System.out.println(line);
}
}InputStreamReader / OutputStreamWriter — мост между байтами и символами// Чтение с указанием кодировки
try (InputStream in = new FileInputStream("file.txt");
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader buffered = new BufferedReader(reader)) {
String line;
while ((line = buffered.readLine()) != null) {
System.out.println(line);
}
}Без буфера каждый write() или read() обращается к диску — это медленно. Буфер накапливает данные и делает одно большое обращение.
// ❌ Медленно — 1000 обращений к диску
try (OutputStream out = new FileOutputStream("file.txt")) {
for (int i = 0; i < 1000; i++) {
out.write(i);
}
}
// ✅ Быстро — одно обращение к диску
try (OutputStream out = new BufferedOutputStream(new FileOutputStream("file.txt"))) {
for (int i = 0; i < 1000; i++) {
out.write(i); // пишется в буфер в памяти
}
} // при закрытии буфер сбрасывается на дискВсе потоки и ридеры реализуют AutoCloseable, поэтому всегда используй try-with-resources:
// ✅ ПРАВИЛЬНО
try (BufferedReader reader = Files.newBufferedReader(Path.of("file.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Ошибка чтения: " + e.getMessage());
}
// ❌ НЕПРАВИЛЬНО — можно забыть закрыть
BufferedReader reader = null;
try {
reader = Files.newBufferedReader(Path.of("file.txt"));
// ...
} finally {
if (reader != null) {
reader.close(); // может бросить своё исключение!
}
}public class FileCopier {
public static void copy(Path source, Path target) throws IOException {
// Способ 1: одной строкой (NIO.2)
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
// Способ 2: через потоки (для больших файлов)
try (InputStream in = Files.newInputStream(source);
OutputStream out = Files.newOutputStream(target,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING)) {
byte[] buffer = new byte[8192]; // 8 КБ буфер
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
}
}public class LineCounter {
public static long countLines(Path file) throws IOException {
try (Stream<String> lines = Files.lines(file)) {
return lines.count();
}
}
public static void main(String[] args) throws IOException {
long count = countLines(Path.of("bigfile.txt"));
System.out.println("Строк: " + count);
}
}public class ContentSearcher {
public static List<Path> findFilesWithWord(Path directory, String word) throws IOException {
List<Path> result = new ArrayList<>();
try (Stream<Path> files = Files.walk(directory)) {
files.filter(Files::isRegularFile)
.forEach(file -> {
try {
boolean contains = Files.lines(file)
.anyMatch(line -> line.contains(word));
if (contains) {
result.add(file);
}
} catch (IOException e) {
System.err.println("Не удалось прочитать: " + file);
}
});
}
return result;
}
}public class CsvReader {
public record Person(String name, int age, String city) {}
public static List<Person> readCsv(Path file) throws IOException {
List<Person> people = new ArrayList<>();
try (BufferedReader reader = Files.newBufferedReader(file)) {
String header = reader.readLine(); // пропускаем заголовок
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",");
if (parts.length == 3) {
people.add(new Person(
parts[0].trim(),
Integer.parseInt(parts[1].trim()),
parts[2].trim()
));
}
}
}
return people;
}
}public class SimpleLogger {
private final Path logFile;
public SimpleLogger(Path logFile) {
this.logFile = logFile;
}
public synchronized void log(String message) throws IOException {
String timestamp = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String line = "[" + timestamp + "] " + message + System.lineSeparator();
Files.writeString(
logFile,
line,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND
);
}
}public class BigFileProcessor {
public static void main(String[] args) throws IOException {
Path input = Path.of("input.txt");
Path output = Path.of("output.txt");
// Читаем, обрабатываем и записываем построчно
try (Stream<String> lines = Files.lines(input);
BufferedWriter writer = Files.newBufferedWriter(output)) {
lines
.filter(line -> !line.isBlank())
.map(String::toUpperCase)
.forEach(line -> {
try {
writer.write(line);
writer.newLine();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
}
}| Ошибка | Как правильно |
|---|---|
new File("path") в новом коде | Используй Path.of("path") |
| Забыл закрыть поток | Всегда используй try-with-resources |
Files.readString() для гигабайтного файла | Используй Files.lines() или BufferedReader |
| Не указал кодировку | Всегда указывай StandardCharsets.UTF_8 |
| path + "/file.txt" для объединения | Используй path.resolve("file.txt") |
Забыл про StandardOpenOption.APPEND | По умолчанию файл перезаписывается |
Files.delete() для непустой директории | Сначала удали содержимое рекурсивно |
Обработка IOException пустым catch | Хотя бы логируй ошибку |
// ❌ ПЛОХО
try {
Files.readString(path);
} catch (IOException e) {
// ничего не делаем — ошибка исчезает!
}
// ✅ ХОРОШО
try {
Files.readString(path);
} catch (IOException e) {
System.err.println("Ошибка чтения файла " + path + ": " + e.getMessage());
// или пробросить дальше, или залогировать через SLF4J
}InputStream/OutputStream для бинарных, Reader/Writer для текста