HashMap - 包含和获取方法不应该一起使用

Kri*_*ran 30 java algorithm hash dictionary hashmap

我从面试中得到了以下问题.

我得到了一个像这样的字符数组:

char[] characters = {'u', 'a', 'u', 'i', 'o', 'f', 'u'};
Run Code Online (Sandbox Code Playgroud)

我需要获得每个角色的不同角色和数量:

u = 3
a = 1
i = 1
o = 1
f = 1
Run Code Online (Sandbox Code Playgroud)

所以我用Java回答了以下代码:

HashMap<Character, Integer> map = new HashMap<Character, Integer>();
int i = 1;
for (char c : characters) {             
    if (map.containsKey(c)) {
        int val = map.get(c);
        map.put(c, ++val);
    } else map.put(c, i);
}
Run Code Online (Sandbox Code Playgroud)

面试官是一名解决方案架构师.他问我为什么,我同时使用containsKey()get()方法,在这里,并指出这是多余的使用这两种方法.他的观点是什么?我在这做错了什么?我的代码会导致性能问题等吗?

Tob*_*fke 25

架构师意味着get并且containsKey具有相同的成本并且可以累积到一个支票中:

Integer val = map.get(c);
if (val != null) {
  ...
} else {
  ...
}
Run Code Online (Sandbox Code Playgroud)

但我想知道为什么建筑师只关心这一点,因为有更多的事情需要改进:

  • 通过接口引用对象(Effective Java 2nd Edition,Item 52)
  • 从Java 1.7开始,您可以使用菱形运算符<>
  • 累计角色的自动装箱操作
  • 如果您使用AtomicInteger(或任何其他可修改的数字类)而不是Integer您甚至可以将get与其中一个put合并

因此,从我的观点来看,使用HashMap时的最佳性能将提供:

Map<Character, AtomicInteger> map = new HashMap<>();
for (Character c : characters) {
    AtomicInteger val = map.get(c);
    if (val != null) {
        val.incrementAndGet();
    } else {
        map.put(c, new AtomicInteger(1));
    }
}
Run Code Online (Sandbox Code Playgroud)

如果字符的范围很小(并且事先已知),则可以使用int数组进行计数.这将是所有可能解决方案中最快的:

char firstCharacter = 'a';
char lastCharacter = 'z';
int[] frequency = new int[lastCharacter - firstCharacter + 1];
for (char c : characters) {
  frequency[c - firstCharacter]++;
}
Run Code Online (Sandbox Code Playgroud)

  • 我还认为`AtomicInteger`应该用于其主要目的 - 作为并发实用程序. (10认同)
  • 如果基于AtomicInteger的解决方案更快,我会非常惊讶.此外,现在我们有了Java8,这一切都可以在一行中完成...... (7认同)

Era*_*ran 18

你的代码是多余的,因为get和containsKey做了几乎相同的工作.您可以检查get是否返回null值,而不是调用containsKey.

代码可以简化为:

HashMap<Character, Integer> map = new HashMap<Character, Integer>();
for (char c : characters) {   
    Integer val = map.get(c);          
    if (val == null)
        val = 0;
    map.put(c,++val);
}
Run Code Online (Sandbox Code Playgroud)

  • 更短:`整数val = map.get(c); map.put(c,1 +(val == null?0:val));` (2认同)
  • @Captain Man嗯,更多的代码比更少的代码更冗余,但我添加了一个解释,让你开心. (2认同)

Raz*_*zib 8

你可以像这样写你的for循环 -

for (char c : characters) {             

   Integer val = map.get(c);
   if (null != val){
      map.put(c, ++val);
   } else {
      map.put(c, 1);
   }
}  
Run Code Online (Sandbox Code Playgroud)

注意:我已修改intInteger以便我可以检查它null如果地图已经包含一个值,那么它将返回该值,它将与您声明的Integer变量一起分配val.否则val就会null.所以我认为你不需要使用Map.containsKey()方法.


Sri*_*ati 7

让我们从您的代码开始,然后开始减少它.

HashMap<Character, Integer> map = new HashMap<Character, Integer>();
int i = 1;

for (char c : characters)
{             
    if (map.containsKey(c))
    {
        int val = map.get(c);
        map.put(c, ++val);
    }
    else map.put(c, i);
}
Run Code Online (Sandbox Code Playgroud)

我要做的第一件事是使用Java 7菱形运算符,并删除变量 i

Map<Character, Integer> map = new HashMap<>();

for (char c : characters)
{
    if (map.containsKey(c))
        map.put(c, ++map.get(c));
    else
        map.put(c, 1);
}
Run Code Online (Sandbox Code Playgroud)

这是我的第一步,我们删除了变量,i因为它始终是常量,1并且在执行期间不会更改.我也简化了声明,并map.get打电话map.put给我.现在,在看到时,我们有三次调用map方法.

Map<Character, Integer> map = new HashMap<>();

for (char c : characters)
{
    Integer i = map.get(c);

    if (i == null) i = 0;

    map.put(c, ++i);
}
Run Code Online (Sandbox Code Playgroud)

这是最好的方式,也是@Eran在上面的回答中所说的.希望这种细分有所帮助


Abh*_*tti 6

for (char c : characters) {   
     Integer val = map.get(c);
     if(val != null){
        map.put(c, ++val); 
     }else{
        map.put(c, 1);
     }
 }
Run Code Online (Sandbox Code Playgroud)

这可能是最好的方式

函数get和contains都做同样的工作......

而不是通过使用get函数同时使用它的好处

使用get函数时,请在此处检查null值.通过避免两次调用,它将改善性能.

注意:在这种情况下,可能看起来性能没有任何改善,但在另一种情况下,它会有大量的数据.


sie*_*egi 6

从Java 8开始,您甚至可以执行以下操作:

final Map<Character, Integer> map = new HashMap<>();
for (char c : characters)
    map.merge(c, 1, Integer::sum);
Run Code Online (Sandbox Code Playgroud)

请注意,您使用此解决方案进行了大量的装箱和拆箱.这应该不是问题,但要注意它是很好的.

上面的代码实际上做了什么(即手动装箱和拆箱):

for (char c : characters)
    map.merge(
        Character.valueOf(c),
        Integer.valueOf(1),
        (a, b) -> Integer.valueOf(Integer.sum(a.intValue(), b.intValue())));
Run Code Online (Sandbox Code Playgroud)