Java 处理映射、equals()和hashCodes()。这有多高效?

Java 处理映射、equals()和hashCodes()。这有多高效?,java,hashmap,Java,Hashmap,我正在写的东西每秒会收到相当多的交易。对于传入的每个事务,都会引用一个映射,其中键值是id,bean将帮助处理该特定事务。基本上,每个事务都带有一个id,将对映射进行查找,以检索相应的bean进行处理。棘手的是,每个事务的id并不意味着与映射中的id精确匹配。更多的问题是从操作开始的。为此,我没有使用字符串作为id,而是创建了一个名为MyId的简单pojo。代码如下: public class MyId { private static final int HASHCODE_CONST

我正在写的东西每秒会收到相当多的交易。对于传入的每个事务,都会引用一个映射,其中键值是id,bean将帮助处理该特定事务。基本上,每个事务都带有一个id,将对映射进行查找,以检索相应的bean进行处理。棘手的是,每个事务的id并不意味着与映射中的id精确匹配。更多的问题是从操作开始的。为此,我没有使用字符串作为id,而是创建了一个名为MyId的简单pojo。代码如下:

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,就像它应该返回的一样。似乎在调用equals()之前,map.containsKey()方法首先调用并比较对象的hashcode方法。如果你的散列值不匹配,它甚至不需要比较。虽然这是可行的,但必须以这种方式实现hashcode方法来欺骗映射,这感觉有点狡猾

我想知道是否有更有效的方法来做到这一点。我们每秒处理相当多的事务,因此需要在地图上查找相当多的内容

PS:我把这个盲编码了,所以我肯定有语法错误。请忽略这些。只是想表达一下大意。

如果您的
hashCode()
方法返回一个常量值,那么您的所有键都将散列到
HashMap
中的同一个bucket中,从而有效地将
HashMap
减少为一个链表,访问时间为O(n)(而不是近似为O(1))

一种可能的解决方案(不节省空间):对于每个字符串,存储与可能的字符串前缀相对应的多个键,但所有键都引用相同的值。例如,对于单词“Hello”,您将存储键“H”、“He”、“Hel”、“Hell”、“Hello”。这显然会占用更多的空间,但查找时间会非常快,您不需要修改类的
equals()
方法来执行“模糊”比较。您可以通过编写自定义类来提高空间效率;e、 g

/**
 * 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;
  }
}
如果您的
hashCode()
方法返回一个常量值,则所有键都将散列到
HashMap
中的同一个存储桶中,从而有效地将
HashMap
减少为一个链表,访问时间为O(n)(而不是近似为O(1))

一种可能的解决方案(不节省空间):对于每个字符串,存储与可能的字符串前缀相对应的多个键,但所有键都引用相同的值。例如,对于单词“Hello”,您将存储键“H”、“He”、“Hel”、“Hell”、“Hello”。这显然会占用更多的空间,但查找时间会非常快,您不需要修改类的
equals()
方法来执行“模糊”比较。您可以通过编写自定义类来提高空间效率;e、 g

/**
 * 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;
  }
}

我认为您正在强制两个不同的对象使用相同的数据结构,这使得您的地图没有那么高效

为了提供更好的解决方案,我可能需要更多信息,例如:地图中的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())

我认为您是在强制两个不同的对象使用相同的数据结构,这使得您的映射效率不高

为了提供更好的解决方案,我可能需要更多信息,例如:地图中的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())

为什么要以如此低效的方式使用HashMap。同样,使用TreeMap可以更快地完成同样的事情——它完全可以完成您想要的。
此外,哈希代码中的const将显示O(n)性能,而TreeMap将显示ln(n)。

为什么以如此低效的方式使用HashMap。同样,使用TreeMap可以更快地完成同样的事情——它完全可以完成您想要的。
此外,哈希代码中的常量将显示O(n)性能,而TreeMap为您提供ln(n)。

此对象甚至不遵循:

  • 如果根据equals(Object)方法两个对象相等,那么对两个对象中的每一个调用hashCode方法必须产生相同的整数结果

  • 根据equals(java.lang.Object)方法,如果两个对象不相等,则对这两个对象中的每一个调用hashCode方法都必须产生不同的整数结果,这不是必需的

但是,程序员应该知道,为不相等的对象生成不同的整数结果可能会提高哈希表的性能


您可能需要测试您的实现(一个始终返回常量的存根)和一个“普通”的
对象,比如
字符串
。请测试测试测试思考测试测试,…

这个目标甚至没有遵循:

  • 如果根据equals(Object)方法两个对象相等,那么对两个对象中的每一个调用hashCode方法必须产生相同的整数结果

  • 根据equals(java.lang.Object)方法,如果两个对象不相等,则对这两个对象中的每一个调用hashCode方法都必须产生不同的整数结果,这不是必需的

但是,程序员应该知道,为不相等的对象生成不同的整数结果可能会提高哈希表的性能

您可能需要测试您的实现(一个始终返回常量的存根)和一个“普通”的
对象,比如
字符串
。请测试测试测试<