Java 为什么在使用乘法值遍历一个bucket时只调用一次hashCode()?
我有一节以下的课Java 为什么在使用乘法值遍历一个bucket时只调用一次hashCode()?,java,equals,hashcode,Java,Equals,Hashcode,我有一节以下的课 public class Animal { private int hash; public Animal(int hash) { this.hash = hash; } @Override public int hashCode() { System.out.println(hash); return hash; } } 这个密码呢 public static vo
public class Animal {
private int hash;
public Animal(int hash) {
this.hash = hash;
}
@Override
public int hashCode() {
System.out.println(hash);
return hash;
}
}
这个密码呢
public static void main(String[] args) {
Map<Animal, Integer> map = new HashMap<>();
for (int i = 0; i < 4; i++) {
map.put(new Animal(16 * i), i);
}
Animal an = new Animal(16*4);
map.put(an, 1);
for (int i = 5; i < 9; i++) {
map.put(new Animal(16 * i), i);
}
Integer value = map.get(an);
}
据我所知,由于哈希代码的原因,所有这些值都应该在一个bucket中。在最后一次调用map.getan时,根据控制台,hashCode只被调用一次,但在通过bucket进行迭代并找到具有正确hashCode的条目时,是否应该多次调用它
EDIT1:如果我使用控制台日志记录实现equals,则不会再次根据控制台调用它,仅当有两个对象具有相同的哈希代码时才会调用它,例如,如果我将其添加到我的代码映射中。putnew Animal16*3,4;,在这种情况下,当从map获取对象时,hashCode会被调用两次。否,hashCode用于查找bucket,这需要在map.getan参数上调用一次hashCode
然后将bucket中的元素与equals进行比较,以找到正确的对象。否,hashcode用于查找bucket,这需要对map.getan参数调用的hashcode进行一次调用
然后将bucket中的元素与equals进行比较,以找到正确的对象。单个bucket可能包含具有不同hashCode的键,并且将相关bucket的键的hashCode与您正在添加/搜索的键进行比较。但是,hashCode缓存在Map.Entry中,因此无需为映射中已经存在的Entry调用键的hashCode方法:
哈希值与缓存的密钥哈希值进行比较,这意味着无需再次调用哈希代码。单个bucket可能包含具有不同哈希代码的密钥,并且相关bucket的密钥哈希代码与您正在添加/搜索的密钥进行比较。但是,hashCode缓存在Map.Entry中,因此无需为映射中已经存在的Entry调用键的hashCode方法:
哈希值与缓存的密钥哈希值进行比较,这意味着无需再次调用哈希代码。最多可以期望调用哈希代码。调用次数是HashMap的一个实现细节,您不应该期望有任何特定的行为,也不应该期望观察到的行为是稳定的。由于hashCode的实现方式可能不同,甚至代价高昂,因此只调用一次是合理的选择,即使需要多次调用,也可以使用返回值而不是对hashCode的新调用。然而,这仅仅是猜测,不应该假设。最多可以期望调用hashCode。调用次数是HashMap的一个实现细节,您不应该期望有任何特定的行为,也不应该期望观察到的行为是稳定的。由于hashCode的实现方式可能不同,甚至代价高昂,因此只调用一次是合理的选择,即使需要多次调用,也可以使用返回值而不是对hashCode的新调用。然而,这只是猜测,不应该假设。每个人都已经回答了这个问题。我想从HashMap中捕获代码以提供更多信息 当我们从Map调用.put时,它们首先在内部调用hashKey 在hash方法中,您将看到hashCode被调用 然后在putVal方法中,有两个位置等于get,如下所示 这就是为什么我们会多次调用hashCode
如果您需要了解更多信息,请查看此链接以了解HashMap的实现每个人都已经回答了问题。我想从HashMap中捕获代码以提供更多信息 当我们从Map调用.put时,它们首先在内部调用hashKey 在hash方法中,您将看到hashCode被调用 然后在putVal方法中,有两个位置等于get,如下所示 这就是为什么我们会多次调用hashCode
请检查此链接以了解HashMap的实现,如果您需要了解更多内容,则在确定bucket后,HashMap将使用.eqauls来确定与提供的对象匹配的链表节点。见实施:
/**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
一旦确定了bucket,HashMap将使用.eqauls来确定与提供的对象匹配的链表节点。见实施:
/**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
map.putnew Animal16*i,i
执行此操作时,Animal对象是key,其哈希值将是值i所在的bucket。把它想象成地图上甚至没有存储的动物对象
当您添加所有9项时,具有值的存储桶为0,16,32,48,…,16*8
当您执行map.getaa时,将从64的对象返回一个的哈希
tl;dr所有对象都属于不同的存储桶,因为它们的哈希值不同
执行此操作时,Animal对象是key,其哈希值将是值i所在的bucket。把它想象成动物的物体是不可能的
甚至存储在地图中
当您添加所有9项时,具有值的存储桶为0,16,32,48,…,16*8
当您执行map.getaa时,将从64的对象返回一个的哈希
tl;dr所有对象都属于不同的bucket,因为它们的hashvalue是不同的注意,您没有实现equals。为什么您认为它们都应该在同一个bucket中?您创建的动物实例大多具有不同的哈希代码值,为什么它们会有哈希冲突?除此之外:哈希映射缓存它存储的键的哈希代码,因此在添加它们时,它只需要对其键调用一次哈希代码。@JoachimSauer index_of_bucket=hashCodekey&n-1,我的哈希代码是0、16、32。。。默认情况下,地图的大小为16@BogdanTimofeev:这不是Java HashMap使用哈希的方式,它实际上是在使用哈希之前对其进行操作。检查您最喜欢的JVM实现中的private HashMap.hashObject方法。请注意,您没有实现equals。为什么您认为它们都应该在同一个bucket中?您创建的动物实例大多具有不同的哈希代码值,为什么它们会有哈希冲突?除此之外:哈希映射缓存它存储的键的哈希代码,因此在添加它们时,它只需要对其键调用一次哈希代码。@JoachimSauer index_of_bucket=hashCodekey&n-1,我的哈希代码是0、16、32。。。默认情况下,地图的大小为16@BogdanTimofeev:这不是Java HashMap使用哈希的方式,它实际上是在使用哈希之前对其进行操作。检查您最喜欢的JVM实现中的private HashMap.hashObject方法。不调用equals也请参见我的编辑1,看起来没有冲突all@BogdanTimofeev如果没有必要的话就不打电话了。这个问题是关于冲突还是关于何时使用equals和hashCode?equals没有被调用也请参见我的编辑1,看起来在这里没有冲突all@BogdanTimofeev如果没有必要的话就不打电话了。这个问题是关于冲突还是关于何时使用equals和hashCode?
/**
* Implements Map.get and related methods
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}