Урок 10 из 11 уверенный около 40 мин
Файлы и HTTP-клиент: читаем, пишем, запрашиваем
Files.readString и writeString, обход каталога, HttpClient из стандартной библиотеки и разбор ответа без внешних зависимостей.
Файлы: коротко о главном
Прочитать текстовый файл целиком в современной Java — одна строка. Класс Files появился ещё в Java 7, но методы readString и writeString добавили только в одиннадцатой версии, и во многих учебниках их до сих пор нет.
import java.nio.file.*;
var path = Path.of("notes.txt");
Files.writeString(path, """
Первая строка
Вторая строка
""");
String content = Files.readString(path);
System.out.print(content);
Кодировка по умолчанию — UTF-8 независимо от системных настроек, поэтому русский текст читается одинаково на Windows и Linux. Класс Path собирает путь из частей без ручной склейки разделителей: Path.of("docs", "2026", "otchet.txt").
Когда файл большой
Читать целиком стоит только небольшие файлы: readString держит весь текст в памяти. Для журналов и выгрузок используйте построчный поток и обязательно закрывайте его через try-with-resources:
try (var lines = Files.lines(Path.of("access.log"))) {
long errors = lines.filter(line -> line.contains(" 500 ")).count();
System.out.println("Ошибок сервера: " + errors);
} catch (java.io.IOException e) {
System.err.println("Не удалось прочитать журнал: " + e.getMessage());
}
Files.lines держит открытый файловый дескриптор, и без try он останется висеть. Это одно из немногих мест, где поток обязан закрываться.
Полезные операции с файловой системой
Files.exists(path); // существует ли
Files.createDirectories(Path.of("out/2026")); // создать вместе с родителями
Files.copy(path, Path.of("copy.txt"), StandardCopyOption.REPLACE_EXISTING);
Files.size(path); // размер в байтах
Files.deleteIfExists(path); // удалить, если есть
// Дописать в конец, а не перезаписать
Files.writeString(path, "ещё строка\n", StandardOpenOption.CREATE, StandardOpenOption.APPEND);
// Обойти каталог: walk рекурсивно, list — только верхний уровень
try (var files = Files.walk(Path.of("src"))) {
files.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(System.out::println);
}
Временные файлы создавайте через Files.createTempFile: он сам выберет каталог и права доступа, а имя не столкнётся с чужим.
HttpClient вместо сторонних библиотек
С Java 11 в стандартной библиотеке есть полноценный HTTP-клиент с поддержкой HTTP/2, редиректов и асинхронных запросов. Для большинства задач внешняя библиотека больше не нужна.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
var client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
var request = HttpRequest.newBuilder(URI.create("https://school-fortuna.ru/robots.txt"))
.header("Accept", "text/plain")
.timeout(Duration.ofSeconds(15))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode()); // 200
System.out.println(response.body());
Клиент создают один раз на приложение и переиспользуют: он держит пул соединений. Два таймаута отвечают за разное: connectTimeout ограничивает установление соединения, timeout у запроса — весь обмен.
Отправка данных и заголовки
var body = """
{"login":"anna","lesson":3}
""";
var post = HttpRequest.newBuilder(URI.create("https://example.org/api/progress"))
.header("Content-Type", "application/json; charset=utf-8")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
var result = client.send(post, HttpResponse.BodyHandlers.ofString());
if (result.statusCode() / 100 != 2) {
throw new IllegalStateException("Сервер ответил " + result.statusCode());
}
Проверять код ответа обязательно: клиент не считает 404 или 500 ошибкой и не бросает исключение — для него это нормальный ответ. Исключение будет только при сетевом сбое или таймауте.
Скачивание прямо в файл и асинхронный запрос
// Тело ответа сразу на диск, без промежуточной строки
var download = client.send(request, HttpResponse.BodyHandlers.ofFile(Path.of("robots.txt")));
System.out.println("Сохранено в " + download.body());
// Асинхронно: метод возвращает управление сразу
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(text -> System.out.println("Получено символов: " + text.length()))
.join(); // в примере ждём результат, в приложении — нет
Три правила безопасности
- Никогда не подставляйте пользовательский ввод в путь без проверки:
Path.of(base).resolve(name).normalize().startsWith(base)защищает от выхода за каталог через «..». - Не логируйте заголовок
Authorizationи тело запроса с паролями — журналы живут дольше, чем кажется. - Ставьте таймауты всегда. Запрос без таймаута может ждать бесконечно и заблокировать поток.
Пример целиком: сохранить страницу и посчитать строки
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
public class Fetch {
public static void main(String[] args) throws Exception {
var url = URI.create("https://school-fortuna.ru/sitemap.xml");
var target = Path.of("sitemap.xml");
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(url).timeout(Duration.ofSeconds(20)).GET().build();
var response = client.send(request, HttpResponse.BodyHandlers.ofFile(target));
if (response.statusCode() != 200) {
System.err.println("Ответ " + response.statusCode());
return;
}
try (var lines = Files.lines(target)) {
System.out.println("Строк в файле: " + lines.count());
}
}
}
Запускается одной командой java Fetch.java — ни сборки, ни зависимостей.
Попробуйте сами
- Напишите программу, которая обходит каталог с исходниками и печатает пять самых больших файлов с их размерами.
- Запросите
https://school-fortuna.ru/robots.txtи выведите только заголовки ответа:response.headers().map(). - Добавьте в пример
Fetchобработку случая, когда сервер недоступен: поймайтеjava.io.IOExceptionи напечатайте понятное сообщение вместо стека вызовов.