【问题标题】:A good Sorted List for Java一个好的 Java 排序列表
【发布时间】:2011-02-09 07:17:19
【问题描述】:

我正在为 java 寻找一个好的排序列表。谷歌搜索给了我一些关于使用 TreeSet/TreeMap 的提示。但是这些组件缺少一件事:随机访问集合中的一个元素。 例如,我想访问排序集中的第 n 个元素,但使用 TreeSet,我必须遍历其他 n-1 个元素才能到达那里。这将是一种浪费,因为我的 Set 中有多达数千个元素。

基本上,我正在寻找类似于 .NET 中排序列表的东西,能够快速添加元素、快速删除元素以及随机访问列表中的任何元素。

这种排序列表在某处实现了吗? 谢谢。

已编辑

我对 SortedList 的兴趣源于以下问题: 我需要维护一个包含数千个对象的列表(并且可以增长到数十万个)。这些对象将被持久化到数据库中。我想从整个列表中随机选择几十个元素。因此,我尝试维护一个单独的内存列表,其中包含所有对象的主键(长数字)。当从数据库中添加/删除对象时,我需要从列表中添加/删除键。我现在正在使用 ArrayList,但是当记录数量增加时,恐怕 ArrayList 不适合它。 (想象一下,每次从数据库中删除对象时,您都必须迭代数十万个元素)。回到我做 .NET 编程的时候,然后我会使用排序列表(List 是一个 .NET 类,一旦 Sorted 属性设置为 true,将保持其元素的顺序,并提供有助于删除/插入元素的二进制搜索很快)。我希望我能从 java BCL 中找到一些类似的东西,但不幸的是,我没有找到一个好的匹配。

【问题讨论】:

  • TreeSet 为您提供 log(n) 查找,而不是线性的。
  • @Stefan: TreeSet 为您提供 log(n) contains 测试(其中 n 是大小),但无法轻松访问第 i 个元素(其中 i 是任意索引) .
  • 不,不会!这就是重点,如果您愿意阅读我所说的话。这将需要 20 * log_2(4000) = 20*~12 = 240 次迭代。这在现代硬件上是可以忽略的!
  • @Stefan:感谢预优化的警告。实际上,我现在正在使用没有排序的 ArrayList。 (因为,我实际上不需要排序。我只需要排序以更快地添加、删除元素)。但如果有一个优化的解决方案,为什么不去适应它呢?我相信解决方案应该已经存在(因为 .NET 已经有很长一段时间了,所以我猜 Java 也应该有它)。
  • 我不确定 .NET 是否有这个。 List 没有 Sort 或 Sorted 属性。它确实有一个 Sort 方法。这与您在此处要求的行为不同(这与 Java Collections.sort 方法没有什么不同)。即使在 .NET 中,保持排序列表也不会缩短删除时间。

标签: java sorting


【解决方案1】:

您似乎想要一个具有非常快速删除和随机访问按索引(而不是按键)时间的列表结构。 ArrayList 为您提供后者,HashMapTreeMap 为您提供前者。

在 Apache Commons Collections 中有一个可能是您正在寻找的结构,TreeList。 JavaDoc 指定它已针对列表中任何索引处的快速插入和删除进行了优化。但是,如果您还需要泛型,这将无济于事。

【讨论】:

  • +1 表示实际上应该优于 Java API 中的集合。
  • 谢谢。这正是我正在寻找的。​​span>
  • 我认为 LinkedList 不适合这里,因为它必须迭代才能到达具有特定索引的元素。但是添加/删除更快。
  • @Sugumar TreeList 不是 LinkedList 也没有像链接列表这样的行为(请参阅提供两者之间性能比较的链接),所以我不明白你的评论。不过你是对的,LinkedList 不适合问题的要求。
  • TreeList 的缺点类似于 LinkedList 元素分散并使用更多指针,因为它似乎使用内部的 AVL 节点作为实现参见:grepcode.com/file/repo1.maven.org/maven2/org.apache.openjpa/…。你可以看到那里的一些操作根本不是很优化。所以kjellkod.wordpress.com/2012/02/25/… 在这里适用。这就是为什么你可能只使用ArrayList
【解决方案2】:

这是我正在使用的 SortedList 实现。也许这有助于解决您的问题:

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
/**
 * This class is a List implementation which sorts the elements using the
 * comparator specified when constructing a new instance.
 * 
 * @param <T>
 */
public class SortedList<T> extends ArrayList<T> {
    /**
     * Needed for serialization.
     */
    private static final long serialVersionUID = 1L;
    /**
     * Comparator used to sort the list.
     */
    private Comparator<? super T> comparator = null;
    /**
     * Construct a new instance with the list elements sorted in their
     * {@link java.lang.Comparable} natural ordering.
     */
    public SortedList() {
    }
    /**
     * Construct a new instance using the given comparator.
     * 
     * @param comparator
     */
    public SortedList(Comparator<? super T> comparator) {
        this.comparator = comparator;
    }
    /**
     * Construct a new instance containing the elements of the specified
     * collection with the list elements sorted in their
     * {@link java.lang.Comparable} natural ordering.
     * 
     * @param collection
     */
    public SortedList(Collection<? extends T> collection) {
        addAll(collection);
    }
    /**
     * Construct a new instance containing the elements of the specified
     * collection with the list elements sorted using the given comparator.
     * 
     * @param collection
     * @param comparator
     */
    public SortedList(Collection<? extends T> collection, Comparator<? super T> comparator) {
        this(comparator);
        addAll(collection);
    }
    /**
     * Add a new entry to the list. The insertion point is calculated using the
     * comparator.
     * 
     * @param paramT
     * @return <code>true</code> if this collection changed as a result of the call.
     */
    @Override
    public boolean add(T paramT) {
        int initialSize = this.size();
        // Retrieves the position of an existing, equal element or the 
        // insertion position for new elements (negative).
        int insertionPoint = Collections.binarySearch(this, paramT, comparator);
        super.add((insertionPoint > -1) ? insertionPoint : (-insertionPoint) - 1, paramT);
        return (this.size() != initialSize);
    }
    /**
     * Adds all elements in the specified collection to the list. Each element
     * will be inserted at the correct position to keep the list sorted.
     * 
     * @param paramCollection
     * @return <code>true</code> if this collection changed as a result of the call.
     */
    @Override
    public boolean addAll(Collection<? extends T> paramCollection) {
        boolean result = false;
        if (paramCollection.size() > 4) {
            result = super.addAll(paramCollection);
            Collections.sort(this, comparator);
        }
        else {
            for (T paramT:paramCollection) {
                result |= add(paramT);
            }
        }
        return result;
    }
    /**
     * Check, if this list contains the given Element. This is faster than the
     * {@link #contains(Object)} method, since it is based on binary search.
     * 
     * @param paramT
     * @return <code>true</code>, if the element is contained in this list;
     * <code>false</code>, otherwise.
     */
    public boolean containsElement(T paramT) {
        return (Collections.binarySearch(this, paramT, comparator) > -1);
    }
    /**
     * @return The comparator used for sorting this list.
     */
    public Comparator<? super T> getComparator() {
        return comparator;
    }
    /**
     * Assign a new comparator and sort the list using this new comparator.
     * 
     * @param comparator
     */
    public void setComparator(Comparator<? super T> comparator) {
        this.comparator = comparator;
        Collections.sort(this, comparator);
    }
}

这个解决方案非常灵活,并且使用了现有的 Java 函数:

  • 完全基于泛型
  • 使用 java.util.Collections 查找和插入列表元素
  • 使用自定义比较器进行列表排序的选项

一些注意事项:

  • 此排序列表不同步,因为它继承自 java.util.ArrayList。如果需要,请使用 Collections.synchronizedList(有关详细信息,请参阅 java.util.ArrayList 的 Java 文档)。
  • 最初的解决方案是基于java.util.LinkedList。为了获得更好的性能,特别是为了找到插入点(Logan 的评论)和更快的 get 操作 (https://dzone.com/articles/arraylist-vs-linkedlist-vs),这已更改为 java.util.ArrayList

【讨论】:

  • 是什么使它成为一个好的实现,并且优于提供的其他答案?请将此解释添加到您的答案中,而不仅仅是粘贴代码。
  • 为什么要扩展 LinkedList?二进制搜索将有 O(n),因为这不是随机访问集合。
  • 我猜 addAll 方法可以改进一点 - 现在它具有 O(N^2) 复杂度,但如果你可以批量插入所有未排序的元素,然后再次排序 -> 通过 Collectoins。 sort() 你可以有更好的复杂性,比如 -> O(N logN + 2N)。顺便说一句 - 我猜 LinkedList 是比 ArrayList 更好的选择,因为它的增长会降低成本。
  • @Anatoliy 过了一段时间才回复你——我喜欢你的评论。我按照您的建议修改了 addAll 方法以使用 super.addAll 。我添加了一个 if 语句,以便能够选择哪种方法更快 - addAll 或多个 add 调用。不过,值 4 只是一个猜测 - 没有进行任何测试。
  • Collectinos.binarySearch() 在链表上是 O(n),所以你的 add 并不比循环查找插入点更有效。同样的批评也适用于contains 方法。只需将 LinkedList 替换为 ArrayList 即可更快。
【解决方案3】:

冯:

对 40,000 个随机数进行排序:

0.022 秒

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;


public class test
{
    public static void main(String[] args)
    {
        List<Integer> nums = new ArrayList<Integer>();
        Random rand = new Random();
        for( int i = 0; i < 40000; i++ )
        {
            nums.add( rand.nextInt(Integer.MAX_VALUE) );
        }

        long start = System.nanoTime();
        Collections.sort(nums);
        long end = System.nanoTime();

        System.out.println((end-start)/1e9);
    }
}   

由于您很少需要排序,根据您的问题陈述,这可能比它需要的效率更高。

【讨论】:

  • 嗨 Stefan:感谢您的基准测试。但是,实际上,我真正想要的排序列表是快速删除。我什至不介意对列表进行排序,因为无论如何我都会从中随机选择元素。我对排序列表感兴趣,因为排序列表会在删除/插入元素方面提供出色的性能。现在我正在处理数千人,但我希望我的数据会增长到数十万。如果没有一个真正的排序列表,那么我认为我不能很好地相处。
  • @PhươngNguyễn 我认为 LinkedList 在删除/插入方面会比 SortedList 提供更好的性能。
【解决方案4】:

根据您使用列表的方式,使用 TreeSet 然后在最后使用 toArray() 方法可能是值得的。我有一个需要排序列表的情况,我发现 TreeSet + toArray() 比添加到数组并在最后进行合并排序要快得多。

【讨论】:

  • @Long:谢谢。你的解决方案非常好。除了具有高更改集之外,toArray() 将被多次调用。例如,如果我的集合从 4000 增长到 4100,那么我需要调用 toArray 100 次,每次迭代超过 4000 多个项目,导致 400000 额外的迭代。我正在寻找一种可以消除额外迭代的解决方案。但是,就像 Stefan Kendall 试图沟通的那样,这将是预优化。
  • 您绝对是对的,您不想每次添加内容时都这样做。我的意思是,如果您知道您将始终批量添加,那么 TreeSet + toArray() 可能会起作用。
【解决方案5】:

Java Happy Libraries 中的 SortedList 装饰器可用于装饰 Apache Collections 中的 TreeList。这将生成一个新列表,其性能可与 TreeSet 进行比较。 https://sourceforge.net/p/happy-guys/wiki/Sorted%20List/

【讨论】:

  • 这个组合太棒了!
【解决方案6】:

GlazedLists 有一个非常非常好的排序列表实现

【讨论】:

  • SortedList 是 log(n) 查找,很像 TreeSet。
  • 与 TreeSet 不同,SortedList 允许随机访问任何给定的索引,因此似乎更适合。我不知道任何允许 O(log(n)) 插入和 O(1) 索引访问的排序列表结构。
  • 嗯,在我看来,它就像一个桌面 GUI 组件。有没有关于那些东西的精简库?
  • GlazedLists 肯定不是 GUI 组件。试一试。至于精简库(大概只有排序功能的东西?)没有。这类事情有很多的工作,只为一种类型的列表做这件事是没有意义的。整个 GL 方法非常优雅。
  • 哈 - 现在我查看 TreeList 的 javadocs(来自 Commons),看起来他们已经完成了这项工作并将其保存在一个类中。 GL 仍然是实时和声明性列表的绝佳选择 - 我强烈推荐它。
【解决方案7】:

使用HashMap 怎么样?插入、删除和检索都是 O(1) 操作。如果您想对所有内容进行排序,您可以获取 Map 中的值列表并通过 O(n log n) 排序算法运行它们。

编辑

快速搜索找到了LinkedHashMap,它维护了您的键的插入顺序。这不是一个精确的解决方案,但非常接近。

【讨论】:

  • 嗯,我没有看到如何使用 LinkedHashMap 进行随机访问。
【解决方案8】:

通常您无法进行恒定时间查找和记录时间删除/插入,但如果您对记录时间查找感到满意,那么您可以使用 SortedList。

不确定你是否相信我的编码,但我最近用 Java 编写了一个 SortedList 实现,你可以从 http://www.scottlogic.co.uk/2010/12/sorted_lists_in_java/ 下载它。此实现允许您在日志时间内查找列表的第 i 个元素。

【讨论】:

  • 此实现现已稳定并得到改进。
【解决方案9】:

为了测试 Konrad Holl 早期 awnser 的效率,我与我认为的慢速方法进行了快速比较:

package util.collections;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/**
 *
 * @author Earl Bosch
 * @param <E> Comparable Element
 *
 */
public class SortedList<E extends Comparable> implements List<E> {

    /**
     * The list of elements
     */
    private final List<E> list = new ArrayList();

    public E first() {
        return list.get(0);
    }

    public E last() {
        return list.get(list.size() - 1);
    }

    public E mid() {
        return list.get(list.size() >>> 1);
    }

    @Override
    public void clear() {
        list.clear();
    }

    @Override
    public boolean add(E e) {
        list.add(e);
        Collections.sort(list);
        return true;
    }

    @Override
    public int size() {
        return list.size();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public boolean contains(Object obj) {
        return list.contains((E) obj);
    }

    @Override
    public Iterator<E> iterator() {
        return list.iterator();
    }

    @Override
    public Object[] toArray() {
        return list.toArray();
    }

    @Override
    public <T> T[] toArray(T[] arg0) {
        return list.toArray(arg0);
    }

    @Override
    public boolean remove(Object obj) {
        return list.remove((E) obj);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return list.containsAll(c);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {

        list.addAll(c);
        Collections.sort(list);
        return true;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        throw new UnsupportedOperationException("Not supported.");
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return list.removeAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return list.retainAll(c);
    }

    @Override
    public E get(int index) {
        return list.get(index);
    }

    @Override
    public E set(int index, E element) {
        throw new UnsupportedOperationException("Not supported.");
    }

    @Override
    public void add(int index, E element) {
        throw new UnsupportedOperationException("Not supported.");
    }

    @Override
    public E remove(int index) {
        return list.remove(index);
    }

    @Override
    public int indexOf(Object obj) {
        return list.indexOf((E) obj);
    }

    @Override
    public int lastIndexOf(Object obj) {
        return list.lastIndexOf((E) obj);
    }

    @Override
    public ListIterator<E> listIterator() {
        return list.listIterator();
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return list.listIterator(index);
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        throw new UnsupportedOperationException("Not supported.");
    }

}

原来它的速度大约是原来的两倍!我认为这是因为 SortedLinkList 获取速度慢 - 这使得它不是列表的好选择。

相同随机列表的比较时间:

  • 排序链接列表:15731.460
  • 排序列表:6895.494
  • ca.odell.glazedlists.SortedList : 712.460
  • org.apache.commons.collections4.TreeList : 3226.546

似乎 glazedlists.SortedList 真的很快...

【讨论】:

  • 它比 Konrad Holl 的答案更快,因为另一个答案使用 LinkedList 作为其基本列表,并对其执行一些非常慢的操作(特别是二进制搜索在链表上很慢,除非与链接遍历相比,比较非常昂贵)。
【解决方案10】:

您不需要排序列表。你根本不需要排序。

当从数据库中添加/删除对象时,我需要从列表中添加/删除键。

但不是立即,删除可以等待。使用 ArrayList 包含 ID 的所有活动对象以及最多一定百分比的已删除对象。使用单独的HashSet 来跟踪已删除的对象。

private List<ID> mostlyAliveIds = new ArrayList<>();
private Set<ID> deletedIds = new HashSet<>();

我想从整个列表中随机选择几十个元素。

ID selectOne(Random random) {
    checkState(deletedIds.size() < mostlyAliveIds.size());
    while (true) {
        int index = random.nextInt(mostlyAliveIds.size());
        ID id = mostlyAliveIds.get(index);
        if (!deletedIds.contains(ID)) return ID;
    }
}

Set<ID> selectSome(Random random, int count) {
    checkArgument(deletedIds.size() <= mostlyAliveIds.size() - count);
    Set<ID> result = new HashSet<>();
    while (result.size() < count) result.add(selectOne(random));
}

为了维护数据,请执行以下操作

void insert(ID id) {
    if (!deletedIds.remove(id)) mostlyAliveIds.add(ID);
} 

void delete(ID id) {
    if (!deletedIds.add(id)) {
         throw new ImpossibleException("Deleting a deleted element);
    }
    if (deletedIds.size() > 0.1 * mostlyAliveIds.size()) {
        mostlyAliveIds.removeAll(deletedIds);
        deletedIds.clear();
    }
}

唯一棘手的部分是insert,它必须检查已删除的 ID 是否已恢复。

delete 确保mostlyAliveIds 中不超过 10% 的元素被删除 ID。发生这种情况时,它们会一口气全部删除(我没有检查 JDK 源代码,但我希望它们是正确的),并且节目继续进行。

在不超过 10% 的死 ID 的情况下,selectOne 的开销平均不超过 10%。

我很确定它比任何排序都快,因为摊销复杂度为O(n)

【讨论】:

    猜你喜欢
    • 2018-04-17
    • 2016-06-16
    • 1970-01-01
    • 2021-06-17
    • 1970-01-01
    • 2013-11-10
    • 2014-07-12
    • 2017-09-27
    相关资源
    最近更新 更多