Неожиданная сложность общих методов (размер) в Java Collections Framework?

недавно я был удивлен тем, что некоторые коллекции не имеют постоянной работы время метод size().

в то время как я узнал, что параллельные реализации коллекций сделали некоторые компромиссы в качестве компромисса для усиления параллелизма (размер O(n) в ConcurrentLinkedQueue, ConcurrentSkipListSet, LinkedTransferQueue и т. д.) хорошей новостью является то, что это должным образом задокументировано в документации API.

что меня беспокоит, так это производительность метода размер представлений, возвращаемых методами некоторых коллекций. Например, TreeSet.хвостовая часть возвращает представление части резервного набора, элементы которого больше или равна fromElement. Что меня очень удивило, так это то, что размер вызова возвращаемого SortedSet является линейным по времени, то есть O(n). По крайней мере, это то, что мне удалось выкопать из исходного кода OpenJDK: В TreeSet реализуется как оболочка над TreeMap, а внутри TreeMap существует класс EntrySetView, метод размера которого следующим образом:

abstract class EntrySetView extends AbstractSet<Map.Entry<K,V>> {
    private transient int size = -1, sizeModCount;

    public int size() {
        if (fromStart && toEnd)
            return m.size();
        if (size == -1 || sizeModCount != m.modCount) {
            sizeModCount = m.modCount;
            size = 0;
            Iterator i = iterator();
            while (i.hasNext()) {
                size++;
                i.next();
            }
        }
        return size;
    }

    ....
}

это означает, что первый раз размер вызывается O (n), а затем он кэшируется до тех пор, пока резервная карта не будет изменена. Я не смог найти этот факт в документации API. Более эффективной реализацией будет O (log n) с компромиссом памяти в кэшировании размеров поддерева. Поскольку такие компромиссы делаются для избежания дублирования кода (TreeSet как обертка над TreeMap), я не вижу причины, по которой они не должны быть сделаны для производительности причины.

Не обращая внимания на то, что я прав или ошибаюсь в моем (очень кратком) анализе реализации OpenJDK TreeSet, я хотел бы знать, есть ли подробная и полная документация о выполнении многих таких операций, особенно тех, которые совершенно неожиданны?

1 ответов


например, TreeSet.tailSet возвращает представление части резервного набора, элементы которого больше или равна fromElement. Что меня очень удивило, так это то, что я позвонил size on returned SortedSet линейно во времени, что составляет O(n).

для меня это не удивительно. Рассмотрим это предложение из javadoc:

"возвращаемый набор поддерживается этим набором, поэтому изменения в возвращаемом наборе отражаются в этом наборе, и наоборот."

поскольку хвостовой набор является динамическим видом резервного набора, из этого следует, что его размер должен быть рассчитан динамически на практике. Альтернативный вариант потребует, чтобы при внесении изменений в резервный набор он должен был бы регулировать размеры всех существующих представлений tailset (и headset). Это сделает обновления резервного набора более дорогими, и это создаст проблему управления хранилищем. (Чтобы обновить размеры представления, набор поддержки будет нужны ссылки на все существующие наборы представлений ... и это потенциальная скрытая утечка памяти.)

теперь у вас есть точка относительно документации. Но на самом деле javadocs ничего не говорит о сложности коллекций представлений. И, действительно, он даже не документирует это TreeSet.size() is O(1)! На самом деле, он только документирует сложность add, remove и contains операции.


Я хотел бы знать, есть ли подробная и полная документация по выполнению многих таких операций, особенно совершенно неожиданных?

насколько мне известно, нет. Конечно, не от Солнца / Оракула ...