Преобразование в массив, ориентированный на столбцы, в Java
Хотя у меня есть Java в названии, это может быть для любого языка OO. Я хотел бы узнать несколько новых идей по улучшению производительности того, что я пытаюсь сделать.
У меня есть метод, который постоянно получает массив Object[]. Мне нужно разделить Объекты в этом массиве по нескольким массивам (List или что-то), чтобы у меня был независимый список для каждого столбца всех массивов, которые получает метод.
Пример:
List<List<Object>> column-oriented = new ArrayList<ArrayList<Object>>();
public void newObject(Object[] obj) {
for(int i = 0; i < obj.length; i++) {
column-oriented.get(i).add(obj[i]);
}
}
Примечание: для простоты я пропустил инициализацию объектов и прочего.
Код, который я показал выше, работает медленно. Я уже попробовал несколько других вещей, но хотел бы услышать некоторые новые идеи.
Как бы вы сделали это, зная, что это очень чувствительно к производительности?
РЕДАКТИРОВАТЬ:
Я проверил несколько вещей и обнаружил, что:
Вместо использования ArrayList (или любой другой коллекции) я обернул массив Object[] в другой объект для хранения отдельных столбцов. Если этот массив достигает своей емкости, я создаю другой массив с удвоенным размером и копирую содержимое из одного в другое, используя System.copyArray. Удивительно (по крайней мере для меня) это быстрее, чем использование ArrayList для хранения внутренних столбцов...
4 ответа
Ответ зависит от данных и профиля использования. Сколько данных у вас в таких коллекциях? Каковы пропорции чтения / записи (добавление массива объектов)? Это влияет на то, какая структура внутреннего списка лучше, и на множество других возможных оптимизаций.
Самый быстрый способ скопировать данные - это вообще избежать копирования. Если вы знаете, что obj
массив не модифицируется кодом вызывающей стороны (это важное условие), один из возможных приемов - реализовать пользовательский List
класс для использования в качестве внутреннего списка. Внутренне вы будете хранить общий List<Object[]>
, При каждом вызове мы просто добавляем новый массив в этот список. Пользовательский класс внутреннего списка будет знать, какой столбец он представляет (пусть это будет n
) и когда его просят отдать предмет на позицию m
Буду транспонировать m
а также n
и запросить внутреннюю структуру, чтобы получить internalArray.get(m)[n]
, Эта реализация небезопасна из-за ограничений на вызывающую сторону, о которых легко забыть, но при некоторых условиях она может быть быстрее (однако при других может быть медленнее).
Я бы попробовал использовать LinkedList для внутреннего списка, потому что он должен иметь лучшую производительность для вставок. Может быть, может помочь обертывание объекта Object в коллекцию и использование addAll.
Использовать LinkedList
для реализации списков столбцов. Он растет линейно с данными и равен O(1). (Если вы используете ArrayList, он должен время от времени изменять размер внутреннего массива).
После сбора значений вы можете преобразовать эти связанные списки в массивы. Если N - количество строк, вы перейдете от удержания 3*N ссылок для каждого списка (каждый LInkedList имеет prevRef/nextRef/itemRef) до только N ссылок.
Было бы неплохо иметь массив для хранения разных списков столбцов, но, конечно, это не большое улучшение, и вы можете сделать это, только если заранее знаете количество столбцов.
Надеюсь, поможет!
Тестыредактирования и теория показывают, что ArrayList лучше по амортизированной стоимости, то есть общая стоимость делится на количество обработанных элементов... так что не следуйте моим "советам":)
ArrayList может быть медленным из-за копирования массивов (он использует тот же подход, что и ваша самописная коллекция).
В качестве альтернативного решения вы можете сначала попытаться сохранить строки и создать столбцы при необходимости. Таким образом, копирование внутренних массивов в списке сводится к минимуму.
Пример:
//Notice: You can use a LinkedList for rows, as no index based access is used.
List<Object[]> rows =...
List<List<Object>> columns;
public void processColumns() {
columns = new ArrayList<List<Object>>();
for(Object[] aRow : rows){
while (aRow.size() > columns.size()){
//This ensures that the ArrayList is big enough, so no copying is necessary
List<Object> newColumn = new ArrayList<Object>(rows.size())
columns.add(newColumn);
}
for (int i = 0; i < aRow.length; i++){
columns.get(i).add(aRow[i]);
}
}
}
В зависимости от количества столбцов, все еще возможно, что внешний список копирует массивы внутри, но обычные таблицы содержат гораздо больше строк, чем столбцов, поэтому это должен быть только небольшой массив.