Я сделал небольшую программу для разделения больших файлов на более мелкие с помощью Java.
Код следующий:
import java.io.*;
public class Main {
final static int DIM = 6;
final static int GB = 1024 * 1024 * 1024;
public static void write(char[][] buffer, long currentFile) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter("out" + currentFile + ".json"))) {
long wrote = 0;
for (int i = 0; i < DIM; i++) {
int firstNull = GB;
for(int j = GB - 1; j > 0; j--){
if (buffer[i][j] == '\0'){
firstNull = j;
}
}
bw.write(buffer[i], 0, firstNull);
wrote += firstNull;
}
bw.close();
System.out.println("Wrote " + wrote + " bytes to <out" + currentFile + ".json>");
}
}
public static void main(String[] args) {
System.out.println("Starting");
File file = new File("./twd.json");
long start = System.currentTimeMillis();
try {
int bufPos = 0;
int mapIdx = 0;
char[][] buffer = new char[DIM][GB];
long currentFile = 0;
BufferedReader br = new BufferedReader(new FileReader(file));
System.out.println("Config correct => Started");
String line;
while ((line = br.readLine()) != null){
if (bufPos + line.length() >= GB){
if (mapIdx == DIM - 1){
write(buffer, currentFile);
buffer = null; // Otherwise it will trigger OutOfMemoryError
buffer = new char[DIM][GB];
bufPos = 0;
double progress = GB * (currentFile + 1.0) / (file.length() / (double) DIM) * 100;
System.out.println("Status: " + progress + "%");
currentFile++;
mapIdx = 0;
}else{
mapIdx++;
bufPos = 0;
System.out.println("Map idx to " + mapIdx);
}
}
int i;
for(i = 0; i < line.length(); i++){
buffer[mapIdx][bufPos] = line.charAt(i);
bufPos++;
}
buffer[mapIdx][bufPos] = '\n';
bufPos++;
}
write(buffer, currentFile);
long end = System.currentTimeMillis();
br.close();
System.out.println("Diff: " + (end - start) / 1000 + "s");
}catch (IOException e){
System.out.println("IO Exception");
}
}
}
Проблема, с которой я столкнулся, заключается в следующем: использование памяти программой должно составлять DIM * ГБ * 2 (символ — 2 байта) ГБ. При DIM=5 все работает нормально. Если я попытаюсь увеличить его, он выдаст эту ошибку.
Я попытался передать дополнительный параметр -Xmx для увеличения максимального размера кучи. Но без всякой удачи. (Моя система имеет 48 ГБ ОЗУ, но даже попытка указать -Xmx(n)g, где n — безумно большое число (< 40), не работает.)
Заметил, что без каких-либо дополнительных параметров и при DIM=5 диспетчер задач показывает мне, что программа достигает использования 12ГБ ОЗУ.
Я что-то пропустил?
По каким-то причинам я не понимаю этот код.
Зачем вам вообще нужен этот огромный буфер? Похоже, что все, что делает ваш код, — это разбивает файл на куски размером не более 6 ГБ, гарантируя при этом, что разделение происходит при разрыве строки. Это означает, что вы узнаете, можете ли вы написать строку, как только встретите конец этой строки. Если бы вы написали строку в это время, вам нужно было бы хранить в памяти только одну строку, что было бы гораздо более эффективно с точки зрения использования памяти и не вызывало бы никаких проблем со сборкой мусора...
Пожалуйста, объясните или покажите, как именно вы указываете -Xmx
. Максимальная куча по умолчанию, если вы не укажете -Xmx
, составляет 1/4 системной оперативной памяти, а 1/4 от 48 ГБ составляет 12 ГБ, что с учетом накладных расходов и поломок достаточно для выделения 5Gx2B, но не 6Gx2B именно так, как вы испытываете.
Ваш код излишне сложен - например, часть, сканирующая каждый символ [] для поиска \0
- вы уже знаете длину, которую он содержит. Вам вообще не нужно выделять buffer
, просто записывайте построчно в файл, пока не будет достигнут предел, а затем переключитесь на следующий файл.
@meriton за сравнительный анализ производительности. Я хотел проверить, происходит ли чтение всего сразу и запись всего быстрее, чем чтение и запись построчно. Мне было интересно, что вызвало эту ошибку, несмотря на то, что я выделил в кучу любой (буквально) максимальный объем памяти. <br/> @dave_thompson_085 java Main -Xmx32g
из CMD Windows. Старался избегать использования IDE, чтобы облегчить понимание проблемы. Но даже из IntelliJ после того, как я установил ограничения кучи (правильно) для любого числа, он все равно выдает ту же ошибку с DIM = 6. Спасибо за подробности! :))
@DuncG полностью согласен! Но суть вопроса в том, чтобы выяснить, почему это дает сбой, а не в том, как сделать его более эффективным :)
@Rogue, спасибо за ответ! Извините, но я недостаточно описал ошибку. Программа вылетает, когда я пытаюсь инициализировать char[][]. В любом случае я очень ценю ваше объяснение! :)
Для общей эффективности, если ваш код ожидает других системных процессов, то вы в значительной степени оптимальны, насколько это возможно. Если вам нужно «магическое число» байтов для чтения/записи, я бы выбрал размер блока файловой системы, в которую вы записываете (для обработки которого большинство прошивок жесткого диска будет хорошо оптимизировано, и любой файл будет по крайней мере, такой размер на диске). A BufferedWriter
вполне может уже сделать это под капотом, отсюда и комментарий о том, что нужно просто писать строки, пока вы их читаете.
Я могу запустить вашу программу с DIM выше 6. Судя по вашим комментариям java Main -Xmx32g
Вам нужно поставить аргументы VM перед именем класса, иначе -Xmx32g
— это аргумент, передаваемый Main.main(String ...args)
:
java -Xmx32g Main
Еще одно предложение: если у вас все еще не работает, попробуйте более позднюю версию JDK. Я использовал JDK22 с DIM, установленным на 8, файлом размером 9 ГБ и java -Xmx20g Main
- все работает нормально.
Есть более простые способы разделить большой файл без такого большого объема памяти, но, судя по вашему комментарию, это не часть вашего вопроса — надеюсь, это поможет.
Кстати, разделение не работает.
Привет! Это определенно решило мою проблему. Интересный факт: приведенному здесь коду требуется около 530 секунд для копирования файла размером 50 ГБ. А простое чтение одной строки и запись занимает около 360 секунд! Спасибо, что поделился! :)
Сказать, что символ имеет размер 2 байта, немного упрощенно. Сам массив имеет очень незначительные накладные расходы, но передача
char[]
вWriter
(а именноBuffered
>File
>OutputStreamWriter
>StreamEncoder
) в конечном итоге обернет каждыйchar[]
вCharBuffer
и закодирует результат, так что определенно будет использовано больше. Что касается чтения, неудивительно, что ваше использование увеличилось. Что интересно,DIM=6
подвел вас больше, чем любое другое более высокое число. Гораздо более эффективным решением может быть запись файла по мере его чтения, вместо того, чтобы читать целые куски, как показано ниже.