哈希表的put性能因键而异

4
我正在尝试从Hibernate中获取的约500万个对象加载到一个哈希映射表中,我为2种类型(A和B)执行此操作。我遍历POJO。Key是来自POJO的字段,值是POJO本身。
1.对于A类类型,键是整数字段。我可以在不到20秒的时间内加载地图。
对于B类
2.a)测试1,我的键是String字段。当我尝试将这些对象加载到新的哈希映射表中时(重新启动Java进程的新尝试,因此无需关心GC),需要约30秒才能将100K个对象加载到映射中。
2.b)测试2,当我尝试使用该类的另一个字段(整数类型)并加载映射时,它像第一个一样工作,并在不到20秒的时间内加载。
2.c)测试3,我想知道问题是否是数据类型。因此,对于类别B,我尝试了另一种方法,使用#2.b中的整数字段创建字符串键(key = int_field +“”),并且在<20秒钟内加载完成。
另一个测试,测试4,我针对类型B的测试是我创建密钥的方式。对于2.c,我像这样创建了密钥
map.put( pojo.getIntField() + "", pojo);
结果如2.c中所述
2.d)但是,当我在POJO中创建了另一个getter,返回int_field +“”并在map put中使用它时,
map.put( pojo.getIntFieldInStringForm(), pojo);
性能恶化到大约30秒,100K个对象。
我知道问题出在密钥上,因为我已经通过将结果对象添加到列表中来验证了db获取阶段,对于两种类型,它在<20秒内加载。
我无法理解其原因。如果有人能够请帮忙解释一下,这将非常有帮助。非常感谢。谢谢。
Map<String, ClassA> map = new HashMap<String, ClassA>();
Session session = sessionFactory.openNewSession();
try {
    Iterator<ClassA> iterator = session.createQuery( "from ClassA" ).setFetchSize( 1000 ).iterate();
    while ( iterator.hasNext() ) {
        ClassB objClassA = iterator.next();
        map.put( objClassB.getIntField(), objClassA );              
    }
}
catch (Exception e) {
    e.printStackTrace();
}
finally {
    session.close();
}


测试2.a

Map<String, ClassB> map = new HashMap<String, ClassB>();
Session session = sessionFactory.openNewSession();
try {
    Iterator<ClassB> iterator = session.createQuery( "from ClassB" ).setFetchSize( 1000 ).iterate();
    while ( iterator.hasNext() ) {
        ClassB objClassB = iterator.next();
        map.put( objClassB.getStringField(), objClassB );               
    }
}
catch (Exception e) {
    e.printStackTrace();
}
finally {
    session.close();
}


测试 #2.b

Map<Integer, ClassB> map = new HashMap<Integer, ClassB>();
Session session = sessionFactory.openNewSession();
try {
    Iterator<ClassB> iterator = session.createQuery( "from ClassB" ).setFetchSize( 1000 ).iterate();
    while ( iterator.hasNext() ) {
        ClassB objClassB = iterator.next();
        map.put( objClassB.getIntField(), objClassB );              
    }
}
catch (Exception e) {
    e.printStackTrace();
}
finally {
    session.close();
}


#2.c 测试

Map<String, ClassB> map = new HashMap<String, ClassB>();
Session session = sessionFactory.openNewSession();
try {
    Iterator<ClassB> iterator = session.createQuery( "from ClassB" ).setFetchSize( 1000 ).iterate();
    while ( iterator.hasNext() ) {
        ClassB objClassB = iterator.next();
        map.put( objClassB.getIntField() + "", objClassB );             
    }
}
catch (Exception e) {
    e.printStackTrace();
}
finally {
    session.close();
}


测试2.d

Map<String, ClassB> map = new HashMap<String, ClassB>();
Session session = sessionFactory.openNewSession();
try {
    Iterator<ClassB> iterator = session.createQuery( "from ClassB" ).setFetchSize( 1000 ).iterate();
    while ( iterator.hasNext() ) {
        ClassB objClassB = iterator.next();
        map.put( objClassB.getIntFieldInStringForm() + "", objClassB );             
    }
}
catch (Exception e) {
    e.printStackTrace();
}
finally {
    session.close();
}

@Andreas 抱歉,我打错了。它只是 getIntFieldInStringForm()。我已经在问题中进行了更正。@Andreas 和 @Vince Emigh,我使用的字符串长度为8到10个字符。@Erwin,感谢您的意见,我会尽快发布示例代码以获得更清晰的理解。 - PKU
我会在性能分析器中运行这两个测试,看看它告诉我的内容。顺便问一下,你是如何实例化你的POJO的?它们是由Hibernate生成的吗? - david a.
已在此处添加了代码片段。@davida。是的,我正在使用Hibernate进行POJO创建。 - PKU
我有一种感觉,JVM正在优化掉objClassB.getIntField() + ""中的空字符串,并仅留下整数作为哈希键。通过向字段添加一些非空字符串来测试它,看看性能是否仍然保持在<20秒。从我所看到的情况来看,显而易见的罪魁祸首是字符串,需要为从数据库中提取的每个对象计算其哈希码。 - smac89
@khredos,你看到任何可能与字符串的基数有关的东西吗?我读到了一些关于这个的内容,想知道这个原因是否是因为有很多冲突。然而,我没有深入研究过。我试图验证哈希码(不保证唯一)是否在任何地方重复,但现在来看,这似乎太牵强了,因为数字太多了。但如果需要,我也可以做到。 - PKU
显示剩余5条评论
1个回答

2
为了将项目放入HashMap中,需要计算键的hashCode。如果你的字符串为8-10个字符,需要进行一些计算才能将它们映射到32位的hashcode上。你的整数键有多大?如果它们小于100,000,那么只需要从5个字符中计算hashCode,速度会快一些。
当两个键计算出相同的hashcode时,也会对性能造成影响,这种情况可能会在使用String键时发生几次。
当你使用唯一的整数作为键时,哈希碰撞永远不会发生。而且,如果你使用转换为整数的字符串,字符串哈希算法的碰撞也会更少。

我的整数键是8位数。另外,让我困惑的是2.c和2.d之间的性能差异。我在这里使用的字符串键基本上是以字符串格式表示的数字。所以我尝试将它们转换为整数,并尝试(大约有8位数),这也花了大约30秒来处理10万条记录。 - PKU
根据 HashMap 的初始容量和负载因子,整数键可能会发生冲突。由于 @nanda-kumar 正在将 5M 个项加载到地图中且未指定初始容量,因此该地图将被频繁重新散列。 - TreeRex
在我们谈论负载因子时,我想再补充一点。我进行了一些测试,使用了5M的初始容量和1.0的负载因子。考虑到所有重复键(不是哈希码)大约有500个,但我并没有看到性能上的很大提升。速度下降了几秒钟,对于10万条记录而言,不到30秒,这与我在测试#1中看到的数字相去甚远。 - PKU
虽然我仍在努力弄清楚2.c和2.d之间性能差异的原因。但是,现在我通过以下方式获得了我期望的结果:try { Iterator iterator = session .createQuery( "select new ModifiedClassB( stringField, cast( stringField as int ), other_fields... ) from ClassB" ) .setFetchSize( 1000 ).iterate(); while ( iterator.hasNext() ) { ModifiedClassB obj = iterator.next(); map.put( obj.getStringFieldCastToInt(), obj ); } } catch (Exception e) { e.printStackTrace(); } finally { session.close(); } - PKU
1
@NandaKumar,至于2.c和2.d之间的区别:我的猜测是你的pojo实际上是由Hibernate创建的代理,当你调用getter方法时它会执行各种操作。不过不确定。例如,在jvisualvm中进行分析可能会有所帮助。 - david a.
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接