улучшить код Java, чтобы избежать исключения кучи из памяти - PullRequest
2 голосов
/ 17 февраля 2012

У меня есть следующий фрагмент кода, где я читаю большой файл, используя объект stringBuffer, выполняя некоторые операции, создавая временные объекты byte [], и из-за этого я получаю исключение нехватки памяти, когда файл имеет большой размер скажем 16 мб.

StringBuffer dataBuffer;
ArrayList<byte[]> sourceFragments;
ArrayList<BitSet> sourceBits = new ArrayList<BitSet>();
dataBuffer = eHelper.readFile(encoder.getFileName());
sourceFragments = eHelper.fragmentFile(dataBuffer.toString());
             /*
     * converting byte[] to BitSet
              the below loop is run 128 times
     */
    Iterator<byte[]> iter = sourceFragments.iterator();
    while (iter.hasNext()) {
        byte[] temp = iter.next();
                    // temp.length will return 128 KB
        sourceBits.add(eHelper.byteArrayToBitSet(temp));
    }

Я удивляюсь, если для меня есть способ предотвратить возникновение этого исключения из памяти. Я не рассматриваю возможность увеличения пространства кучи, я использую пространство кучи по умолчанию на 32-разрядной машине. Можно ли как-нибудь уменьшить количество создаваемых временных объектов, чтобы избежать исключения outOfMemory

edit1:

Я сделал следующие изменения в коде, где я не загружаю весь файл в память в виде строки, я не создаю массив byte [], но читаю непосредственно из файла и преобразую его непосредственно в массив arrayList из BITSET. Это немного помогло, когда я могу работать с 20 МБ файлами, мне интересно, можно ли еще продвинуть это для работы с макс. 30 МБ файлами?

edit2:

Я изменил исходный код следующим образом: я удалил все избыточные типы данных, которые я создал public ArrayList фрагментSourceData (имя файла файла) { RandomAccessFile r; ArrayList sourceBits = new ArrayList ();

    try {
        r= new RandomAccessFile(filename, "r");
        System.out.println(r.length());
        encoder.setSourceFileLength((int)r.length());
        int fragmentSize = encoder.calculateFragmentSize();
        System.out.println(fragmentSize);
        encoder.setFragmentSize(fragmentSize);
        encoder.setParameters();

        byte[] b = new byte[fragmentSize] ;

            long new_pos=0;
            int i=0;
            while(new_pos<=encoder.getSourceFileLength()){
                i++;
                r.read(b ,0, fragmentSize );
                 new_pos=fragmentSize*i;
                 r.seek(new_pos);
                 sourceBits.add(BitSet.valueOf(b));
                  }
               r.close();
               b=null;

    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }


    return sourceBits;
}

}

Ответы [ 5 ]

1 голос
/ 17 февраля 2012
  • Почему вы не можете увеличить память?
  • Все, что вы делаете с dataBuffer, это вызываете toString для него, вы можете попробовать сделать метод фрагмента () для получения имени файла и возвратаВ sourceFragements вы сохраните память как для буфера данных, так и для строки, созданной в toString ().
  • Вы также можете избежать создания массива sourceFragments и вместо этого попытаться напрямую создавать исходные биты из каждого прочитанного вами байта [].
1 голос
/ 17 февраля 2012

Кажется очевидным, но почему вы читаете весь файл в память? Почему бы не прочитать его 128 КБ за раз?

Также не очевидно, что делает fragmentFile. Как он преобразует строку в byte[] фрагменты?

0 голосов
/ 17 февраля 2012

Чтение всего файла в буфер строк кажется пустой тратой памяти, если только вам это не понадобится позже.

Поскольку вам понадобятся все 16 МБ (даже больше в памяти) для хранения битфайл, вам придется сохранить немного памяти на этапе генерации.

Я не уверен, что это за объект eHelper.Если исходные биты должны быть фрагментированы, вы можете попробовать что-то вроде:

BITSET_MAX_SIZE = ...;

File file = new File("somefile");
int total = file.length();
InputStream in = new BufferedInputStream(new FileInputStream(file));
for (int bytesRead = 0; bytesRead < total;) {
    int currBitsetSize = Math.min(BITSET_MAX_SIZE, (total - read) * 8); // Can this be variable or should it be padded?
    BitSet bitset = new Bitset(currBitsetSize);
    for (int bitsetIndex = 0; bitsetIndex < currBitsetSize; bitsetIndex += 8) {
        int currByte = in.read();
        bytesRead++;
        for (int bitPos = 0; bitPos < 8; bitPos++) {
            if ((currByte & (1 << i)) > 0) {
                bitset.set(bitsetIndex + i); // Set the position to 1
            }
        }
    }
    sourceBits.add(bitset);
}
in.close();

Я не пробовал это сам, но что-то подобное может сработать.Извините, если это не самый красивый пример.

Возможно, вы не можете использовать новый FileInputStream напрямую в зависимости от источника, но вы должны получить входной поток и читать из него побайтово.безусловно, будет улучшено, так как это не будет самым эффективным.Вы, вероятно, захотите читать с in.read (буфер byte [], int byteOffset, int byteCount).

0 голосов
/ 17 февраля 2012

Ну, предел памяти по умолчанию может быть 64M (это зависит от вашей JVM), поэтому, если вы читаете файл 16M в память, затем преобразуете его в List<byte[]>, также требующий 16M, а затем конвертируете в List<BitSet>, что также потребует 16M, тогда вы, безусловно, раздвигаете предел, поскольку, вероятно, происходят другие вещи, которые также требуют некоторой памяти.

После того, как содержимое dataBuffer было преобразовано в List<byte[]>, вы можете явно установить для dataBuffer значение null. Затем в цикле вместо использования итератора вы можете зациклить List по старому способу, позволяя вам явно установить для каждого элемента значение null после преобразования его в BitSet. Давление памяти должно запустить цикл ГХ, который очистит эти неиспользуемые структуры данных.

0 голосов
/ 17 февраля 2012

В коде есть несколько улучшений. Давайте сосредоточимся вокруг петли.

Iterator<byte[]> iter = sourceFragments.iterator();
while (iter.hasNext()) {
   byte[] temp = iter.next();
   // temp.length will return 128 KB
   sourceBits.add(eHelper.byteArrayToBitSet(temp));
}

нет необходимости получать итератор для ArrayList sourceFragments. Вы можете преобразовать цикл while в цикл для и просто цикл для каждой записи в ArrayList . Изменения будут выглядеть следующим образом.

for(byte[] val : sourceFragments){
   sourceBits.add(eHelper.byteArrayToBitSet(val));
}
...