2010-03-08 3 views
1

Я сталкиваюсь с следующими ошибками при попытке сохранить большой файл в строку.Копирование текстового файла java в строку

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
    at java.util.Arrays.copyOf(Arrays.java:2882) 
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100) 
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:515) 
    at java.lang.StringBuffer.append(StringBuffer.java:306) 
    at rdr2str.ReaderToString.main(ReaderToString.java:52) 

Как очевидно, я выбегаю из кучи. В основном моя pgm выглядит примерно так.

FileReader fr = new FileReader(<filepath>); 
sb = new StringBuffer(); 
char[] b = new char[BLKSIZ]; 

while ((n = fr.read(b)) > 0) 
    sb.append(b, 0, n);  

fileString = sb.toString(); 

Может кто-нибудь подскажет, почему я столкнулся с ошибкой в ​​куче пространства? Благодарю.

+0

Каков размер файла? Каковы ваши настройки памяти JVM -Xms, -Xmx? Любые настройки -XX? – shoover

+0

Его около 30 МБ. Это может быть нечто большее. В принципе, то, что я написал, является клиентом, который потребляет веб-сервис. Клиент передает строку в качестве аргумента в веб-службу. В настоящее время я не изменил настройки JVM. – Deepak

ответ

1

В OP ваша программа прерывается, пока расширяется StringBuffer. Вы должны предварительно распределить это до необходимого вам размера или, по крайней мере, близко к нему. Когда StringBuffer должен расширять, ему требуется оперативная память для первоначальной емкости и новой емкости. Как сказал TomTom, ваш файл скорее всего будет 8-битным символом, поэтому он будет преобразован в 16-разрядный Unicode в памяти, поэтому он будет вдвое больше.

Программа еще не сталкивалась еще следующее удвоения - то есть в Java 6 StringBuffer.toString() выделят новый String и внутренний char[] будет скопирован снова (в некоторых более ранних версиях Java это было не так). Во время этой копии вам потребуется вдвое больше места для кучи - поэтому в этот момент, по крайней мере, в 4 раза больше, чем ваш фактический размер файлов (30 МБ * 2 для байта-> Юникод, затем 60 МБ * 2 для вызова toString() = 120 МБ) , Как только этот метод будет закончен, GC очистит временные классы.

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

Посмотрите на код своего веб-сервиса в клиенте. Он может предоставить способ использования другого класса, отличного от String - возможно, java.io.Reader, java.lang.CharSequence или специального интерфейса, например, связанного с SAX org.xml.sax.InputSource. Каждый из них может быть использован для создания класса реализации, который читает из вашего файла в кусках, поскольку вызывающие пользователи нуждаются в нем вместо того, чтобы сразу загрузить весь файл.

Например, если маршруты обработки веб-сервисов могут принимать CharSequence, тогда (если они написаны хорошо), вы можете создать специальный обработчик для возврата только одного символа за раз из файла, но буфера ввода. См. Аналогичный вопрос: How to deal with big strings and limited memory.

1

По умолчанию Java начинается с очень малой максимальной кучи (по крайней мере, 64 М на Windows). Возможно ли, что вы пытаетесь прочитать слишком большой файл?

Если это так, вы можете увеличить кучу с параметром JVM -Xmx256M (установить максимальную кучу до 256 МБ)

Я попытался запустить слегка модифицированную версию кода:

public static void main(String[] args) throws Exception{ 
    FileReader fr = new FileReader("<filepath>"); 
    StringBuffer sb = new StringBuffer(); 
    char[] b = new char[1000]; 
    int n = 0; 
    while ((n = fr.read(b)) > 0) 
     sb.append(b, 0, n);  

    String fileString = sb.toString(); 
    System.out.println(fileString); 
} 

на небольшой файл (2 КБ), и он работал, как ожидалось. Вам нужно будет установить параметр JVM.

+0

@ Kris. Благодарю. Моя программа работает и для небольших файлов. Просто я не могу настроить параметры JVM, поскольку этот кусок кода идет в клиенте, который принимает файлы с переменным размером, и в идеале должен преобразовывать их в String, передавать их в веб-службу. – Deepak

2
  • Вы выделяете небольшой StringBuffer, который становится длиннее и длиннее. Предопределите в соответствии с размером файла, и вы также будете намного быстрее.

  • Обратите внимание, что java - это Unicode, строка, вероятно, нет, поэтому вы используете ... в два раза больше размера в памяти.

  • В зависимости от VM (32 бит? 64 бит?) И установленных пределов (http://www.devx.com/tips/Tip/14688) у вас может быть недостаточно памяти. Насколько велик файл на самом деле?

+0

Файл составляет около 30 МБ.Я попробовал preallocating StringBuffer с размером файла в байтах, но он не выделял бы столько. – Deepak

+1

@ Deepak: если вы не можете предварительно распределить ожидаемый размер, вы, конечно, не сможете его прочитать постепенно. Вы должны увеличить размер кучи (и, как сказал TomTom, это будет как минимум вдвое, поэтому для вашего 30-мегабайтного файла потребуется не менее 60 МБ кучи). Обратите внимание, что когда вы конвертируете это в строку ('StringBuffer.toString()'), многие текущие реализации создают новый 'String', что означает, что вам нужно снова удвоить (т. Е. Пустое место на 120 МБ). Или вы можете сделать это постепенно. –

4

У вас заканчивается память, потому что, как вы написали свою программу, она требует хранения всего произвольно большого файла в памяти. У вас есть 2 варианта:

  • Вы можете увеличить объем памяти, передавая ключи командной строки для виртуальной машины Java:

    java -Xms<initial heap size> -Xmx<maximum heap size> 
    
  • Вы можете переписать логику так, что она имеет дело с данными файла, как это потоки в, тем самым сохраняя низкий уровень памяти вашей программы.

Я рекомендую второй вариант. Это больше работы, но это правильный путь.

EDIT: Для того, чтобы определить значения по умолчанию для вашей системы для начального и максимального размера кучи, вы можете использовать этот фрагмент код (который я stole from a JavaRanch thread):

public class HeapSize {  
    public static void main(String[] args){  
     long kb = 1024; 
     long heapSize = Runtime.getRuntime().totalMemory();  
     long maxHeapSize = Runtime.getRuntime().maxMemory(); 
     System.out.println("Heap Size (KB): " + heapSize/1024); 
     System.out.println("Max Heap Size (KB): " + maxHeapSize/1024); 
    }  
} 
+0

Размер кучи (KB): 81280 Max Heap Размер (KB): 83392 – Deepak

+0

@Asaph - Проблема с настройкой heapsize заключается в том, что я использую этот код в клиенте, который потребляет веб-сервис. Клиент берет файл, преобразует его в строку и передает его вебсервису. Поэтому я сомневаюсь, насколько полезной была опция JVM.? Благодарю. – Deepak

+0

Мне было бы интересно узнать больше о втором варианте. Вы предлагаете какой-то механизм синхронизации? – Deepak

1

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

  • создать StringBuffer с начальной емкостью размер строки вы читаете
  • закрыть FileReader в конце: fr.close();
1

Попытка прочитать произвольно большой файл в основной памяти в приложении плохой дизайн. Период. Никакие настройки JVM-настроек/и т. Д. Не помогут решить эту проблему. Я рекомендую вам сделать перерыв и сделать некоторые поисковые запросы и прочитать о том, как обрабатывать потоки в java - вот хороший tutorial и вот еще один good tutorial, чтобы вы начали.