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

假设我正在使用Map(可能是HashMap)创建一个词频列表,其中每个键都是一个字符串,其中包含正在统计的单词,值是一个整数,它会在每次找到单词的标记时递增.

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

$map{$word}++;

但在Java中,它要复杂得多.以下是我目前的做法:

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

这当然依赖于较新Java版本中的自动装箱功能.我想知道你能否提出一种更有效的方法来增加这个值.是否有很好的性能原因可以避免使用Collections框架,而是使用其他东西?

更新:我对其中几个答案进行了测试.见下文.

推荐答案

一些测试结果

对于这个问题,我得到了很多很好的答案——谢谢大家——所以我决定运行一些测试,找出哪种方法实际上最快.我测试的五种方法如下:

  • 我在the question年提出的"容器密钥"方法
  • Aleksandar Dimitrov建议的"TestForNull"方法
  • 汉克·盖伊提出的"原子长"方法
  • 尤道夫提出的"藏宝法"
  • phax建议的"可变点"方法.肌萎缩.通用域名格式

方法

我是这么做的...

  1. 创建了五个相同的类,除了下面显示的差异.每个类都必须执行我介绍的场景中的典型操作:打开一个10MB的文件并读取它,然后对文件中的所有单词标记执行频率计数.因为这平均只需要3秒钟,所以我让它执行了10次频率计数(而不是I/O).
  2. 对10次迭代的循环计时,但为not the I/O operation次,并使用Ian Darwin's method in the Java Cookbook记录了总花费的时间(以时钟秒为单位).
  3. 连续进行了所有五个测试,然后又做了三次.
  4. 平均每种方法的四个结果.

结果

我将首先向感兴趣的人展示结果和下面的代码.

正如预期的那样,容器密钥方法是最慢的,所以我将给出每种方法的速度与该方法的速度进行比较.

  • 30.654秒(基线)
  • 原子长: 29.780秒(速度的1.03倍)
  • 28.804秒(快1.06倍)
  • 26.313秒(是原来的1.16倍)
  • 可变点: 25.747秒(速度的1.19倍)

结论

似乎只有可变点方法和珍宝方法的速度要快得多,因为只有它们的性能提升才超过10%.然而,如果线程是一个问题,原子长可能比其他的更具吸引力(我不确定).我还用final个变量运行了TestForNull,但差异可以忽略不计.

请注意,我没有分析不同场景中的内存使用情况.我很高兴听到任何人对可变点和珍宝方法可能如何影响内存使用有很好的见解.

就我个人而言,我发现可变点方法最有吸引力,因为它不需要加载任何第三方类.所以,除非我发现它有问题,否则我最有可能走的是这条路.

密码

下面是每个方法的关键代码.

容器密钥

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);
}

原子长

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

珍宝

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

可变点

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

Java相关问答推荐

使用log 4j2格式的Hibernate 显示SQL日志(log)

Spring安全实现多个SQL表身份验证

如何在Docker容器中使用wireock—Webhooks阻止请求?

Java:根据4象限中添加的行数均匀分布行的公式

多个Java线程和TreeMap.put()的非预期行为

使用java访问具体子类特定方法的最佳方法是什么?

在运行MVN测试时,为什么构建失败,并显示了java.lang.ClassNotFoundException:java.net.http.HttpResponse?

Hibernate EmptyInterceptor可以工作,但不能拦截器

相同的Java SerializedLambda为implMethodKind返回不同的结果

为什么使用JDK21获取锁定锁比使用JDK11慢

什么是Java原子属性的正确getter和setter

如何在Jooq中获取临时表列引用?

将stringBuilder + forloop转换为stream + map

使用MediaPlayer类在一段时间后停止播放音乐

在Java Spring JPA中插入包含对其他实体的引用的列

TinyDB问题,无法解析符号';上下文&

如何利用OpenTelemeter将初始值(零)输出到普罗米修斯

如何在Struts2中使用操作类中的结果注释重定向到不同的命名空间

使用@ExceptionHandler的GlobalExceptionHandler还是来自服务器的REST应答的ResponseEntity?

我可以使用一个 PoolingNHttpClientConnectionManager 运行多个 HttpAsyncClient 吗?