Java 顺序无关散列算法
我目前正在为我的自定义编程语言开发一个集合库。我已经有了几种数据类型(Collection、List、Map、Set)和它们的实现(可变和不可变),但到目前为止我缺少的是Java 顺序无关散列算法,java,algorithm,hash,set,Java,Algorithm,Hash,Set,我目前正在为我的自定义编程语言开发一个集合库。我已经有了几种数据类型(Collection、List、Map、Set)和它们的实现(可变和不可变),但到目前为止我缺少的是hashCode和equals。由于列表是有序集合,所以这些对列表来说没有问题,但对于集合和映射来说,它们扮演着特殊的角色。如果两个集合具有相同的大小和相同的元素,则认为它们相等,并且集合保持它们相等的顺序不应影响它们的相等性。由于equals hashCode契约,hashCode实现也必须反映这种行为,这意味着具有相同元素但
hashCode
和equals
。由于列表是有序集合,所以这些对列表来说没有问题,但对于集合和映射来说,它们扮演着特殊的角色。如果两个集合具有相同的大小和相同的元素,则认为它们相等,并且集合保持它们相等的顺序不应影响它们的相等性。由于equals hashCode契约,hashCode实现也必须反映这种行为,这意味着具有相同元素但顺序不同的两个集合应该具有相同的hash代码。(这同样适用于地图,从技术上讲,地图是一组键值对)
示例(伪代码):
让set1:Set=[“a”、“b”、“c”]
设set2:Set=[“b”,“c”,“a”]
set1==set2//应返回true
set1.hashCode==set2.hashCode//也应返回true
如何实现一个相当好的散列算法,使上例中的
hashCode
s返回相同的值?以下是可能实现的伪代码:
String hashCode = null;
for(element : elements){
hashCode = xor(hashCode, getHashCode(element));
}
return hashCode;
xor
函数应返回与两个参数中最长的字符串一样长的字符串。它将对每个参数中的位进行异或运算,直到到达其中一个参数的末尾。然后,它将从较长的字符串中提取剩余的位,并将这些位附加到上
这个实现意味着一个集合的hashCode与它最长元素的hashCode一样长。因为要对位进行XOR运算,所以不管元素的顺序如何,哈希代码最终都是相同的。但是,与任何散列实现一样,都有可能发生冲突。JDK本身提出了以下解决方案。接口合同规定: 返回此集合的哈希代码值。集合的哈希代码定义为集合中元素的哈希代码之和,其中空元素的哈希代码定义为零。这确保了s1.equals(s2)意味着s1.hashCode()==s2.hashCode()对于任意两个集合s1和s2,这是Object.hashCode()的通用合同所要求的 使用条目的哈希代码之和的替代方法是使用,例如,
^
(XOR)运算符
Scala语言使用算法的排序不变版本(参见私有类)来实现its和类似集合的
hashCode
(或##
)方法。您可以按字母顺序计算对集合排序的哈希和
这里有一个C#示例-我希望您能用Java翻译它:)
静态字符串GetHash(列表l)
{
使用(System.Security.Cryptography.MD5 MD5=System.Security.Cryptography.MD5.Create())
{
返回BitConverter.ToString(md5.ComputeHash(l.OrderBy(p=>p).SelectMany(s=>System.Text.Encoding.ASCII.GetBytes(s+(char)0)).ToArray())。替换(“-”,”);
}
}
集合中的一对术语(和、积)如何?就我所见,对于不同的数字集来说,这两种方法并不常见。例如,类似于(e1.hashCode()+e2.hashCode()+…+en.hashCode())^(e1.hashCode()*e2.hashCode()*…*en.hashCode())
?您是否尝试过看看Java是如何实现的?刚刚做了,它对元素的hashCode
求和“和的散列”和“散列的和”之间有很大的区别。如你的例子所示,前者是有问题的。如果单个散列在大范围内分布良好,则后者的问题较少。但是,当我需要int
hashCode时,如何使用String
?这似乎是一个非常灵活的解决方案。@Clashsoft我不确定您想要的是int
还是String
。如果它只是一个int,那么获取各个元素的hashcode之和将得到您所需要的,只要溢出环绕而不是导致错误。如果溢出导致错误,则需要显式处理该情况并手动包装。同样的概念。谢谢你的回答,但我想找到一个不同的解决方案,而不是对元素的哈希代码求和(参见注释)。@Clashsoft-请注意,[1,4]是否会与[2,3]冲突,这实际上取决于你对哈希数的实现。记住,您是对数字的散列求和,而不是对数字求和。此外,尽管字符串实现的资源更为密集,但这也意味着它在散列中有更多的位,因此发生冲突的可能性更小。通常,整数的散列码(如果不是超级安全的话)就是整数本身。所以1.hashCode==1
。另外,由于hashCode
被强制为int
,我最终还是要使用string.hashCode
。正如我在评论中所说的,我已经找到了这个问题的JDK解决方案,但我想知道更有用的无序集合哈希算法,冲突可能性更小。@Clashsoft冲突可能性是多少?如果单个哈希代码中只有一个工作正常,则整个哈希算法将均匀分布。@b很遗憾,结果并不一致@augurar实数和32位有符号整数之间有非常重要的区别。这是其中之一。编写java.set.Util
的人知道他们在做什么,并在这里提出了一个好的策略。Scala的排序不变哈希仅基于元素哈希的和、积(不包括零)、计数和异或,因此它不像“排序不变的哈希算法版本”那样具有抗冲突性听起来很像。这比只对散列求和要好,但仍然不行
String hashCode = null;
for(element : elements){
hashCode = xor(hashCode, getHashCode(element));
}
return hashCode;
static String GetHash(List<String> l)
{
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
return BitConverter.ToString(md5.ComputeHash(l.OrderBy(p => p).SelectMany(s => System.Text.Encoding.ASCII.GetBytes(s + (char)0)).ToArray())).Replace("-", "");
}
}