Проблемы с памятью кучи на Java

Я пытаюсь запустить приведенную ниже программу, но получаю ошибку OutOfMemory во время добавления структуры цикла StringBuilder.

  1. Я пытаюсь сделать что-нибудь, чтобы уменьшить использование памяти настолько, чтобы иметь возможность читать файл CSV (более 200 000 строк, но только 3 столбца: элемент, рейтинг, пользователь).
  2. Затем я бы создал массив 2D int, в котором уникальные элементы представляют строки, уникальные пользователи представляют столбцы, а пересечение — это рейтинг.
  3. Наконец, я бы использовал StringBuilder для создания выходного CSV-файла.

Спасибо за вашу помощь и время.

      List<String> userList = new ArrayList<String>();
      List<String> itemList = new ArrayList<String>();


      FileInputStream stream = null;
      Scanner scanner = null;
      int[][] layout = new int[10672][24303];

      int indexItemList = 0;
      double temp = 0;

      try{
         stream = new FileInputStream(fileName);
         scanner = new Scanner(stream, "UTF-8");
         while (scanner.hasNextLine()){
             String line = scanner.nextLine();
             if (!line.equals("")){
                String[] elems = line.split(",");
                if (indexItemList == 0) {
                    temp = Double.valueOf(elems[1]);
                  layout[0][0] = (int)temp;
                    itemList.add(elems[0]);
                    userList.add(elems[2]);
                    indexItemList++;
                }
                else {
                    boolean itemFound = itemList.contains(elems[0]);
                    boolean userFound = userList.contains(elems[2]);

                    int indexItem = 1;
                    int indexUser = 1;
                    if ((itemFound) && (userFound)) {
                        indexItem = itemList.indexOf(elems[0]);
                        indexUser = userList.indexOf(elems[2]);
                     temp = Double.valueOf(elems[1]);
                        layout[indexItem][indexUser] = (int)temp;
                    }                    
                    else if ((itemFound) && (!userFound)) {
                        userList.add(elems[2]);
                        indexItem = itemList.indexOf(elems[0]);
                        indexUser = userList.indexOf(elems[2]);
                     temp = Double.valueOf(elems[1]);
                        layout[indexItem][indexUser] = (int)temp;
                    }
                    else if ((!itemFound) && (userFound)){
                        itemList.clear();
                        itemList.add(elems[0]);
                        indexUser = userList.indexOf(elems[2]);
                     temp = Double.valueOf(elems[1]);
                        layout[indexItemList][indexUser] = (int)temp;
                        indexItemList++;
                    }
                    else if (!((itemFound) && (userFound))) {
                        itemList.clear();
                        itemList.add(elems[0]);
                        userList.add(elems[2]);
                        indexUser = userList.indexOf(elems[2]);
                     temp = Double.valueOf(elems[1]);
                        layout[indexItem][indexUser] = (int)temp;
                        indexItemList++;
                    }   
                }
             }
         } 
         if (scanner.ioException() != null){
            throw scanner.ioException();
         }
      }
      catch (IOException e){
         System.out.println(e);
      }
      finally{
         try{
            if (stream != null){
               stream.close();
            }
         }
         catch (IOException e){
            System.out.println(e);
         }
         if (scanner != null){
            scanner.close();
         }
      }

      StringBuilder sb = new StringBuilder();

      for (int i = 0; i < layout.length; i++){
          for (int j = 0; j < layout[i].length; j++){
             sb.append(layout[i][j] + "");
             layout[i][j] = 0;
             if (j < layout[i].length - 1){
                sb.append(",");
             }
          }
          sb.append("\n");
       }

person mm321    schedule 04.10.2019    source источник
comment
Мне кажется, что вам не нужен этот StringBuilder: вы можете просто написать прямо в выходной файл.   -  person Maurice Perry    schedule 04.10.2019
comment
Какой конечный CSV вам нужен? Вы пытаетесь записать значение для каждой уникальной комбинации пользователя/элемента? Чтобы уменьшить объем памяти, занимаемой компоновщиком строк, часто записывайте в файл вместо того, чтобы пытаться удерживать в памяти весь csv.   -  person Martin'sRun    schedule 04.10.2019


Ответы (2)


В вашем файле 200 000 строк, но в вашем двумерном массиве 259 361 616 ячеек, и размер StringBuilder будет пропорционален этому числу. Все это хранить не нужно: это очень пустая матрица.

Вот что я бы сделал: при чтении входного файла я бы построил два набора строк: элементы и пользователи, а также карту, связывающую рейтинг с каждой парой (элемент, пользователь):

    Set<String> items = new TreeSet<>();
    Set<String> users = new TreeSet<>();
    Map<String,Double> ratings = new HashMap<>();
    try (InputStream stream = new FileInputStream(fileName);
            Scanner scanner = new Scanner(stream, "UTF-8")) {
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            if (!line.equals("")) {
                String[] elems = line.split(",");
                String item = elems[0];
                String user = elems[2];
                double rating = Double.parseDouble(elems[1]);
                items.add(item);
                users.add(user);
                ratings.put(item+','+user, rating);
            }
        }
    } catch (IOException e) {
        System.out.println(e);
    }

Обратите внимание, что я использовал TreeSet, чтобы обеспечить сортировку элементов, но если вас это не волнует, вы можете вместо этого использовать HashSet. Чтобы сохранить элементы в порядке появления, как вы делаете в своем коде, вы можете использовать LinkedHashSets.

Затем вы можете написать в выходной файл следующим образом:

    try (OutputStream stream = new FileOutputStream(outputName);
            Writer writer = new OutputStreamWriter(stream, "UTF-8");
            PrintWriter out = new PrintWriter(writer)) {
        for (String item: items) {
            int j = 0;
            for (String user: users) {
                Double rating = ratings.get(item+','+user);
                double r = rating == null ? 0 : rating;
                out.print(r);
                ++j;
                if (j < users.size()) {
                    out.print(',');
                }
            }
            out.println();
        }
    } catch (IOException e) {
        System.out.println(e);
    }

ОБНОВИТЬ:

В случае, если у вас есть более одной оценки для одной и той же пары (элемент, пользователь), вы сохраняете только последнюю. Вы можете рассчитать среднее значение, используя Accumulators вместо Doubles на своей карте:

public class Accumulator {
    private int count;
    private double sum;

    public void add(double value) {
        sum += value;
        ++count;
    }

    public double getAverage() {
        return count == 0 ? 0 : sum/count;
    }
}

ОБНОВЛЕНИЕ 2: Исправления

Размер StringBuilder пропорционален не размеру матрицы, а количеству элементов, умноженному на количество пользователей.

person Maurice Perry    schedule 04.10.2019

Структура матрицы, которую я пытаюсь создать

Исходный файл CSV, который я анализирую, содержит более 200 000 строк и ровно 3 столбца (пользователь, рейтинг, элемент). Я надеялся создать матрицу, похожую на прилагаемую фотографию. Причина в том, что я планировал использовать эту матрицу позже для моего метода вычисления сходства косинусов между двумя строками матрицы (которые будут входными данными метода). По сути, я бы сравнил все оценки первой строки (идентификатор пользователя № 1) с оценками второй строки (идентификатор пользователя № 2).

person mm321    schedule 08.10.2019