【问题标题】:Is there a better way to retrieve values from a Map有没有更好的方法从 Map 中检索值
【发布时间】:2015-03-13 20:21:59
【问题描述】:

我有以下代码

final Map<String, Location> map = new HashMap<>();
map.put("1", new Location("a", null));
map.put("2", new Location("b", null));
map.put("3", new Location("c", null));

final List<String> list = new ArrayList<>();

for (final Location s : map.values()) {
    list.add(s.getId());
}

当我打印列表时,结果是 a,b,c(如预期)。

for (final String string : list) {
    System.out.println(string);
}

Java6 中是否有更好的方法可以在不使用 for 循环的情况下获取 Id。

根据 Java8,引用代码形式 @rohit-jain 回答

final List<String> list = map.values().stream().map(loc -> loc.getId()).collect(Collectors.toList());

在 java6 中有没有这样的 consise?

【问题讨论】:

  • 您能否更具体地说明您所说的高效?在某种程度上,如果要打印它们,则需要遍历映射中的条目。现在有办法解决了。
  • 另外,请注意,结果很可能是 c、a、b 或 b、a、c 或任何其他组合。
  • 为了澄清,效率意味着我可以摆脱第一个 for 循环。我知道我需要第二个来打印,这是不可避免的。
  • 没有。没有比这更好的方法了。
  • for (final Location s : map.values()) { System.out.println(s.getId()); }。如果您只想打印值,则无需创建临时列表。

标签: java collections lambda guava


【解决方案1】:

不确定效率(因为它不会影响太大),但如果你想使用 lambdas 做到这一点,它可以是这样的:

final Map<String, Location> locationMap = new HashMap<>();
locationMap.put("1", new Location("a", null));
locationMap.put("2", new Location("b", null));
locationMap.put("3", new Location("c", null));

final List<String> list = locationMap.values().stream()
                             .map(loc -> loc.getId())
                             .collect(Collectors.toList());

System.out.println(list);  // can be `[a, b, c]` or `[b, c, a]`, etc

但是,仅仅因为您在此处没有看到 for 循环并不意味着它没有迭代地图的值。确实如此,但只是隐藏了迭代逻辑。

或者,如果您只想打印值(一次性使用),您甚至可以避免在此处创建列表:

locationMap.values().stream().map(loc -> loc.getId())
                    .forEach(System.out::println);

【讨论】:

  • 你秒赢我! ;) 也许终端操作可能是forEach(System.out::println)。这样,只有 1 个 for 会按照 OP 的要求发生。
  • @Magnamag 补充说:)
  • 打印是次要的,我知道即使使用 lambdas 至少会发生 1 次传递值,我的问题是我能否像在 java8 中那样在 java6 中更简洁地编写此代码。如果问题具有误导性,我深表歉意
  • @SDS 在 Java 6 中,没有。这是你将得到的最好的。
【解决方案2】:

编辑:问题已修改,现在似乎不再涉及效率。现在,这个答案不再适合了,但现在,我将其留在这里,也许有人觉得它有帮助

首先是一个一般性提示:你说过

当我打印列表时,结果是 a,b,c(如预期)。

但是,您不应该对此抱有期望。 HashMap 没有以任何方式排序。元素的顺序可能不同。同样重要的是:如果您在地图中添加了 更多 个元素,那么之前包含在地图中的元素的顺序可能会更改

如果您希望迭代期间元素的顺序与插入顺序相同,则应使用LinkedHashMap 而不是HashMap。它保留了迭代顺序,在那里,您对输出的期望将得到满足。

有趣的是,这引出了关于性能的问题:

迭代LinkedHashMap 实际上可以(明显)比迭代Map 快。这是一个小的微基准测试,与往常一样,应该对它持保留态度:

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;

public class MapIteration
{
    public static void main(String[] args)
    {
        long sum = 0;
        for (int size=100000; size<=1000000; size += 100000)
        {
            Map<String, Integer> mapA = 
                new HashMap<String, Integer>();
            fillMap(mapA, size);

            Map<String, Integer> mapB = 
                new LinkedHashMap<String, Integer>();
            fillMap(mapB, size);

            int runs = 100;
            long beforeA = System.nanoTime();
            for (int i=0; i<runs; i++)
            {
                sum += computeValueSum(mapA);
            }
            long afterA = System.nanoTime();
            double durationA = (afterA - beforeA) / 1e6;

            long beforeB = System.nanoTime();
            for (int i=0; i<runs; i++)
            {
                sum += computeValueSum(mapB);
            }
            long afterB = System.nanoTime();
            double durationB = (afterB - beforeB) / 1e6;

            System.out.printf(
                "Normal size %10d duration %10.3f\n", size, durationA);
            System.out.printf(
                "Linked size %10d duration %10.3f\n", size, durationB);

        }
        System.out.println(sum);

    }

    private static void fillMap(Map<String, Integer> map, int n)
    {
        Random random = new Random(0);
        for (int i=0; i<n; i++)
        {
            map.put(String.valueOf(i), random.nextInt(n));
        }
    }

    private static long computeValueSum(Map<?, Integer> map)
    {
        long sum = 0;
        for (Integer i : map.values())
        {
            sum += i;
        }
        return sum;
    }
}

对我来说,它打印...

...
Normal size    1000000 duration   2611,288
Linked size    1000000 duration   1796,030

同样,这不应该被认为是理所当然的,除非它通过适当的 Micobenchmarking 框架进行验证,但坦率地说:LinkedHashMap 快 30%,给或取一些,我怀疑 Micobenchmarking 框架会告诉我这边正好相反。


一般来说,我基本上总是使用LinkedHashMap 而不是普通的HashMap。但不是因为性能,主要是因为迭代顺序一致。关于性能:LinkedHashMap 中的插入和删除可能比HashMap 更昂贵,但在大多数情况下这些性能差异可以忽略不计。

【讨论】:

    【解决方案3】:

    如果你使用Eclipse Collections,你可以这样写:

    MutableMap<String, Location> map = Maps.mutable.empty();
    map.put("1", new Location("a", null));
    map.put("2", new Location("b", null));
    map.put("3", new Location("c", null));
    
    List<String> list = map.collect(Location::getId).toSortedList();
    Bag<String> bag = map.collect(Location::getId);
    
    Assert.assertEquals(Lists.mutable.with("a", "b", "c"), list);
    Assert.assertEquals(Bags.mutable.with("a", "b", "c"), bag);
    

    以下代码也适用于 Java 5 - 7:

    Function<Location, String> function = new Function<Location, String>()
    {
        public String valueOf(Location location)
        {
            return location.getId();
        }
    };
    List<String> list = map.collect(function).toSortedList();
    Bag<String> bag = map.collect(function);
    
    Assert.assertEquals(Lists.mutable.with("a", "b", "c"), list);
    Assert.assertEquals(Bags.mutable.with("a", "b", "c"), bag);
    

    注意:我是 Eclipse Collections 的提交者

    【讨论】:

      【解决方案4】:

      如果你喜欢使用兼容 Java 6 的版本,那么你可以使用 Guava 及其Function 接口:

      public class ExtractLocationId implements Function<Location, String> {
           @Override
           public String apply(final Location location) {
                return location.getId();
           }
      }
      

      并像这样使用它:

      final List<String> list =
          FluentIterable.from(map.values()).transform(new ExtractLocationId()).toList();
      

      它需要比 Java 8 Lambda 版本更多的代码(由于自己的 Function 实现),但它支持较旧的 Java 版本。

      【讨论】:

        【解决方案5】:

        在某些时候,您无法逃避对地图的迭代。 对于地图,最有效的方法是遍历“entrySet()”。

        【讨论】:

        • 如果 OP 不关心键,为什么迭代 entrySet 比迭代值更有效?
        • 由于整体性能,迭代映射条目更快,然后是键或值。
        • @Dae 你能证明吗?我的意思是,在谈论效率时,我不相信文字..
        • @Dae 你应该阅读源代码。您会意识到,值的迭代器与条目的迭代器相同,只是它返回的是值而不是条目。
        猜你喜欢
        • 2019-10-29
        • 1970-01-01
        • 2014-07-27
        • 1970-01-01
        • 1970-01-01
        • 2017-03-12
        • 1970-01-01
        • 2021-07-05
        • 1970-01-01
        相关资源
        最近更新 更多