当前位置: 首页 > 面试题库 >

用Java增加Map值的最有效方法

晋奕
2023-03-14
问题内容

我希望这个问题对于本论坛来说不是太基本了,但是我们会看到的。我想知道如何重构一些代码以获得更好的性能,而这些性能已经运行了很多次。

假设我正在使用地图(可能是HashMap)创建一个单词频率列表,其中每个键是一个带有要计数单词的字符串,并且值是一个整数,每次找到该单词的标记时,该值就会递增。

在Perl中,增加这样的值非常容易:

$map{$word}++;

但是在Java中,它要复杂得多。这是我目前的操作方式:

int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);

当然,哪个依赖于新Java版本中的自动装箱功能。我想知道你是否可以建议一种更有效的递增此值的方法。避开Collections框架并改用其他方法,甚至有良好的性能原因吗?


问题答案:

一些测试结果

对于这个问题,我已经得到了很多不错的答案-谢谢大家-所以我决定进行一些测试,找出哪种方法实际上最快。我测试的五种方法是:

  • the “ContainsKey” method that I presented in the question
  • the “TestForNull” method suggested by Aleksandar Dimitrov
  • the “AtomicLong” method suggested by Hank Gay
  • the “Trove” method suggested by jrudolph
  • the “MutableInt” method suggested by phax.myopenid.com

方法

这是我做的…

  1. 创建了五个相同的类,除了以下所示的差异。每个班级都必须执行我所介绍的场景的典型操作:打开一个10MB的文件并读入它,然后对文件中所有单词标记的频率进行计数。由于平均只需要3秒钟,因此我让它执行了10次频率计数(而不是I / O)。
  2. 对10次迭代(而非I / O操作)的时间进行计时,并基本上使用Java Cookbook中的Ian Darwin的方法记录所花费的总时间(以时钟秒为单位)。
  3. 依次执行了所有五个测试,然后又进行了三次。
  4. 将每种方法的四个结果取平均值。

结果
我将首先介绍结果,并为感兴趣的人提供以下代码。

如所预期的,ContainsKey方法是最慢的,因此,与该方法的速度相比,我将给出每种方法的速度。

  • ContainsKey: 30.654秒(基准)
  • AtomicLong: 29.780秒(速度的1.03倍)
  • TestForNull: 28.804秒(速度的1.06倍)
  • Trove: 26.313秒(1.16倍的速度)
  • MutableInt: 25.747秒(1.19倍的速度)

结论
似乎只有MutableInt方法和Trove方法要快得多,因为它们的性能提升只有10%以上。但是,如果线程成为问题,AtomicLong可能比其他线程更具吸引力(我不确定)。我也用final变量运行了TestForNull ,但是差别可以忽略不计。

请注意,我没有介绍不同情况下的内存使用情况。我很高兴听到任何对MutableInt和Trove方法将如何影响内存使用情况有深刻见解的人。

我个人认为MutableInt方法最吸引人,因为它不需要加载任何第三方类。因此,除非我发现问题,否则这是我最有可能采取的方法。

代码
这是每种方法的关键代码。

ContainsKey

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
int count = freq.containsKey(word) ? freq.get(word) : 0;
freq.put(word, count + 1);

TestForNull

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
Integer count = freq.get(word);
if (count == null) {
    freq.put(word, 1);
}
else {
    freq.put(word, count + 1);
}

AtomicLong

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
...
final ConcurrentMap<String, AtomicLong> map = 
    new ConcurrentHashMap<String, AtomicLong>();
...
map.putIfAbsent(word, new AtomicLong(0));
map.get(word).incrementAndGet();

Trove

import gnu.trove.TObjectIntHashMap;
...
TObjectIntHashMap<String> freq = new TObjectIntHashMap<String>();
...
freq.adjustOrPutValue(word, 1, 1);

MutableInt

import java.util.HashMap;
import java.util.Map;
...
class MutableInt {
  int value = 1; // note that we start at 1 since we're counting
  public void increment () { ++value;      }
  public int  get ()       { return value; }
}
...
Map<String, MutableInt> freq = new HashMap<String, MutableInt>();
...
MutableInt count = freq.get(word);
if (count == null) {
    freq.put(word, new MutableInt());
}
else {
    count.increment();
}


 类似资料:
  • 问题内容: 我不是太在意时间效率(这种操作很少见),而是在内存效率上: 我可以在不将所有值都临时设置两次的情况下增加数组吗? 有没有比创建一个新数组并复制所有值更有效的方法来增长大型数组?喜欢,将其与新的连接起来吗? 将固定大小的数组存储在另一个数组中并重新分配/复制该顶级数组会怎样?会保留实际值吗? 我知道ArrayList,但是我需要对访问数组进行大量控制,并且访问必须非常快。举例来说,我想我

  • 问题:有一个字符串和映射,其中键-符号必须被替换,而值-新符号代替替换。 例如,假设有一个字符串,一、二、三、四。我需要用替换,用替换,用-替换,等等,得到一,t-o,三,我们的 如何以最有效的方式进行?我只找到了一个解决方案——迭代map,并为每个map条目使用。有没有更有效的方法?

  • 问题内容: 有一些方法,例如搜索重复项,但我想知道对于此任务是否有更好的解决方案。 问题答案: 您可以为此使用。

  • 问题内容: 是否有与C ++等效的Java Map keySet()? Java 方法返回“此映射中包含的键的设置视图”。 问题答案: 也许以下可能有用: 使用STL兼容序列(例如std :: vector,std :: deque或std :: list)的 make_key_set 函数的重载可以如下所示:

  • 我对Java8还不是很熟悉,我想看看是否可以使用流找到类似于下面代码的东西。 下面的代码主要尝试寻找跨其值最多的键并返回该键。我找不到任何关于这种格式的帮助。