[英]Dealing with maps, equals() and hashCodes(). How efficient is this?
我正在写一些东西,每秒将接收大量事务。 对于每个进来的事务,都会引用一个映射,该映射的键值是id和一个有助于处理特定事务的bean。 基本上,每个事务都带有一个ID,将对地图进行查找以检索相应的bean进行处理。 粘性部分在于,每个交易的ID均不与地图中的ID完全匹配。 更多的是从操作开始的。 为此,我创建了一个名为MyId的简单pojo,而不是使用字符串作为id。 以下代码:
public class MyId
{
private static final int HASHCODE_CONSTANT = 1;
private String value;
public MyId(String value)
{
this.value = value;
}
@Override
public int hashCode()
{
//Returns the same hashcode value for all instances of this pojo
return HASHCODE_CONSTANT;
}
@Override
public boolean equals(Object obj)
{
//Checks for object type, forcibly casts and then compares the starts with
if(obj instanceof MyId)
{
if(!(obj == null || "".equals(obj)))
{
return this.value.startsWith(((MyId)obj).getValue());
}
}
return false;
}
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
//Test
public static void main(String[] args)
{
Map map = new HashMap();
map.put(new MyId("123456"), "");
System.out.println("Result: " + map.containsKey(new MyId("12345677")));
System.out.println("Result: " + map.containsKey(new MyId("11234567")));
}
}
第一个测试返回true,第二个测试返回false,这与预期的一样。 似乎map.containsKey()方法在调用equals()之前先调用并比较对象的哈希码方法。 如果您的哈希值不匹配,它甚至都不会比较。 尽管这样做有效,但必须以这种方式实现哈希码方法才能欺骗地图,这让人有些困惑。
想知道是否有更有效的方法来做到这一点。 我们正在处理的相当数量的交易/秒,因此相当多的查找在地图上。
PS:我编码了这个盲人,所以我确定有语法错误。 请忽略那些。 只是试图传达一般想法。
如果您的hashCode()
方法返回一个恒定值,则所有键都将散列到HashMap
的同一存储桶中,从而有效地将HashMap
减少为链接列表,访问时间为O(n)(而不是近似O(1))。
一种可能的解决方案(不节省空间):对于每个字符串,都存储多个与可能的String首选项相对应的键,但所有键都引用相同的值 。 例如,对于单词“ Hello”,您将存储键“ H”,“ He”,“ Hel”,“ Hell”,“ Hello”。 显然这会占用更多空间,但是查找时间会非常快,并且您无需破坏类的equals()
方法即可执行“模糊”比较。 您可以通过编写自定义类来提高空间效率。 例如
/**
* Class representing String prefix.
* Storage overhead == original string + two ints.
*/
public class Prefix {
private final String str;
private final int len;
private final int hc;
public Prefix(String str, int len) {
this.str = str;
this.len = len;
this.hc = toString().hashCode(); // Precompute and store hash code.
}
public String toString() {
return str.substring(0, len);
}
public int hashCode() {
return hc;
}
public boolean equals(Object o) {
boolean ret;
if (this == o) {
ret = true;
} else if (o instanceof Prefix) {
ret = toString().equals(((Prefix)o).toString());
} else {
ret = false;
}
return ret;
}
}
如果您的比较器使用startsWith()
,则哈希映射是错误的数据结构。 您需要一些可以按字母首字母快速找到键的东西:您需要树形图。
与哈希图不同,树图是有序的。 因此,您可以从根开始搜索,而不是盲目地进入奇数分布的数学空间,性能将为O(log(n))。 Java实现的主要问题:它已关闭并锁定。 您不能真正将其扩展为使用startsWith()
搜索。
在您的情况下,事务处理器的数量似乎是稳定的(这意味着您不会一直创建新的处理器)。 如果不是这种情况,那么处理器的数量应该相对较少(例如,<1000)。
我的建议是使用一个数组并将所有处理器放入该数组中。 按其ID对它们进行排序。
现在,您可以使用Arrays.binarySearch(T[] a, T key, Comparator<? super T> c)
使用比较器中equals()
的代码来有效地查找元素。
我认为哈希表不是一个好的解决方案。 @Adamskis加载带有前缀的哈希表的想法很有趣,但是我认为如果键共享前缀或如果您需要即时插入/删除条目,它将变得混乱。
如果您的地图/查找表条目没有变化,那么使用Arrays.binarySearch(...)
数组和Arrays.binarySearch(...)
(@ Aaron建议)是一个很好的解决方案。 它应该给你O(log(N))查找。
但是,如果您需要即时插入或删除映射条目,则对于基于数组的解决方案,这些操作将为O(N)。 相反,您应该使用TreeMap,并使用NavigableMap API中的方法(例如'lowerKey() ,
floorKey() and
HigherKey()`)在表中找到“最接近”的匹配项。 那应该给你O(log(N))进行查找,插入和删除。
为什么以这种低效的方式使用HashMap。 使用TreeMap可以更快地获得相同的结果-完全可以完成所需的操作。 哈希代码中的const也将显示O(n)性能,而TreeMap则为ln(n)。
该对象甚至不遵循hashCode的一般约定 :
如果根据equals(Object)方法,两个对象相等,则在两个对象中的每个对象上调用hashCode方法必须产生相同的整数结果。
根据equals(java.lang.Object)方法,如果两个对象不相等,则不需要在两个对象中的每个对象上调用hashCode方法必须产生不同的整数结果。
但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
您可能要测试实现(总是返回常量的存根)和“正常” Object
(如String
。 请测试 , 测试 , 测试 , 思考 , 测试 , 测试 , 测试 ,...
您的equals()方法不遵守Object.equals()的约定-它不是可传递的。 它将使“ a” .equals(“ ab”)返回true,而“ a” .equals(“ ac”)返回true,但是“ ab” .equals(“ ac”)返回false。
如果您尝试基于字符串前缀存储与字符串相关的对象,则可能需要使用trie进行研究 。
好的,谢谢您的投入。 认为问题陈述中最大的因素之一是存储的密钥几乎总是比比较短。 为此,提出了两种不同的方法来解决问题陈述,以防万一将来有人遇到类似情况时需要参考:
按照正常使用地图。 当输入比较出现时,进行比较。 如果没有命中,则修剪字符串并再次进行比较。
这是一位小鸽友。 相当喜欢我读到的关于Don Knuth的Trie(感谢Avi的参考),并提出了一个非常简单的实现。 (仅供参考,Ids的格式应类似于1.1.1.2。请记住这一点,以使示例代码看起来不会太怪异)。
公共类Trie {private HashMap map = new HashMap();
public Trie()
{
}
public Object get(String key)
{
return recurse(key.split("\\."), map, 0);
}
protected Object recurse(String[] key, Map map, int location)
{
Object value = map.get(key[location]);
if(value instanceof Map)
return recurse(key, (Map)value, location+1);
else
return value;
}
public void addKey(String key, Object value)
{
String[] keys = key.split("\\.");
addKey(keys, map, 0, value);
}
protected void addKey(String[] key, Map map, int location, Object value)
{
if((location+1) == key.length)
{
//end of the road. value insertion
map.put(key[location], value);
}
else
{
Map hashMap = (Map) map.get(key[location]);
if(!(map.containsKey(key[location])))
{
hashMap = new HashMap();
map.put(key[location], hashMap);
}
addKey(key, hashMap, location+1, value);
}
}
public static void main(String[] args)
{
Trie trie = new Trie();
trie.addKey("1.1.2.1", "1.1.2.1");
trie.addKey("1.1.2.2", "1.1.2.2");
trie.addKey("1.1.2.3.1", "1.1.2.3.1");
trie.addKey("1.1.2.3.2", "1.1.2.3.2");
trie.addKey("1.1.2.4", "1.1.2.4");
System.out.println(trie.get("1.1.2.1.0")); //returns 1.1.2.1
System.out.println(trie.get("1.1.2.3.1.0")); //returns 1.1.2.3.1
System.out.println(trie.get("1.1.2.4.1.0")); //returns 1.1.2.4
}
}
在我的用例中,我不希望Trie的深度增长超过2-3个级别,因此,如果您的树结构变得非常复杂,则可能需要分析性能问题,并查看额外的查找是否会导致过多的开销。 哦,由于我们只处理String对象,因此这两种方法都不需要对hashCode进行任何狡猾的更改或等于合同。
注意事项:
尚未决定使用哪个来进行待定行为分析。 问题是大多数时候,比较值将与存储在地图中的比较值完全相同,因此简单查找就足够了。 它只是需要满足的其他“特殊”情况。 总而言之,如果特殊事件的发生频率往往非常低,我很想去最初的进阶(#1)。 绝大多数搜索将很快进行,当出现特殊情况时,我将承受字符串处理开销的痛苦。 如果情况相反,#2可能会更具吸引力。
PS:欢迎评论
我认为您正在强迫两个不同的对象使用相同的数据结构,这使您的地图效率不高。
为了提供更好的解决方案,我可能需要更多信息,例如:地图中的ID是否始终为6位数字?
好的,那么您可以例如创建两个这样的类。
public class MyIdMap {
private String value;
public MyIdMap(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyIdMap other = (MyIdMap) obj;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
public class MyId {
private String value;
public MyId(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public MyIdMap getMyIDMap() {
return new MyIdMap(value.substring(0, 6));
}
}
将MyIdMap放在地图中,然后在查找时,只需使用map.get(myId.getMyIdMap())
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.