Ответ
Задача: Оптимизация обработки больших CSV-файлов (1ГБ+) в Spring Boot приложении для избежания OutOfMemoryError.
Исходная проблема: Использовался BufferedReader с загрузкой всех строк в List<String>, что приводило к исчерпанию heap-памяти.
Решение: Переход на потоковую обработку с использованием Java Stream API, что позволило обрабатывать данные чанками без полной загрузки в память.
Ключевые шаги реализации:
- Потоковое чтение:
Files.lines()для построчного чтения файла. - Пропуск заголовка и парсинг:
public void processLargeFile(String filePath) throws IOException { try (Stream<String> lines = Files.lines(Paths.get(filePath), StandardCharsets.UTF_8)) { lines .skip(1) // Пропускаем строку заголовка .map(line -> line.split(",")) // Парсим CSV .forEach(this::processRow); // Обрабатываем каждую строку } } - Оптимизация (пакетная обработка): Для снижения нагрузки на БД добавили батчинг:
List<Entity> batch = new ArrayList<>(BATCH_SIZE); lines.forEach(row -> { batch.add(toEntity(row)); if (batch.size() >= BATCH_SIZE) { repository.saveAll(batch); batch.clear(); } }); if (!batch.isEmpty()) repository.saveAll(batch);
Результат:
- Потребление памяти снизилось с ~4 ГБ до стабильных ~200 МБ.
- Время обработки сократилось на 40% за счет эффективного использования ресурсов.
- Приложение стало стабильно работать с файлами любого размера.
Вывод: Для работы с большими данными в Java критически важно использовать потоковые и ленивые модели обработки, избегая загрузки всего датасета в память.
Ответ 18+ 🔞
А, слушай, вот реальная история, как мы тут один раз чуть не обосрались с памятью. Представь себе: приложение на Spring Boot, а в него, как из пулемёта, начинают загружать CSV-файлы по гигабайту. Ну, классика же, да? Сделали по-простому — BufferedReader, все строки в List<String> загнали и думали, что красавчики.
А потом — бабах! — и вылетает OutOfMemoryError. Ну, пиздец, конечно. Приложение накрылось медным тазом, как будто его хуем по лбу ударили. Оказалось, что наш "простенький" список сожрал всю heap-пассию, как голодный студент в столовой.
И тут мы такие: "Так, блядь, надо думать". А что делать-то? Нельзя же всё в память тащить, это же самоубийство. Начали копать и нашли выход — потоковая обработка через Stream API. Это, блядь, как включить холодную воду и не пытаться выпить всю реку сразу, а просто пропускать её через себя.
Вот как переделали, смотри:
- Чтение по потоку. Взяли
Files.lines()— эта штука не грузит весь файл, а читает его построчно, лениво, как я по понедельникам. - Пропустили шапку и распарсили. Первую строку (заголовок) — нахуй, а остальные — на разделители.
- И главное — обработали каждую строку отдельно, не копя их в одной куче.
Вот кусочек кода, который всё изменил. Смотри, не трогай его, он святой:
public void processLargeFile(String filePath) throws IOException {
try (Stream<String> lines = Files.lines(Paths.get(filePath), StandardCharsets.UTF_8)) {
lines
.skip(1) // Пропускаем строку заголовка
.map(line -> line.split(",")) // Парсим CSV
.forEach(this::processRow); // Обрабатываем каждую строку
}
}
Но и это ещё не всё, ёпта! Потом пришла мысль: а если мы каждую строку будем сразу пихать в базу, то мы её так заебём запросами, что она взвоет. Поэтому добавили батчинг — накопили пачку записей, а потом разом и отправили. Как будто не тычёшь пальцем в соседа каждую секунду, а накопил недовольство за месяц и высказал всё одним матом.
List<Entity> batch = new ArrayList<>(BATCH_SIZE);
lines.forEach(row -> {
batch.add(toEntity(row));
if (batch.size() >= BATCH_SIZE) {
repository.saveAll(batch);
batch.clear();
}
});
if (!batch.isEmpty()) repository.saveAll(batch);
И что в итоге, спросишь ты? А в итоге — красота, блядь!
- Память успокоилась. Вместо диких 4 ГБ она теперь скромно кушает свои 200 МБ и не дерзит.
- Скорость выросла на 40%, потому что система перестала бороться сама с собой, как два мудака в подъезде.
- И теперь можно хоть терабайтный файл закинуть — приложение будет жужжать, как холодильник ночью, и ни одного падения.
Вывод, который бьёт прямо в глаз: если работаешь с большими данными на Java, забудь как страшный сон про загрузку всего и сразу. Только потоки, только ленивая обработка, только хардкор. И тогда никакой OutOfMemoryError тебе не будет страшен, хоть обосрись.