有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

在Java7和Java8中从现有列表创建不同的列表?

如果我有:

List<Integer> listInts = { 1, 1, 3, 77, 2, 19, 77, 123, 14, 123... }

在Java中,创建只包含来自listInts不同值的List<Integer> listDistinctInts的有效方法是什么

我的直接想法是创建一个包含来自listInts的所有值的Set<Integer> setInts,然后调用List<Integer> listDistinctInts = new ArrayList<>(setInts);

但这似乎有潜在的低效性——是否有更好的使用Java7的解决方案

我没有使用Java 8,但我相信使用它我可以做类似的事情(?):

List<Integer> listDistinctInts = listInts.stream().distinct().collect(Collectors.toList());

这会比上述方法更有效吗?或者在Java8中有没有更有效的方法

最后,(我知道问多个问题可能会令人不快,但这是直接相关的)如果我只关心listInts中不同元素的计数,有没有更有效的方法来获取该值(在Java 7和8中)-而不必先创建所有不同元素的列表或集合

我对本机java实现这一点最感兴趣,避免重新发明任何轮子,但是如果能提供更好的清晰度或性能,可以考虑手工编写的代码或库。我已经读过这个相关的问题Java - Distinct List of Objects,但是不完全清楚Java7和Java8方法之间的性能差异,或者是否有更好的技术


共 (6) 个答案

  1. # 1 楼答案

    你应该试试new LinkedList(new HashSet(listInts))

  2. # 2 楼答案

    Guava可以是您的选择:

    ImmutableSet<Integer> set = ImmutableSet.copyOf(listInts);
    

    API经过了极大的优化

    它比listInts.stream().distinct()new LinkedHashSet<>(listInts)

  3. # 3 楼答案

    如果您正在使用Eclipse Collections(以前是GS Collections),那么可以使用方法distinct()

    ListIterable<Integer> listInts = FastList.newListWith(1, 1, 3, 77, 2, 19, 77, 123, 14, 123);
    Assert.assertEquals(
            FastList.newListWith(1, 3, 77, 2, 19, 123, 14),
            listInts.distinct());
    

    使用distinct()而不是转换为集合然后返回列表的优点是distinct()保留原始列表的顺序,保留每个元素的第一次出现。它通过使用集合和列表来实现

    MutableSet<T> seenSoFar = UnifiedSet.newSet();
    int size = list.size();
    for (int i = 0; i < size; i++)
    {
        T item = list.get(i);
        if (seenSoFar.add(item))
        {
            targetCollection.add(item);
        }
    }
    return targetCollection;
    

    如果无法将原始列表转换为GS集合类型,则可以使用ListAdapter获得相同的API

    MutableList<Integer> distinct = ListAdapter.adapt(integers).distinct();
    

    无法避免创建集合。尽管如此,UnifiedSet比HashSet更高效,因此会有一些速度优势

    如果您只需要不同项目的数量,只创建一个集合而不创建列表会更有效

    Verify.assertSize(7, UnifiedSet.newSet(listInts));
    

    EclipseCollections8.0需要Java8。EclipseCollections7。x可以很好地使用Java8,但只需要Java5

    注意:我是Eclipse集合的提交者

  4. # 4 楼答案

    将值添加到listInts检查时:

    int valueToAdd;
    //...
    if (!listInts.contains(valueToAdd)) {listInts.add(valueToAdd)}
    

    如果您有一个现有列表,请使用for each语句将该列表中的所有值复制到一个新列表中,以使其“不同”:

    List<Integer> listWithRepeatedValues;
    List<Integer> distinctList;
    //...
    for (Integer i : listWithRepeatedValues) {
        if (!listInts.contains(valueToAdd)) {distinctList.add(i);}
    }
    
  5. # 5 楼答案

    现在,我已经从提供的优秀答案中对大多数建议选项进行了微基准标记。与大多数与性能相关的非平凡问题一样,哪一个问题最好的答案是“这取决于”

    我所有的测试都是用JMH Java Microbenchmarking Harness执行的

    大多数测试都是使用JDK1.8执行的,尽管我也使用JDK1.7执行了一些测试,只是为了确保其性能没有太大差异(几乎相同)。我从目前提供的答案中测试了以下技巧:


    1。Java 8流-如果使用Java 8,我已经提出了使用stream()的解决方案:

    public List<Integer> testJava8Stream(List<Integer> listInts) {
        return listInts.stream().distinct().collect(Collectors.toList());
    }
    

    优点现代Java 8方法,无第三方依赖关系

    cons需要Java 8


    2。添加到列表-由Victor2748提出的解决方案,其中当且仅当列表尚未包含该值时,构造并添加一个新列表。请注意,我还以原始列表的大小(最大可能)预先分配目标列表,以防止任何重新分配:

    public List<Integer> testAddingToList(List<Integer> listInts) {
        List<Integer> listDistinctInts = new ArrayList<>(listInts.size());
        for(Integer i : listInts)
        {
            if( !listDistinctInts.contains(i) ) { listDistinctInts.add(i); }
        }
        return listDistinctInts;
    }
    

    pros适用于任何Java版本,无需创建集然后进行复制,无需第三方DEP

    cons在构建列表时,需要反复检查列表中的现有值


    3。GS Collections Fast(现在是Eclipse Collections)-由Craig P. Motlin使用GS Collections library及其自定义列表类型FastList提出的解决方案:

    public List<Integer> testGsCollectionsFast(FastList listFast)
    {
        return listFast.distinct();
    }
    

    pros据报道,非常快速、简单的表达代码,适用于Java7和Java8

    cons需要第三方库和FastList而不是常规的List<Integer>


    4。GS Collections Adapted-FastList解决方案没有进行类似的比较,因为它需要一个FastList传递给方法,而不是一个好的ol'ArrayList<Integer>,所以我还测试了Craig提出的适配器方法:

    public List<Integer> testGsCollectionsAdapted(List<Integer> listInts)
    {
        return listAdapter.adapt(listInts).distinct();
    }
    

    pros不需要FastList,在Java7和Java8中工作

    cons必须调整列表,因此可能无法正常运行,需要第三方库


    5。番石榴免疫表集——由Louis Wasserman在评论中提出的方法,以及卢声远 Shengyuan Lu在回答中使用Guava提出的方法:

    public List<Integer> testGuavaImmutable(List<Integer> listInts)
    {
        return ImmutableSet.copyOf(listInts).asList();
    }
    

    pros据说速度非常快,可以在Java 7或8中运行

    cons返回一个Immutable List,无法处理输入列表中的空值,需要第三方库


    7。HashSet-我的原始想法(也由EverV0idulix和Radiodef推荐)

    public List<Integer> testHashSet(List<Integer> listInts)
    {
        return new ArrayList<Integer>(new HashSet<Integer>(listInts));
    }
    

    pros适用于Java 7和8,无第三方依赖关系

    cons不保留列表的原始顺序,必须构造集合然后复制到列表


    6。LinkedHashSet-由于HashSet解决方案没有保留原始列表中整数的顺序,我还测试了一个使用LinkedHashSet保留顺序的版本:

    public List<Integer> testLinkedHashSet(List<Integer> listInts)
    {
        return new ArrayList<Integer>(new LinkedHashSet<Integer>(listInts));
    }
    

    pros保留原始顺序,使用Java 7和Java 8,无第三方依赖关系

    cons不可能像常规HashSet方法那样快


    结果

    以下是我对不同大小的listInts的结果(结果从最慢到最快排列):

    1。从0-50000之间的100000个随机整数的ArrayList中选择不同的整数(即大列表,一些重复项)

    Benchmark                Mode       Samples     Mean   Mean error    Units
    
    AddingToList            thrpt        10        0.505        0.012    ops/s
    Java8Stream             thrpt        10      234.932       31.959    ops/s
    LinkedHashSet           thrpt        10      262.185       16.679    ops/s
    HashSet                 thrpt        10      264.295       24.154    ops/s
    GsCollectionsAdapted    thrpt        10      357.998       18.468    ops/s
    GsCollectionsFast       thrpt        10      363.443       40.089    ops/s
    GuavaImmutable          thrpt        10      469.423       26.056    ops/s
    

    2。从0-50之间的1000个随机整数的数组列表中选择不同的整数(即中等列表,许多重复项)

    Benchmark                Mode       Samples     Mean   Mean error    Units
    
    AddingToList            thrpt        10    32794.698     1154.113    ops/s
    HashSet                 thrpt        10    61622.073     2752.557    ops/s
    LinkedHashSet           thrpt        10    67155.865     1690.119    ops/s
    Java8Stream             thrpt        10    87440.902    13517.925    ops/s
    GsCollectionsFast       thrpt        10   103490.738    35302.201    ops/s
    GsCollectionsAdapted    thrpt        10   143135.973     4733.601    ops/s
    GuavaImmutable          thrpt        10   186301.330    13421.850    ops/s
    

    3。从0-100之间的100个随机整数的ArrayList中选择不同的整数(即小列表,一些重复项)

    Benchmark                Mode       Samples     Mean   Mean error    Units
    
    AddingToList            thrpt        10   278435.085    14229.285    ops/s
    Java8Stream             thrpt        10   397664.052    24282.858    ops/s
    LinkedHashSet           thrpt        10   462701.618    20098.435    ops/s
    GsCollectionsAdapted    thrpt        10   477097.125    15212.580    ops/s
    GsCollectionsFast       thrpt        10   511248.923    48155.211    ops/s
    HashSet                 thrpt        10   512003.713    25886.696    ops/s
    GuavaImmutable          thrpt        10  1082006.560    18716.012    ops/s
    

    4。取不同的f0-50之间的10个随机整数的rom数组列表(即小列表,少量重复)

    Benchmark                Mode       Samples     Mean   Mean error    Units
    
    Java8Stream             thrpt        10  2739774.758   306124.297    ops/s
    LinkedHashSet           thrpt        10  3607479.332   150331.918    ops/s
    HashSet                 thrpt        10  4238393.657   185624.358    ops/s
    GsCollectionsAdapted    thrpt        10  5919254.755   495444.800    ops/s
    GsCollectionsFast       thrpt        10  7916079.963  1708778.450    ops/s
    AddingToList            thrpt        10  7931479.667   966331.036    ops/s
    GuavaImmutable          thrpt        10  9021621.880   845936.861    ops/s
    

    结论

    • 如果您只从列表中提取一次不同的项,并且列表并不长,那么这些方法中的任何一种都应该足够了

    • 最有效的通用方法来自第三方图书馆:GS Collections和Guava表现出色

    • >p>在选择最有效率的方法时,您可能需要考虑列表的大小和可能的重复数。

    • 仅当值不在新列表中时才添加到新列表的天真方法对于小列表非常有效,但一旦输入列表中有多个值,它就会执行尝试过的最差方法

    • 番石榴{}方法在大多数情况下最快。但是请注意这些限制:返回的列表是Immutable,输入列表不能包含空值

    • HashSet方法执行非第三方方法中最好的方法,通常比Java8流更好,但是重新排序整数(这可能是一个问题,也可能不是,取决于您的用例)

    • LinkedHashSet方法保持了顺序,但毫不奇怪,它通常比HashSet方法更糟糕

    • 当使用具有复杂哈希代码计算的数据类型列表时,HashSetLinkedHashSet方法的性能都会更差,因此,如果您试图从List<Foo>中选择不同的Foo,请执行您自己的评测

    • 如果已经将GS Collections作为依赖项,那么它的性能非常好,并且比ImmutableList Guava方法更灵活。如果您没有将其作为依赖项,那么如果选择不同项的性能对应用程序的性能至关重要,则值得考虑添加它

    • 令人失望的是,Java8流的性能似乎相当差。与我使用的方法相比,可能有更好的方法来编码distinct()调用,因此,当然欢迎评论或其他答案

    NB。我不是微基准营销专家,因此如果有人发现我的结果或方法中存在缺陷,请通知我,我将努力纠正答案

  6. # 6 楼答案

    别担心。使用哈希集是消除重复项的一种非常简单有效的方法:

        Set<Integer> uniqueList = new HashSet<>();
        uniqueList.addAll(listInts);   // Add all elements eliminating duplicates
    
        for (int n : uniqueList)       // Check the results (in no particular order)
            System.out.println(n);
    
        System.out.println("Number distinct values: " + uniqueList.size());
    

    在一个更具体的场景中,只是为了防止可能值的范围已知,它不是很大,而listInts非常大
    我能想到的计算列表中唯一条目数量的最有效方法是:

        boolean[] counterTable = new boolean[124];
        int counter = 0;
    
        for (int n : listInts)
            if (!counterTable[n]) {
                counter++;
                counterTable[n] = true;
            }
    
        System.out.println("Number of distinct values: " + counter);