【问题标题】:LinkedHashSet - insertion order and duplicates - keep newest "on top"LinkedHashSet - 插入顺序和重复 - 保持最新的“顶部”
【发布时间】:2016-04-04 10:10:51
【问题描述】:

我需要一个保持插入顺序并具有唯一值的集合。 LinkedHashSet 看起来像是要走的路,但有一个问题 - 当两个项目相等时,它会删除最新的一个(这是有道理的),这是一个例子:

set.add("one");
set.add("two");
set.add("three");
set.add("two");

LinkedHashSet 将打印:

one, two, three

但我需要的是:

onethreetwo

这里最好的解决方案是什么?是否有任何收集/收集方法可以做到这一点,或者我应该手动实现它?

【问题讨论】:

  • 在插入之前使用ArrayListcontains 方法怎么样?
  • 我怀疑您将不得不为此实现自己的算法,可能由HashSet 支持。
  • @AvihooMamka TreeSet 保持元素根据自然或给定顺序排序,它本身并不保留插入顺序。
  • 请注意,LinkedHashSet 不是 final,但您将无法通过用于存储/检索元素的支持映射访问。
  • 您可以使用地图。每次用户输入字母时,您都会更新此地图。

标签: java list linkedhashset


【解决方案1】:

大部分Java Collections 都可以扩展以进行调整。

子类LinkedHashSet,覆盖add 方法。

class TweakedHashSet<T> extends LinkedHashSet<T> {

    @Override
    public boolean add(T e) {
        // Get rid of old one.
        boolean wasThere = remove(e);
        // Add it.
        super.add(e);
        // Contract is "true if this set did not already contain the specified element"
        return !wasThere;
    }

}

【讨论】:

  • @rafakob - ArrayList 允许重复。
  • @OldCurmudgeon:如果您在插入之前删除,则不会
  • @rafakob 删除应该在LinkedHashSet 中更有效,但您必须进行分析以确保(取决于您的Ts 的典型hashCode 实现的质量以及您收藏的典型大小等)
  • @corsiKa:差异不是像 3 或 4 那样的因素,而是 缩放,所以如果集合足够大,性能差异会很痛苦。但当然,与迭代相比,我们对 OP 的最大集合大小以及查找/修改发生的频率一无所知……
  • @corsiKa:原则上,我们同意这一点。对于特定的应用程序,分析是关键关于问题的分析,是否可以在不同的场景/生产环境/不同的客户/等中出现显着更多的元素。如果您知道您的任务、测试和配置文件中存在固有限制以及您的预期最大值……
【解决方案2】:

您可以简单地使用LinkedHashMap的特殊功能:

Set<String> set = Collections.newSetFromMap(new LinkedHashMap<>(16, 0.75f, true));
set.add("one");
set.add("two");
set.add("three");
set.add("two");
System.out.println(set); // prints [one, three, two]

在 Oracle 的 JRE 中,LinkedHashSet 无论如何都由 LinkedHashMap 支持,因此没有太大的功能差异,但这里使用的特殊构造函数配置 LinkedHashMap 以更改每次访问的顺序 不仅在插入上。这听起来可能太多了,但实际上只影响已包含的键(Set 意义上的值)的插入。返回的Set 不使用其他受影响的Map 操作(即get)。

如果您不使用 Java 8,由于类型推断有限,您必须帮助编译器:

Set<String> set
    = Collections.newSetFromMap(new LinkedHashMap<String, Boolean>(16, 0.75f, true));

但功能是一样的。

【讨论】:

  • +1 我喜欢这种方法。但我建议将这个 Set 的创建放入一个命名良好的方法 (createAccessOrderSet?) 中,并添加一些单元测试来明确它的作用。
  • @Hulk:当然,这对于生产代码来说是正确的。然后,您可能会提供重载来提供容量和负载因子参数。我在这里使用了与默认构造函数相同的值,但没有记录的工厂方法,它们对读者来说就像魔法文字......
【解决方案3】:

初始化时,您是 LinkedHashSet,您可以覆盖 add 方法。

Set<String> set = new LinkedHashSet<String>(){
    @Override
    public boolean add(String s) {
        if(contains(s))
            remove(s);
        return super.add(s);
    }
};

现在它给你:

set.add("1");
set.add("2");
set.add("3");
set.add("1");
set.addAll(Collections.singleton("2"));

// [3, 1 ,2]

甚至 addAll 方法都在工作。

【讨论】:

    【解决方案4】:

    上面提供的所有解决方案都非常好,但如果我们不想覆盖已经实现的集合。我们可以通过一个小技巧简单地使用 ArrayList 来解决这个问题

    我们可以创建一个用于将数据插入列表的方法

    public static <T> void addToList(List<T> list, T element) {
        list.remove(element); // Will remove element from list, if list contains it
        list.add(element); // Will add element again to the list 
    }
    

    我们可以调用这个方法将元素添加到我们的列表中

    List<String> list = new ArrayList<>();
    
    addToList(list, "one");
    addToList(list, "two");
    addToList(list, "three");
    addToList(list, "two");
    

    这里唯一的缺点是我们每次都需要调用我们自定义的addToList()方法而不是list.add()

    【讨论】:

    • 漂亮而简单。但我不确定你为什么根据问题使用列表而不是LinkedHashSet
    • 感谢@shmosel,我已经为我的代码示例考虑了 ArrayList,因为 LinkedHashSet 没有维护用户想要的顺序,而且 ArrayList 也是解决此问题的最简单和最快的方法。
    • LinkedHashSet 维护插入顺序,就像ArrayList 一样。而且它更有效地清除。
    • list.remove(element) 在 O(N) 时间内运行。随着您的列表变大,addToList 方法的性能将迅速下降。另一方面,无论大小如何,HashSet 都会在 O(1) 中运行 contains(element)
    猜你喜欢
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 2019-01-15
    • 1970-01-01
    • 2010-12-09
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    相关资源
    最近更新 更多