Слишком большой файл java

Java OutOfMemoryError при чтении большого текстового файла

Я новичок в Java и работаю над чтением очень больших файлов, нуждаюсь в помощи, чтобы понять проблему и решить ее. У нас есть код устаревшего кода, который нужно оптимизировать, чтобы он работал правильно. Размер файла может варьироваться от 10 до 10 гб. только проблема начинается, когда файл начинается за пределами размера 800 МБ.

InputStream inFileReader = channelSFtp.get(path); // file reading from ssh. byte[] localbuffer = new byte[2048]; ByteArrayOutputStream bArrStream = new ByteArrayOutputStream(); int i = 0; while (-1 != (i = inFileReader.read(buffer))) < bArrStream.write(localbuffer, 0, i); >byte[] data = bArrStream.toByteArray(); inFileReader.close(); bos.close(); 
java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:2271) at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113) at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93) at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140) 

В приведенном выше примере кода вы просто загружаете весь файл в ByteArrayOutputStream . Какой вариант использования? Действительно ли нужны данные всего файла в byte[] ?

Можете ли вы сообщить мне, какую версию JDK вы планируете использовать, у меня есть другое решение для JDK 8 и JDK7 или меньше.

@Luffy Луффи, имеет ли смысл отвечать на этот вопрос, не зная, почему столько данных записывается в память?

Помог ли какой-либо из приведенных ниже ответов решить вашу проблему? Если это помогло, пожалуйста, примите это, чтобы помочь тем, у кого есть подобные проблемы. Если это не помогло, пожалуйста, дайте мне знать, чтобы я мог удалить свою, чтобы люди не тратили время на это.

14 ответов

Попробуйте использовать java.nio.MappedByteBuffer.

Вы можете сопоставить содержимое файла на память, не копируя его вручную. Высокоуровневые операционные системы предлагают сопоставление памяти, а Java имеет API для использования этой функции.

Если мое понимание верное, отображение памяти не загружает весь файл целиком в память (что означает «загружается и выгружается частично по мере необходимости» ), поэтому я думаю, что 10-гигабайтный файл не будет уничтожать вашу память.

Читайте также:  Java file все методы

Несмотря на то, что вы можете увеличить предел памяти JVM, это бесполезно и выделяет огромную память, такую ​​как 10 ГБ, для обработки переполнения файлов и ресурса.

В настоящее время вы используете «ByteArrayOutputStream», который хранит внутреннюю память для хранения данных. Эта строка в вашем коде добавляет последний прочитанный фрагмент файла 2KB в конец этого буфера:

bArrStream.write(localbuffer, 0, i); 

bArrStream продолжает расти, и в итоге у вас заканчивается память.

Вместо этого вы должны реорганизовать свой алгоритм и обработать файл потоковым способом:

InputStream inFileReader = channelSFtp.get(path); // file reading from ssh. byte[] localbuffer = new byte[2048]; int i = 0; while (-1 != (i = inFileReader.read(buffer))) < //Deal with the current read 2KB file chunk here >inFileReader.close(); 

Виртуальная машина Java (JVM) работает с фиксированным верхним пределом памяти, который вы можете изменить таким образом:

например. вышеприведенная опция (-Xmx. ) устанавливает ограничение на 1024 мегабайта. Вы можете внести поправку по мере необходимости (в пределах вашей машины, ОС и т.д.). Обратите внимание, что это отличается от традиционных приложений, которые будут выделять все больше и больше памяти из ОС по требованию.

Однако лучшим решением является переработка вашего приложения, так что вам не нужно загружать весь файл в память за один раз. Таким образом, вам не нужно настраивать JVM, и вы не накладываете огромный объем памяти.

Запустите Java с параметром командной строки -Xmx, который устанавливает максимальный размер кучи.

Эта ссылка не работает для меня, не могли бы вы разместить важную информацию здесь в дополнение к ссылке?

Обязательно ли получить полный ByteArray() выходного потока?

byte[] data = bArrStream.toByteArray(); 

Лучший подход читается по строкам и записывается по строкам. Вы можете использовать BufferedReader или Scanner для чтения больших файлов, как показано ниже.

import java.io.*; import java.util.*; public class FileReadExample < public static void main(String args[]) throws FileNotFoundException < File fileObj = new File(args[0]); long t1 = System.currentTimeMillis(); try < // BufferedReader object for reading the file BufferedReader br = new BufferedReader(new FileReader(fileObj)); // Reading each line of file using BufferedReader class String str; while ( (str = br.readLine()) != null) < System.out.println(str); >>catch(Exception err) < err.printStackTrace(); >long t2 = System.currentTimeMillis(); System.out.println("Time taken for BufferedReader:"+(t2-t1)); t1 = System.currentTimeMillis(); try ( // Scanner object for reading the file Scanner scnr = new Scanner(fileObj);) < // Reading each line of file using Scanner class while (scnr.hasNextLine()) < String strLine = scnr.nextLine(); // print data on console System.out.println(strLine); >> t2 = System.currentTimeMillis(); System.out.println("Time taken for scanner:"+(t2-t1)); > > 

Вы можете заменить System.out на ByteArrayOutputStream в приведенном выше примере.

Пожалуйста, посмотрите ниже статью для более подробной информации: Прочитать большой файл

Посмотрите на связанный вопрос SE:

Попытка использования большого размера чтения буфера может составлять 10 мб, а затем проверить.

Вы не можете прочитать текстовый файл 10GB в памяти. Сначала вы должны прочитать X МБ, сделать с ним что-то, а затем прочитать следующий X МБ.

@Brian Брайан Нет, он не может. Даже под 64-битными возможен только ограниченный размер элементов в массиве.

@user2717498 user2717498 — Я просто опровергаю ваше утверждение о том, что вы не можете загрузить 10 ГБ файл в память. Вы можете сделать это, храня массивы строк, например

Проблема заключается в том, что вы делаете. Чтение целых файлов в память всегда и везде — плохая идея. Вы действительно не сможете читать 10GB файл в памяти с использованием современных технологий, если у вас нет довольно поразительного оборудования. Найдите способ их обработки по строкам, запись по записи, фрагмент с помощью куска.

Прочитайте файл итеративно linewise. Это значительно снизит потребление памяти. В качестве альтернативы вы можете использовать

предоставляется Apache Commons IO.

FileInputStream inputStream = null; Scanner sc = null; try < inputStream = new FileInputStream(path); sc = new Scanner(inputStream, "UTF-8"); while (sc.hasNextLine()) < String line = sc.nextLine(); // System.out.println(line); >// note that Scanner suppresses exceptions if (sc.ioException() != null) < throw sc.ioException(); >> finally < if (inputStream != null) < inputStream.close(); >if (sc != null)

Привет, я предполагаю, что вы читаете большой файл txt, и данные устанавливаются по строкам, используйте метод подсчета строк за строкой. Как я знаю, вы можете читать до 6 ГБ, может быть больше. Я настоятельно рекомендую вам попробовать этот подход.

// Open the file FileInputStream fstream = new FileInputStream("textfile.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(fstream)); String strLine; //Read File Line By Line while ((strLine = br.readLine()) != null) < // Print the content on the console System.out.println (strLine); >//Close the input stream br.close(); 

ByteArrayOutputStream записывается в буфер памяти. Если это действительно так, как вы хотите, чтобы он работал, тогда вам нужно определить размер кучи JVM после максимально возможного размера ввода. Кроме того, если возможно, вы можете проверить размер ввода до начала обработки, чтобы сэкономить время и ресурсы.

Альтернативный подход — это потоковое решение, в котором известно количество памяти, используемой во время выполнения (возможно, настраивается, но все еще известно до запуска программы), но если это возможно или полностью зависит от вашего домена приложения (потому что вы можете » t использовать буфер в памяти больше) и, возможно, архитектуру остальной части вашего кода, если вы не можете/не хотите ее изменять.

Вы должны увеличить размер кучи, как указано в следующем ответе:

Но помните, что время выполнения Java и ваш код занимают некоторое пространство, поэтому добавьте некоторый буфер в желаемый максимум.

не делая ничего, вы можете увеличить текущий предел в 1,5 раза. Это означает, что если вы можете обрабатывать 800 МБ, вы можете обрабатывать 1200 МБ. Это также означает, что если какой-то трюк с java -Xm . вы можете перейти к точке, где ваш текущий код может обрабатывать 7 ГБ, ваша проблема решена, потому что фактор 1.5 приведет вас к 10,5 ГБ, если у вас есть это пространство на вашем системы и что JVM может получить его.

Ошибка довольно самоописательна. Вы нажимаете ограничение на практическую память в своей конфигурации. Существует много размышлений о пределе, который вы можете иметь с JVM, я недостаточно знаю об этом, так как я не могу найти никакой официальной информации. Однако вы каким-то образом ограничены ограничениями, такими как доступный обмен, использование адресного пространства ядра, фрагментация памяти и т.д.

Теперь происходит то, что объекты ByteArrayOutputStream создаются с использованием буфера по умолчанию размером 32, если вы не поставляете какой-либо размер (это ваш случай). Всякий раз, когда вы вызываете метод write на объект, начинается встроенный механизм. openjdk реализация релиза 7u40-b43, которая, кажется, идеально сочетается с выходом вашей ошибки, использует внутренний метод ensureCapacity , чтобы проверить, что буфер достаточно места для размещения байтов, которые вы хотите записать. Если места недостаточно, для увеличения размера буфера вызывается другой внутренний метод grow . Метод grow определяет соответствующий размер и вызывает метод copyOf из класса Arrays для выполнения задания. Соответствующий размер буфера является максимальным между текущим размером и размером, необходимым для хранения всего содержимого (настоящего содержимого и нового содержимого для записи). Метод copyOf из класса Arrays (следовать по ссылке) выделяет пространство для нового буфера, копирует содержимое старого буфера к новому и верните его на grow .

Ваша проблема возникает при распределении пространства для нового буфера. После некоторого write вы попали в точку, в которой исчерпана доступная память: java.lang.OutOfMemoryError: Java heap space .

Если мы рассмотрим детали, вы читаете куски 2048. Итак

  • ваш первый напишет, чтобы увеличить размер буфера от 32 до 2048
  • ваш второй вызов удвоит его до 2 * 2048
  • ваш третий вызов займет 2 ^ 2 * 2048, вы должны время написать еще два раза перед необходимостью выделения.
  • тогда 2 ^ 3 * 2048, у вас будет время для 4 комментариев, прежде чем выделять снова.
  • в какой-то момент ваш буфер будет иметь размер 2 ^ 18 * 2048, который составляет 2 ^ 19 * 1024 или 2 ^ 9 * 2 ^ 20 (512 МБ).
  • затем 2 ^ 19 * 2048, который составляет 1024 МБ или 1 ГБ.

Что-то, что неясно в вашем описании, это то, что вы можете как-то читать до 800 МБ, но не можете выйти за его пределы. Вы должны объяснить это мне.

Я ожидаю, что ваш предел будет ровно силой 2 (или близко, если мы будем использовать мощность 10 единиц). В этой связи я ожидаю, что вы сразу начнете испытывать проблемы над одним из них: 256 МБ, 512 МБ, 1 ГБ, 2 ГБ и т.д.

Когда вы нажимаете этот предел, это не означает, что вы потеряли память, это просто означает, что нельзя выделить другой буфер, вдвое превышающий размер уже имеющегося буфера. Это наблюдение открывает возможности для улучшения вашей работы: найдите максимальный размер буфера, который вы можете выделить, и зарезервируйте его заранее, вызвав соответствующий конструктор

ByteArrayOutputStream bArrStream = new ByteArrayOutputStream(myMaxSize); 

Преимущество состоит в том, что сокращение распределения памяти фоновой памяти происходит под капотом, чтобы вы были счастливы. Делая это, вы сможете перейти к 1.5 лимиту, который у вас есть прямо сейчас. Это просто потому, что в последний раз, когда буфер был увеличен, он переместился с половины текущего размера на текущий размер, и в какой-то момент у вас в памяти был как текущий буфер, так и старый. Но вы не сможете выйти за пределы 3-х кратного предела, который у вас сейчас есть. Объяснение точно такое же.

Было сказано, что у меня нет никакого волшебного предложения, чтобы решить проблему, кроме обработки ваших данных кусками заданного размера, по одному куску за раз. Другим хорошим подходом будет использование предложения Такахико Кавасаки и использование MappedByteBuffer . Имейте в виду, что в любом случае вам понадобится не менее 10 ГБ физической памяти или swap-памяти, чтобы иметь возможность загружать файл размером 10 ГБ.

Источник

Оцените статью