Копирование текстового файла Java в строку - PullRequest
1 голос
/ 08 марта 2010

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

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)

Как видно, мне не хватает места в куче. В основном мой ПГМ выглядит примерно так.

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();

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

Ответы [ 7 ]

4 голосов
/ 08 марта 2010

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

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

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

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

РЕДАКТИРОВАТЬ: Чтобы определить значения по умолчанию вашей системы для начального и максимального размера кучи, вы можете использовать этот фрагмент кода (который я украл из потока JavaRanch ):

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);  
     }    
}
2 голосов
/ 08 марта 2010
  • Вы выделяете небольшой StringBuffer, который становится длиннее и длиннее. Предварительно распределите в соответствии с размером файла, и вы также будете НАМНОГО быстрее.

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

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

1 голос
/ 09 марта 2010

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

1 голос
/ 08 марта 2010

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

Программа еще даже не встречала следующего удвоения - то есть StringBuffer.toString() в Java 6 выделит новый 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, то (если они написаны правильно) вы можете создать специальный обработчик, который будет возвращать только один символ за раз из файла - но буферизует ввод. Смотрите этот похожий вопрос: Как бороться с большими строками и ограниченной памятью .

1 голос
/ 08 марта 2010

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

  • создайте свой StringBuffer с начальной емкостью, равной размеру строки, которую вы читаете
  • закройте файл-ридер в конце: fr.close ();
1 голос
/ 08 марта 2010

У Криса есть ответ на вашу проблему.

Вы также можете посмотреть java commons fileutils 'readFileToString , который может быть более эффективным.

1 голос
/ 08 марта 2010

По умолчанию Java запускается с очень маленькой максимальной кучей (не менее 64M в 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...