Как эффективно (производительность) удалить много элементов из списка в Java? - PullRequest
29 голосов
/ 11 января 2010

У меня довольно большой список именованных элементов (> = 1 000 000 элементов) и некоторое условие, обозначенное , которое выбирает элементы для удаления, и верно для многих (возможно, половины) элементов в моем списке.

Моя цель состоит в том, чтобы эффективно удалить элементы, выбранные с помощью , и сохранить все остальные элементы, список источников может быть изменен, может быть создан новый список - лучший способ сделать это следует выбирать с учетом производительности.

Вот мой код тестирования:

    System.out.println("preparing items");
    List<Integer> items = new ArrayList<Integer>(); // Integer is for demo
    for (int i = 0; i < 1000000; i++) {
        items.add(i * 3); // just for demo
    }

    System.out.println("deleting items");
    long startMillis = System.currentTimeMillis();
    items = removeMany(items);
    long endMillis = System.currentTimeMillis();

    System.out.println("after remove: items.size=" + items.size() + 
            " and it took " + (endMillis - startMillis) + " milli(s)");

и наивная реализация:

public static <T> List<T> removeMany(List<T> items) {
    int i = 0;
    Iterator<T> iter = items.iterator();
    while (iter.hasNext()) {
        T item = iter.next();
        // <cond> goes here
        if (/*<cond>: */i % 2 == 0) {
            iter.remove();
        }
        i++;
    }
    return items;
}

Как видите, я использовал индекс предмета по модулю 2 == 0 в качестве условия удаления () - только для целей демонтажа.

Какая лучшая версия removeMany может быть предоставлена ​​и почему эта лучшая версия на самом деле лучше?

Ответы [ 12 ]

37 голосов
/ 12 января 2010

ОК, пришло время проверить результаты предложенных подходов. Вот какие подходы я протестировал (имя каждого подхода также является именем класса в моих источниках):

  1. NaiveRemoveManyPerformer - ArrayList с итератором и удалением - первая и наивная реализация, приведенная в моем вопросе.
  2. BetterNaiveRemoveManyPerformer - ArrayList с обратной итерацией и удалением от конца к фронту.
  3. LinkedRemoveManyPerformer - наивный итератор и удалить, но работает на LinkedList. Недостаток: работает только для LinkedList.
  4. CreateNewRemoveManyPerformer - ArrayList создается как копия (добавляются только сохраненные элементы), итератор используется для обхода ввода ArrayList.
  5. SmartCreateNewRemoveManyPerformer - лучше CreateNewRemoveManyPerformer - начальный размер (емкость) результата ArrayList устанавливается на окончательный размер списка. Недостаток: вы должны знать окончательный размер списка при запуске.
  6. FasterSmartCreateNewRemoveManyPerformer - еще лучше (?) SmartCreateNewRemoveManyPerformer - использовать индекс элемента (items.get(idx)) вместо итератора.
  7. MagicRemoveManyPerformer - работает на месте (без копии списка) для ArrayList и сжимает отверстия (удаленные элементы), начиная с элементов в конце списка. Недостаток: этот подход меняет порядок элементов в списке.
  8. ForwardInPlaceRemoveManyPerformer - работает на месте для ArrayList - перемещать удерживающие элементы, чтобы заполнить отверстия, в конце возвращается subList (без окончательного удаления или очистки).
  9. GuavaArrayListRemoveManyPerformer - Google Guava Iterables.removeIf используется для ArrayList - почти так же, как ForwardInPlaceRemoveManyPerformer, но выполняет окончательное удаление элементов в конце списка.

Полный исходный код приведен в конце этого ответа.

Тесты, выполняемые с различными размерами списка (от 10 000 до 10 000 000 элементов) и разными коэффициентами удаления (определяющими, сколько элементов необходимо удалить из списка).

Поскольку я писал здесь в комментариях для других ответов - я думал, что копирование элементов из ArrayList во второй ArrayList будет быстрее, чем итерация LinkedList и просто удаление элементов. Java-документация Sun гласит, что постоянный коэффициент ArrayList ниже по сравнению с постоянным коэффициентом LinkedList, но, как ни странно, в моей проблеме это не так.

На практике LinkedList с простой итерацией и удалением имеет большую производительность в большинстве случаев (этот подход реализован в LinkedRemoveManyPerformer). Обычно только MagicRemoveManyPerformer производительность сопоставима с LinkedRemoveManyPerformer, другие подходы значительно медленнее. Google Guava GuavaArrayListRemoveManyPerformer работает медленнее, чем ручной код (потому что мой код не удаляет ненужные элементы в конце списка).

Пример результатов для удаления 500 000 элементов из 1 000 000 исходных элементов:

  1. NaiveRemoveManyPerformer: тест не выполнен - ​​я не такой пациент, но он работает хуже, чем BetterNaiveRemoveManyPerformer.
  2. BetterNaiveRemoveManyPerformer: 226080 миллисекунд
  3. LinkedRemoveManyPerformer: 69 миллисекунд
  4. CreateNewRemoveManyPerformer: 246 милли (с)
  5. SmartCreateNewRemoveManyPerformer: 112 миллисекунд
  6. FasterSmartCreateNewRemoveManyPerformer: 202 миллисекунды
  7. MagicRemoveManyPerformer: 74 милли (с)
  8. ForwardInPlaceRemoveManyPerformer: 69 милли (с)
  9. GuavaArrayListRemoveManyPerformer: 118 миллисекунд

Пример результатов для удаления 1 элемента из 1 000 000 исходных элементов (первый элемент удален):

  1. BetterNaiveRemoveManyPerformer: 34 милли (с)
  2. LinkedRemoveManyPerformer: 41 миллисекунда
  3. CreateNewRemoveManyPerformer: 253 миллисекунды
  4. SmartCreateNewRemoveManyPerformer: 108 миллисекунд
  5. БыстрееСмартСоздатьНовое УдалятьМаниПерформер: 71 миллисекунда
  6. MagicRemoveManyPerformer: 43 миллисекунды
  7. ForwardInPlaceRemoveManyPerformer: 73 милли (ов)
  8. GuavaArrayListRemoveManyPerformer: 78 миллисекунд

Пример результатов для удаления 333 336 элементов из 1 000 000 исходных элементов:

  1. BetterNaiveRemoveManyPerformer: 253206 миллисекунд
  2. LinkedRemoveManyPerformer: 69 миллисекунд
  3. CreateNewRemoveManyPerformer: 245 миллисекунд
  4. SmartCreateNewRemoveManyPerformer: 111 миллисекунд
  5. Быстрее УмнееСоздатьНовое УдалитеМногоПроизводителя: 203 миллисекунды
  6. MagicRemoveManyPerformer: 69 миллисекунд
  7. ForwardInPlaceRemoveManyPerformer: 72 миллисекунды
  8. GuavaArrayListRemoveManyPerformer: 102 милли (ов)

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

  1. BetterNaiveRemoveManyPerformer: 58 милли (s)
  2. LinkedRemoveManyPerformer: 88 милли (s)
  3. CreateNewRemoveManyPerformer: 95 милли (s)
  4. FasterSmartCreateNewRemoveManyPerformer: 48 миллисекунд
  5. MagicRemoveManyPerformer: 61 миллисекунда
  6. ForwardInPlaceRemoveManyPerformer: 49 миллилитров (с) * 11

Мои окончательные выводы: используйте гибридный подход - если имеете дело с LinkedList - лучше всего использовать простую итерацию и удаление, если имеет дело с ArrayList - это зависит от важности порядка элементов - используйтеForwardInPlaceRemoveManyPerformer тогда, если порядок элементов может быть изменен - ​​лучший выбор - MagicRemoveManyPerformer,Если коэффициент удаления известен априори (вы знаете, сколько элементов будет удалено и сохранено), тогда можно будет добавить еще несколько условий, чтобы выбрать подход, который будет работать еще лучше в конкретной ситуации.Но известный коэффициент удаления не является обычным случаем ... Google Guava Iterables.removeIf является таким гибридным решением, но с несколько иным допущением (необходимо изменить исходный список, создать новый нельзя, а порядок элементов всегда имеет значение) - это наиболее распространенные предположениятак что removeIf - лучший выбор в большинстве случаев из реальной жизни.

Обратите внимание, что все хорошие подходы (наивный - это не хорошо!) достаточно хороши - любой из них должен хорошо работать в реальном приложении, нонужно избегать наивного подхода.

Наконец - мой исходный код для тестирования.

package WildWezyrListRemovalTesting;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class RemoveManyFromList {

    public static abstract class BaseRemoveManyPerformer {

        protected String performerName() {
            return getClass().getSimpleName();
        }

        protected void info(String msg) {
            System.out.println(performerName() + ": " + msg);
        }

        protected void populateList(List<Integer> items, int itemCnt) {
            for (int i = 0; i < itemCnt; i++) {
                items.add(i);
            }
        }

        protected boolean mustRemoveItem(Integer itemVal, int itemIdx, int removeFactor) {
            if (removeFactor == 0) {
                return false;
            }
            return itemIdx % removeFactor == 0;
        }

        protected abstract List<Integer> removeItems(List<Integer> items, int removeFactor);

        protected abstract List<Integer> createInitialList();

        public void testMe(int itemCnt, int removeFactor) {
            List<Integer> items = createInitialList();
            populateList(items, itemCnt);
            long startMillis = System.currentTimeMillis();
            items = removeItems(items, removeFactor);
            long endMillis = System.currentTimeMillis();
            int chksum = 0;
            for (Integer item : items) {
                chksum += item;
            }
            info("removing took " + (endMillis - startMillis)
                    + " milli(s), itemCnt=" + itemCnt
                    + ", removed items: " + (itemCnt - items.size())
                    + ", remaining items: " + items.size()
                    + ", checksum: " + chksum);
        }
    }
    private List<BaseRemoveManyPerformer> rmps =
            new ArrayList<BaseRemoveManyPerformer>();

    public void addPerformer(BaseRemoveManyPerformer rmp) {
        rmps.add(rmp);
    }
    private Runtime runtime = Runtime.getRuntime();

    private void runGc() {
        for (int i = 0; i < 5; i++) {
            runtime.gc();
        }
    }

    public void testAll(int itemCnt, int removeFactor) {
        runGc();
        for (BaseRemoveManyPerformer rmp : rmps) {
            rmp.testMe(itemCnt, removeFactor);
        }
        runGc();
        System.out.println("\n--------------------------\n");
    }

    public static class NaiveRemoveManyPerformer
            extends BaseRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            if (items.size() > 300000 && items instanceof ArrayList) {
                info("this removeItems is too slow, returning without processing");
                return items;
            }
            int i = 0;
            Iterator<Integer> iter = items.iterator();
            while (iter.hasNext()) {
                Integer item = iter.next();
                if (mustRemoveItem(item, i, removeFactor)) {
                    iter.remove();
                }
                i++;
            }
            return items;
        }

        @Override
        public List<Integer> createInitialList() {
            return new ArrayList<Integer>();
        }
    }

    public static class BetterNaiveRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
//            if (items.size() > 300000 && items instanceof ArrayList) {
//                info("this removeItems is too slow, returning without processing");
//                return items;
//            }

            for (int i = items.size(); --i >= 0;) {
                Integer item = items.get(i);
                if (mustRemoveItem(item, i, removeFactor)) {
                    items.remove(i);
                }
            }
            return items;
        }
    }

    public static class LinkedRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> createInitialList() {
            return new LinkedList<Integer>();
        }
    }

    public static class CreateNewRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            List<Integer> res = createResultList(items, removeFactor);
            int i = 0;

            for (Integer item : items) {
                if (mustRemoveItem(item, i, removeFactor)) {
                    // no-op
                } else {
                    res.add(item);
                }
                i++;
            }

            return res;
        }

        protected List<Integer> createResultList(List<Integer> items, int removeFactor) {
            return new ArrayList<Integer>();
        }
    }

    public static class SmartCreateNewRemoveManyPerformer
            extends CreateNewRemoveManyPerformer {

        @Override
        protected List<Integer> createResultList(List<Integer> items, int removeFactor) {
            int newCapacity = removeFactor == 0 ? items.size()
                    : (int) (items.size() * (removeFactor - 1L) / removeFactor + 1);
            //System.out.println("newCapacity=" + newCapacity);
            return new ArrayList<Integer>(newCapacity);
        }
    }

    public static class FasterSmartCreateNewRemoveManyPerformer
            extends SmartCreateNewRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            List<Integer> res = createResultList(items, removeFactor);

            for (int i = 0; i < items.size(); i++) {
                Integer item = items.get(i);
                if (mustRemoveItem(item, i, removeFactor)) {
                    // no-op
                } else {
                    res.add(item);
                }
            }

            return res;
        }
    }

    public static class ForwardInPlaceRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            int j = 0; // destination idx
            for (int i = 0; i < items.size(); i++) {
                Integer item = items.get(i);
                if (mustRemoveItem(item, i, removeFactor)) {
                    // no-op
                } else {
                    if (j < i) {
                        items.set(j, item);
                    }
                    j++;
                }
            }

            return items.subList(0, j);
        }
    }

    public static class MagicRemoveManyPerformer
            extends NaiveRemoveManyPerformer {

        @Override
        public List<Integer> removeItems(List<Integer> items, int removeFactor) {
            for (int i = 0; i < items.size(); i++) {
                if (mustRemoveItem(items.get(i), i, removeFactor)) {
                    Integer retainedItem = removeSomeFromEnd(items, removeFactor, i);
                    if (retainedItem == null) {
                        items.remove(i);
                        break;
                    }
                    items.set(i, retainedItem);
                }
            }

            return items;
        }

        private Integer removeSomeFromEnd(List<Integer> items, int removeFactor, int lowerBound) {
            for (int i = items.size(); --i > lowerBound;) {
                Integer item = items.get(i);
                items.remove(i);
                if (!mustRemoveItem(item, i, removeFactor)) {
                    return item;
                }
            }
            return null;
        }
    }

    public static class GuavaArrayListRemoveManyPerformer
            extends BaseRemoveManyPerformer {

        @Override
        protected List<Integer> removeItems(List<Integer> items, final int removeFactor) {
            Iterables.removeIf(items, new Predicate<Integer>() {

                public boolean apply(Integer input) {
                    return mustRemoveItem(input, input, removeFactor);
                }
            });

            return items;
        }

        @Override
        protected List<Integer> createInitialList() {
            return new ArrayList<Integer>();
        }
    }

    public void testForOneItemCnt(int itemCnt) {
        testAll(itemCnt, 0);
        testAll(itemCnt, itemCnt);
        testAll(itemCnt, itemCnt - 1);
        testAll(itemCnt, 3);
        testAll(itemCnt, 2);
        testAll(itemCnt, 1);
    }

    public static void main(String[] args) {
        RemoveManyFromList t = new RemoveManyFromList();
        t.addPerformer(new NaiveRemoveManyPerformer());
        t.addPerformer(new BetterNaiveRemoveManyPerformer());
        t.addPerformer(new LinkedRemoveManyPerformer());
        t.addPerformer(new CreateNewRemoveManyPerformer());
        t.addPerformer(new SmartCreateNewRemoveManyPerformer());
        t.addPerformer(new FasterSmartCreateNewRemoveManyPerformer());
        t.addPerformer(new MagicRemoveManyPerformer());
        t.addPerformer(new ForwardInPlaceRemoveManyPerformer());
        t.addPerformer(new GuavaArrayListRemoveManyPerformer());

        t.testForOneItemCnt(1000);
        t.testForOneItemCnt(10000);
        t.testForOneItemCnt(100000);
        t.testForOneItemCnt(200000);
        t.testForOneItemCnt(300000);
        t.testForOneItemCnt(500000);
        t.testForOneItemCnt(1000000);
        t.testForOneItemCnt(10000000);
    }
}
11 голосов
/ 11 января 2010

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

Но, если вы хотите попробовать отредактировать список на месте, эффективный способ сделать это - использовать Iterables.removeIf() из Гуавы. Если его аргумент является списком, он объединяет сохраненные элементы в направлении фронта, а затем просто обрезает конец - гораздо быстрее, чем удаляя () внутренние элементы по одному.

6 голосов
/ 11 января 2010

Удаление большого количества элементов из ArrayList - это операция O(n^2).Я бы рекомендовал просто использовать LinkedList, который более оптимизирован для вставки и удаления (но не для произвольного доступа).В LinkedList есть небольшая перегрузка памяти.

Если вам нужно сохранить ArrayList, то вам лучше создать новый список.

Обновление: сравнение с созданием нового списка:

Повторное использование того же списка приводит к тому, что основная стоимость заключается в удалении узла и обновлении соответствующих указателей в LinkedList.Это постоянная операция для любого узла.

При создании нового списка основные затраты связаны с созданием списка и инициализацией записей массива.Оба являются дешевыми операциями.Вы также можете понести расходы на изменение размера нового массива списка;Предполагая, что конечный массив больше, чем половина входящего массива.

Таким образом, если вы удалите только один элемент, то подход LinkedList, вероятно, будет быстрее.Если вы удалите все узлы, кроме одного, возможно, новый подход к списку будет быстрее.

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

Наилучший вариант - реализовать альтернативы самостоятельно и сравнить результаты при работе с типичной нагрузкой.

5 голосов
/ 11 января 2010

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

public static List<T> removeMany(List<T> items) {
    List<T> tempList = new ArrayList<T>(items.size()/2); //if about half the elements are going to be removed
    Iterator<T> iter = items.iterator();
    while (item : items) {
        // <cond> goes here
        if (/*<cond>: */i % 2 != 0) {
            tempList.add(item);
        }
    }
    return tempList;
}

РЕДАКТИРОВАТЬ: Я не проверял это, поэтому вполне может быть небольшие синтаксические ошибки.

Второе РЕДАКТИРОВАНИЕ: использование LinkedList лучше, когда вам не нужен произвольный доступ, а быстрое время добавления.

НО ...

Постоянный коэффициент для ArrayList меньше, чем для LinkedList ( Ref ). Поскольку вы можете сделать разумное предположение о том, сколько элементов будет удалено (вы сказали «около половины» в своем вопросе), добавление элемента в конец ArrayList равно O (1), пока у вас нет перераспределить его. Итак, если вы сможете сделать разумное предположение, я бы ожидал, что ArrayList будет незначительно быстрее, чем LinkedList в большинстве случаев. (Это относится к коду, который я выложил. В вашей наивной реализации, я думаю, LinkedList будет быстрее).

2 голосов
/ 12 января 2010

Извините, но во всех этих ответах не хватает смысла, я думаю: вы, вероятно, не должны и, вероятно, не должны использовать Список.

Если этот тип «запроса» является распространенным, почему бы не создать упорядоченную структуру данных, которая устраняет необходимость обхода всех узлов данных? Вы не достаточно подробно расскажете нам о проблеме, но, приведя пример, предоставив простое дерево, можно добиться цели. Для каждого элемента есть издержки вставки, но вы можете очень быстро найти поддерево, содержащее совпадающие узлы, и поэтому вы избегаете большинства сравнений, которые вы делаете сейчас.

Кроме того:

  • В зависимости от конкретной проблемы и конкретной структуры данных, которую вы настроили, вы можете ускорить удаление - если узлы, которые вы хотите уничтожить, уменьшаются до поддерева или чего-то в этом роде, вы просто drop это поддерево, вместо обновления целого множества узлов списка.

  • Каждый раз, когда вы удаляете элемент списка, вы обновляете указатели - например, lastNode.next и nextNode.prev или что-то еще, но если это оказывается, вы также хотите удалить nextNode , тогда только что вызванное вами обновление указателя выбрасывается новым обновлением.)

2 голосов
/ 11 января 2010

Я полагаю, что создание нового списка, а не изменение существующего списка, будет более производительным, особенно когда количество элементов так велико, как вы указываете. Это предполагает, что ваш список ArrayList, а не LinkedList. Для некруглых LinkedList вставка равна O (n), но удаление в существующей позиции итератора равно O (1); в этом случае ваш наивный алгоритм должен быть достаточно производительным.

Если список не является LinkedList, стоимость смещения списка при каждом вызове remove(), вероятно, является одной из самых дорогих частей реализации. Для списков массивов я бы хотел использовать:

public static <T> List<T> removeMany(List<T> items) {
    List<T> newList = new ArrayList<T>(items.size());
    Iterator<T> iter = items.iterator();
    while (iter.hasNext()) {
        T item = iter.next();
        // <cond> goes here
        if (/*<cond>: */i++ % 2 != 0) {
            newList.add(item);
        }
    }
    return newList;
}
1 голос
/ 12 января 2010

Поскольку скорость является наиболее важной метрикой, есть возможность использовать больше памяти и делать меньше воссоздания списков (как упоминалось в моем комментарии). Однако фактическое влияние на производительность будет полностью зависеть от того, как используются эти функции.

Алгоритм предполагает, что хотя бы одно из следующих условий верно:

  • все элементы исходного списка не нуждаются в проверке. Это может произойти, если мы действительно ищем первые N элементов, которые соответствуют нашему условию, а не все элементы, которые соответствуют нашему условию.
  • копировать список в новую память дороже. Это может произойти, если исходный список использует более 50% выделенной памяти, поэтому работа на месте может быть лучше или если операции с памятью окажутся медленнее (это будет неожиданным результатом).
  • штраф за скорость удаления элементов из списка слишком велик, чтобы принять все сразу, но распределение этого штрафа по нескольким операциям приемлемо, даже если общее наказание больше, чем принятие всего за один раз. Это похоже на получение ипотеки в размере 200 тыс. Долларов: оплата 1000 долларов в месяц в течение 30 лет возможна на ежемесячной основе и имеет преимущества, связанные с владением жильем и собственным капиталом, хотя общая сумма выплаты составляет 360 тыс. В течение срока действия кредита.

Отказ от ответственности: есть много синтаксических ошибок - я ничего не пытался скомпилировать.

Во-первых, создайте подкласс ArrayList

public class ConditionalArrayList extends ArrayList {

  public Iterator iterator(Condition condition)
  { 
    return listIterator(condition);
  }

  public ListIterator listIterator(Condition condition)
  {
    return new ConditionalArrayListIterator(this.iterator(),condition); 
  }

  public ListIterator listIterator(){ return iterator(); }
  public iterator(){ 
    throw new InvalidArgumentException("You must specify a condition for the iterator"); 
  }
}

Тогда нам нужны вспомогательные классы:

public class ConditionalArrayListIterator implements ListIterator
{
  private ListIterator listIterator;
  Condition condition;

  // the two following flags are used as a quick optimization so that 
  // we don't repeat tests on known-good elements unnecessarially.
  boolean nextKnownGood = false;
  boolean prevKnownGood = false;

  public ConditionalArrayListIterator(ListIterator listIterator, Condition condition)
  {
    this.listIterator = listIterator;
    this.condition = condition;
  }

  public void add(Object o){ listIterator.add(o); }

  /**
   * Note that this it is extremely inefficient to 
   * call hasNext() and hasPrev() alternatively when
   * there's a bunch of non-matching elements between
   * two matching elements.
   */
  public boolean hasNext()
  { 
     if( nextKnownGood ) return true;

     /* find the next object in the list that 
      * matches our condition, if any.
      */
     while( ! listIterator.hasNext() )
     {
       Object next = listIterator.next();
       if( condition.matches(next) ) {
         listIterator.set(next);
         nextKnownGood = true;
         return true;
       }
     }

     nextKnownGood = false;
     // no matching element was found.
     return false;
  }

  /**
   *  See hasPrevious for efficiency notes.
   *  Copy & paste of hasNext().
   */
  public boolean hasPrevious()
  { 
     if( prevKnownGood ) return true;

     /* find the next object in the list that 
      * matches our condition, if any.
      */
     while( ! listIterator.hasPrevious() )
     {
       Object prev = listIterator.next();
       if( condition.matches(prev) ) {
         prevKnownGood = true;
         listIterator.set(prev);
         return true;
       }
     }

     // no matching element was found.
     prevKnwonGood = false;
     return false;
  }

  /** see hasNext() for efficiency note **/
  public Object next()
  {
     if( nextKnownGood || hasNext() ) 
     { 
       prevKnownGood = nextKnownGood;
       nextKnownGood = false;
       return listIterator.next();
     }

     throw NoSuchElementException("No more matching elements");
  }

  /** see hasNext() for efficiency note; copy & paste of next() **/
  public Object previous()
  {
     if( prevKnownGood || hasPrevious() ) 
     { 
       nextKnownGood = prevKnownGood;
       prevKnownGood = false;
       return listIterator.previous();                        
     }
     throw NoSuchElementException("No more matching elements");
  }

  /** 
   * Note that nextIndex() and previousIndex() return the array index
   * of the value, not the number of results that this class has returned.
   * if this isn't good for you, just maintain your own current index and
   * increment or decriment in next() and previous()
   */
  public int nextIndex(){ return listIterator.previousIndex(); }
  public int previousIndex(){ return listIterator.previousIndex(); }

  public remove(){ listIterator.remove(); }
  public set(Object o) { listIterator.set(o); }
}

и, конечно, нам нужен интерфейс условия:

/** much like a comparator... **/
public interface Condition
{
  public boolean matches(Object obj);
}

И условие для проверки

public class IsEvenCondition {
{
  public boolean matches(Object obj){ return (Number(obj)).intValue() % 2 == 0;
}

и мы наконец-то готовы к тестовому коду


    Condition condition = new IsEvenCondition();

    System.out.println("preparing items");
    startMillis = System.currentTimeMillis();
    List<Integer> items = new ArrayList<Integer>(); // Integer is for demo
    for (int i = 0; i < 1000000; i++) {
        items.add(i * 3); // just for demo
    }
    endMillis = System.currentTimeMillis();
    System.out.println("It took " + (endmillis-startmillis) + " to prepare the list. ");

    System.out.println("deleting items");
    startMillis = System.currentTimeMillis();
    // we don't actually ever remove from this list, so 
    // removeMany is effectively "instantaneous"
    // items = removeMany(items);
    endMillis = System.currentTimeMillis();
    System.out.println("after remove: items.size=" + items.size() + 
            " and it took " + (endMillis - startMillis) + " milli(s)");
    System.out.println("--> NOTE: Nothing is actually removed.  This algorithm uses extra"
                       + " memory to avoid modifying or duplicating the original list.");

    System.out.println("About to iterate through the list");
    startMillis = System.currentTimeMillis();
    int count = iterate(items, condition);
    endMillis = System.currentTimeMillis();
    System.out.println("after iteration: items.size=" + items.size() + 
            " count=" + count + " and it took " + (endMillis - startMillis) + " milli(s)");
    System.out.println("--> NOTE: this should be somewhat inefficient."
                       + " mostly due to overhead of multiple classes."
                       + " This algorithm is designed (hoped) to be faster than "
                       + " an algorithm where all elements of the list are used.");

    System.out.println("About to iterate through the list");
    startMillis = System.currentTimeMillis();
    int total = addFirst(30, items, condition);
    endMillis = System.currentTimeMillis();
    System.out.println("after totalling first 30 elements: total=" + total + 
            " and it took " + (endMillis - startMillis) + " milli(s)");

...

private int iterate(List<Integer> items, Condition condition)
{
  // the i++ and return value are really to prevent JVM optimization
  // - just to be safe.
  Iterator iter = items.listIterator(condition);
  for( int i=0; iter.hasNext()); i++){ iter.next(); }
  return i;
}

private int addFirst(int n, List<Integer> items, Condition condition)
{
  int total = 0;
  Iterator iter = items.listIterator(condition);
  for(int i=0; i<n;i++)
  {
    total += ((Integer)iter.next()).intValue();
  }
}

1 голос
/ 11 января 2010

Использование Коллекции Apache Commons . В частности эта функция . Это реализовано практически так же, как люди предлагают вам его реализовать (т.е. создать новый список, а затем добавить к нему).

1 голос
/ 11 января 2010

Одна вещь, которую вы можете попробовать, - это использовать LinkedList вместо ArrayList, как и в случае ArrayList, если все элементы удалены из списка, необходимо скопировать все остальные элементы.

0 голосов
/ 12 января 2010

Вместо того, чтобы запятнать мой первый ответ, который уже довольно длинный, вот второй, связанный вариант: вы можете создать свой собственный ArrayList и пометить вещи как «удаленные». Этот алгоритм делает предположения:

  • лучше тратить время (меньшую скорость) во время строительства, чем делать то же самое во время операции удаления. Другими словами, он перемещает штраф скорости из одного места в другое.
  • лучше тратить память сейчас, и время сбора мусора после результата вычисляется, а не тратить время заранее (вы всегда застряли со временем сбора мусора ...).
  • после начала удаления элементы никогда не будут добавлены в список (в противном случае возникают проблемы с перераспределением объекта flags)

Кроме того, это, опять же, не проверено, поэтому есть синтаксические ошибки prlolly.

public class FlaggedList extends ArrayList {
  private Vector<Boolean> flags = new ArrayList();
  private static final String IN = Boolean.TRUE;  // not removed
  private static final String OUT = Boolean.FALSE; // removed
  private int removed = 0;

  public MyArrayList(){ this(1000000); }
  public MyArrayList(int estimate){
    super(estimate);
    flags = new ArrayList(estimate);
  }

  public void remove(int idx){
    flags.set(idx, OUT);
    removed++;
  }

  public boolean isRemoved(int idx){ return flags.get(idx); }
}

и итератор - для его синхронизации может потребоваться дополнительная работа, и на этот раз многие методы опущены:

public class FlaggedListIterator implements ListIterator
{
  int idx = 0;

  public FlaggedList list;
  public FlaggedListIterator(FlaggedList list)
  {
    this.list = list;
  }
  public boolean hasNext() {
    while(idx<list.size() && list.isRemoved(idx++)) ;
    return idx < list.size();
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...