【问题标题】:Java lists filter and find first/find lastJava 列表过滤和查找第一个/查找最后一个
【发布时间】:2019-08-12 12:30:58
【问题描述】:

我有一个 java 对象列表。对象属性为:

public class CheckPoint {
    private String message;
    private String tag;
}

现在,我想根据标签找到过滤器列表并获取第一个/最后一个元素。

例如:可能的标签值:A、B、C。对于带有值的标签 A - 我想要最后一个元素,而 B - 我想要第一个元素, C - 第一个元素

当前解决方案:

CheckPoint inTransitCheckPoint = checkPointsList.stream().filter(c -> c.getTag().equals("A")).reduce((first, second) -> second).orElse(null);

CheckPoint useCheckPoint = checkPointsList.stream().filter(c -> c.getTag().equals("B")).findFirst.orElse(null);

CheckPoint typeCheckPoint = checkPointsList.stream().filter(c -> c.getTag().equals("C")).findFirst.orElse(null);

但是,我知道这是一个低效的解决方案,因为我们循环了三次。有人可以帮助我以更高效的方式解决它吗?

任何帮助将不胜感激,谢谢:)

【问题讨论】:

  • checkPointsList 在最坏的情况下有多大,您是否遇到任何实际性能问题?
  • 如果你想要代码清晰,Streams 很好。如果您想要性能,for 循环可能更适合在此用例中
  • 你真的应该只使用一个好的老式 for 循环并将开销减少超过 3... 只需在 for 查找之前创建初始变量并有 3 个 if 语句。我完全不同意其他海报说只有在你有性能问题时才改变它。这就是您最终遇到性能问题的方式。或者,如果你真的想使用流,你可以使用 forEach...
  • “这是一个低效的解决方案” 并非如此。与其他循环相比,循环本身相当小。如果您正在寻找该级别的微优化,您应该使用 for 循环,而不是流,因为单个流的开销远远超过 3 个 for 循环的性能,更不用说单个 for循环收集所有 3 个结果。简而言之,“低效解决方案”是不正确的,您应该继续使用对您最有意义的代码,直到代码分析显示您确实有问题。
  • 对不起,在同一个列表中循环三次以找到三个不同项目的第一次出现是精神错乱的,考虑到他正在积极询问此代码,没有理由继续其他事情.

标签: java performance optimization collections java-8


【解决方案1】:

我建议按标签将checkPointList 分组为Map<String, LinkedList<CheckPoint>>

Map<String, LinkedList<CheckPoint>> map = new HashMap<>();
map.put("A", new LinkedList<>());
map.put("B", new LinkedList<>());
map.put("C", new LinkedList<>());

for(CheckPoint c : checkPointList) {
    map.computeIfAbsent(c.getTag(), ignored -> new LinkedList<>()).add(c);
}

链表是一个方便的助手,因为它允许您直接获取第一个或最后一个元素(如果不存在则返回null):

CheckPoint A = map.get("A").pollLast();
CheckPoint B = map.get("B").pollFirst();
CheckPoint C = map.get("C").pollFirst();

或者您可以更轻松地使用它for-loop

CheckPoint a = null, b = null, c = null;
for (CheckPoint checkPoint : checkPointList) {
    String tag = checkPoint.getTag();
    if ("A".equals(tag) && a == null) {
        a = checkPoint;
    } else if("B".equals(tag)){
        b = checkPoint;
    } else if("C".equals(tag)){
        c = checkPoint;
    }
}

"B""C" 的变量始终被最后一个值覆盖,而 a 仅选择第一个 "A" 检查点。

【讨论】:

  • @Eugene 我对应该使用哪些集合有所了解,也许Queue 会更好,一个只保留最后添加的元素,另一个保留第一个添加的元素跨度>
  • @Eugene 随时在您的答案中包含一个带有数组列表的方法。我不认为使用ArrayList 会大大改善我的答案。它的内存效率更高,但这在大多数环境中不会产生太大影响
  • 是经典的时间和空间权衡吗?我最初的解决方案似乎效率低下,因为它流三次,而你的使用地图和另外三个列表?我的理解正确吗?
  • @SwapnilBagadia 正确,我已经用一个更简单的解决方案更新了我的答案,只使用了一些 if-elses 和 3 个变量,这应该是最快的
  • 感谢您的解决方案。这是我想不到的最好的解决方案。简单的 for 循环可以节省时间。
【解决方案2】:

您可以先收藏到Map,例如:

Map<String, List<CheckPoint>> map = 
       checkPointsList.stream()
                      .collect(Collectors.groupingBy(CheckPoint::getTag));

Optional.ofNullable(map.get("A"))
        .orElse(Collection.emptyList())
        .reduce((left, right) -> right)
        .orElse(null);
// same for "B" and "C"

【讨论】:

    猜你喜欢
    • 2012-06-11
    • 1970-01-01
    • 2022-12-18
    • 2018-08-15
    • 2023-03-09
    • 2011-12-23
    • 2013-06-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多