не хватает памяти при преобразовании большого потока в строку

Я пытаюсь преобразовать большой поток (4 МБ) в строку, которую я в конечном итоге преобразую в массив JSON.

когда размер потока небольшой (в КБ), все работает нормально, в ту минуту, когда он начинает обрабатывать поток 4 МБ, у него заканчивается память

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

  public String convertStreamToString(InputStream is)
            throws IOException {

        if (is != null) {
            Writer writer = new StringWriter();

            char[] buffer = new char[1024];
            try
            {
                Reader reader = new BufferedReader(
                        new InputStreamReader(is, "UTF-8"));
                int n;
                while ((n = reader.read(buffer)) != -1) 
                {
                    writer.write(buffer, 0, n);
                }
            }
            finally 
            {
                is.close();
            }
            return writer.toString();
        } else {       
            return "";
        }
    }

Обновление: хорошо, это то, чего я достиг на данный момент, я на правильном пути? Я думаю, что я близок ... не уверен, что еще я могу закрыть или сбросить, чтобы восстановить память ..

public String convertStreamToString(InputStream is)
        throws IOException {

    String encoding = "UTF-8";
    int maxlines = 2000;
    StringWriter sWriter = new StringWriter(7168);
    BufferedWriter writer = new BufferedWriter(sWriter);
    BufferedReader reader = null;
    if (is == null) {
        return "";
    } else {     


        try {
            int count = 0;
            reader = new BufferedReader(new InputStreamReader(is, encoding));
            for (String line; (line = reader.readLine()) != null;) {
                if (count++ % maxlines == 0) {
                    sWriter.close();
                    // not sure what else to close or flush here to regain memory
                    //Log.v("Max Lines Reached", "Max Lines Reached");;
                }

                writer.write(line);


            }
            Log.v("Finished Loop", "Looping over");


    } finally {
        is.close();
        writer.close();

    }
        return writer.toString();
    }
}

person Leon Leony    schedule 22.12.2012    source источник
comment
Перво-наперво: почему бы не инвертировать условие if в начале? if (is == null) return ""; Также попробуйте использовать Jackson: у него есть потоковый API для преобразования входных потоков в JSON.   -  person fge    schedule 22.12.2012
comment
я посмотрел на Джексона и GSON, пытаясь избежать их, поскольку они требуют больше работы... это работало нормально, пока поток не стал слишком большим, чтобы с ним справиться.   -  person Leon Leony    schedule 22.12.2012
comment
Почему бы не использовать StringBuilder вместо StringWriter. Но я не уверен, решит ли это вашу проблему с памятью.   -  person MrSmith42    schedule 22.12.2012
comment
Вам либо нужно увеличить доступную память для JVM с настройкой -Xmx, либо... не загружать все это в память.   -  person Brian Roach    schedule 22.12.2012
comment
@LeonLeony: больше работы? Джексон чертовски прост! Создайте ObjectMapper, используйте его методы чтения, и все.   -  person fge    schedule 22.12.2012
comment
это проект для Android, я рассмотрел возможность увеличения кучи, но, видимо, это не очень помогает с Android или, похоже, не работает.   -  person Leon Leony    schedule 22.12.2012
comment
Когда вы используете Stringwriter и конвертируете его в строку, вы используете почти вдвое больше памяти. Почему бы не использовать изменяемые классы строк при построении строки из потока. Также вы можете построить массив JSON, когда читаете Steam.   -  person coder000001    schedule 22.12.2012


Ответы (1)


StringWriter пишет в StringBuffer внутренне. StringBuffer - это в основном оболочка вокруг массива char. Этот массив имеет определенную емкость. Когда этой емкости недостаточно, StringBuffer выделит новый больший массив char и скопирует содержимое предыдущего. В конце вы вызываете toString() для StringWriter, который снова скопирует содержимое массива char в массив char результирующей строки.

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

Тем не менее, это не исключает окончательной копии, которая происходит в toString(). Если вы имеете дело с потоками, которые могут быть большими, вам может потребоваться пересмотреть вопрос о том, действительно ли вам нужен этот входной поток как String. Использование достаточно большого массива char напрямую позволит избежать копирования и значительно сократить использование памяти.

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

person bowmore    schedule 22.12.2012
comment
спасибо .. я только что проверил это, я также удвоил емкость, это не помогло .. я не могу выйти из цикла while без сбоя нехватки памяти. - person Leon Leony; 22.12.2012
comment
новый StringWriter (1024) и новый StringWriter (5120) - person Leon Leony; 23.12.2012
comment
Если у вас есть 4 МБ данных, они не уместятся в 5120 символов. Внутренний буфер все равно нужно будет увеличить. И если вы внимательно прочитали мой ответ, то заметите, что правильная инициализация внутреннего буфера решает только половину проблемы. - person bowmore; 23.12.2012
comment
да, спасибо .. я внимательно прочитал ваш ответ, я понимаю, что это решает половину проблемы. я попытался увеличить буфер до 102400, но все равно... не повезло... - person Leon Leony; 23.12.2012
comment
Закрытие StringWriter не имеет никакого эффекта. И, в конце концов, у вас все еще есть весь ввод в StringWriter, и вы копируете его в String => использование памяти * 2. Вам действительно нужна String? Вся строка? - person bowmore; 23.12.2012
comment
да, эта же строка затем преобразуется в jsonarray obj = new JSONArray(thestring); - person Leon Leony; 23.12.2012
comment
Вы можете рассмотреть возможность разбора элементов массива по отдельности. Или, если вы также пишете исходный поток, рассмотрите возможность написания отдельных элементов. - person bowmore; 24.12.2012