当给定一组静态物体插入其中重复,需要具有最佳性能的并发查询,这是更好的,一个(因为一旦装好了很少,如果发生了改变,感觉静态)的HashMap
或使用一些自定义的比较二进制搜索一个数组?
答案是对象或结构类型的功能? 哈希和/或同等功能的表现? 哈希的独特性? 列表的大小? Hashset
尺寸/集的大小?
一组是我看的大小可以从任何地方500K 10米 - 柜面的信息是有益的。
虽然我在寻找一个C#的答案,我觉得真正的数学答案不在于语言,所以我不包括标签。 但是,如果有C#具体的事情要注意的,这些信息是需要的。
Answer 1:
好吧,我会尽量简短。
C#简单的答案:
测试两种不同的方法。
.NET提供的工具用一行代码来改变你的方法。 否则,使用System.Collections.Generic.Dictionary,并确保有大量的初始容量初始化它,否则你会通过你的生活将其余项目因工作GC已经做收集旧桶阵列。
更长的答案:
一个哈希表几乎不断查找时间和获取到的项目在哈希表在现实世界并不仅仅需要计算一个哈希值。
为了得到一个项目,你哈希表会做这样的事情:
- 拿到钥匙的散列
- 获取该哈希桶号码(通常是地图功能看起来像这样水桶=哈希%bucketsCount)
- 遍历项目链(基本上是共享相同的桶,大多数哈希表使用的处理桶/哈希冲突这种方法的项目列表)启动在那个桶和比较项目的您要添加一个每个键/删除/更新/检查载。
查找时间取决于如何“好”(是如何稀疏的输出)和快速是你的哈希函数,您使用的桶的数量和速度是关键比较器,它并不总是最好的解决方案。
一个更好的,更深入的解释: http://en.wikipedia.org/wiki/Hash_table
Answer 2:
对于非常小集合的差异将是微不足道的。 在你的范围(500K项目)的低端,你将开始看到一个区别,如果你正在做大量的查找。 二进制搜索将是O(log n)的,而散列查询将是O(1), 摊销 。 那是不一样的真正的稳定,但你仍然必须有一个非常可怕的哈希函数来获得比二进制搜索性能差。
(当我说“可怕的哈希值”,我的意思是这样的:
hashCode()
{
return 0;
}
是啊,它的速度极快本身,而是使你的哈希映射成为一个链表。)
ialiashkevich写使用阵列和字典的两种方法比较一些C#代码,但它使用长值的键。 我想测试的东西,会在查找过程中实际执行的哈希函数,所以我修改的代码。 我改成了使用的字符串值,我重构了填充,并查找节放到自己的方法,所以它更容易在探查见。 我还留在所用长值的代码,就像比较点。 最后,我摆脱了自定义的二进制搜索功能和所使用的一个在Array
类。
下面是代码:
class Program
{
private const long capacity = 10_000_000;
private static void Main(string[] args)
{
testLongValues();
Console.WriteLine();
testStringValues();
Console.ReadLine();
}
private static void testStringValues()
{
Dictionary<String, String> dict = new Dictionary<String, String>();
String[] arr = new String[capacity];
Stopwatch stopwatch = new Stopwatch();
Console.WriteLine("" + capacity + " String values...");
stopwatch.Start();
populateStringArray(arr);
stopwatch.Stop();
Console.WriteLine("Populate String Array: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
populateStringDictionary(dict, arr);
stopwatch.Stop();
Console.WriteLine("Populate String Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
Array.Sort(arr);
stopwatch.Stop();
Console.WriteLine("Sort String Array: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
searchStringDictionary(dict, arr);
stopwatch.Stop();
Console.WriteLine("Search String Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
searchStringArray(arr);
stopwatch.Stop();
Console.WriteLine("Search String Array: " + stopwatch.ElapsedMilliseconds);
}
/* Populate an array with random values. */
private static void populateStringArray(String[] arr)
{
for (long i = 0; i < capacity; i++)
{
arr[i] = generateRandomString(20) + i; // concatenate i to guarantee uniqueness
}
}
/* Populate a dictionary with values from an array. */
private static void populateStringDictionary(Dictionary<String, String> dict, String[] arr)
{
for (long i = 0; i < capacity; i++)
{
dict.Add(arr[i], arr[i]);
}
}
/* Search a Dictionary for each value in an array. */
private static void searchStringDictionary(Dictionary<String, String> dict, String[] arr)
{
for (long i = 0; i < capacity; i++)
{
String value = dict[arr[i]];
}
}
/* Do a binary search for each value in an array. */
private static void searchStringArray(String[] arr)
{
for (long i = 0; i < capacity; i++)
{
int index = Array.BinarySearch(arr, arr[i]);
}
}
private static void testLongValues()
{
Dictionary<long, long> dict = new Dictionary<long, long>(Int16.MaxValue);
long[] arr = new long[capacity];
Stopwatch stopwatch = new Stopwatch();
Console.WriteLine("" + capacity + " Long values...");
stopwatch.Start();
populateLongDictionary(dict);
stopwatch.Stop();
Console.WriteLine("Populate Long Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
populateLongArray(arr);
stopwatch.Stop();
Console.WriteLine("Populate Long Array: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
searchLongDictionary(dict);
stopwatch.Stop();
Console.WriteLine("Search Long Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
searchLongArray(arr);
stopwatch.Stop();
Console.WriteLine("Search Long Array: " + stopwatch.ElapsedMilliseconds);
}
/* Populate an array with long values. */
private static void populateLongArray(long[] arr)
{
for (long i = 0; i < capacity; i++)
{
arr[i] = i;
}
}
/* Populate a dictionary with long key/value pairs. */
private static void populateLongDictionary(Dictionary<long, long> dict)
{
for (long i = 0; i < capacity; i++)
{
dict.Add(i, i);
}
}
/* Search a Dictionary for each value in a range. */
private static void searchLongDictionary(Dictionary<long, long> dict)
{
for (long i = 0; i < capacity; i++)
{
long value = dict[i];
}
}
/* Do a binary search for each value in an array. */
private static void searchLongArray(long[] arr)
{
for (long i = 0; i < capacity; i++)
{
int index = Array.BinarySearch(arr, arr[i]);
}
}
/**
* Generate a random string of a given length.
* Implementation from https://stackoverflow.com/a/1344258/1288
*/
private static String generateRandomString(int length)
{
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[length];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
return new String(stringChars);
}
}
这里有几个不同大小的集合的结果。 (时报以毫秒为单位)。
500000个长值...
填充长词典:26
填充多头排列:2
搜索龙词典:9
搜索多头排列:80
500000个字符串值...
填充字符串数组:1237
填充字符串词典:46
排序字符串数组:1755
搜索字符串字典:27
搜索字符串数组:1569
百万长值...
填充长词典:58
填充多头排列:5
搜索龙词典:23
搜索多头排列:136
百万字符串值...
填充字符串数组:2070
填充字符串字典:121
排序字符串数组:3579
搜索字符串字典:58
搜索字符串数组:3267
3000000个长值...
填充长字典:207
填充多头排列:14
搜索龙词典:75
搜索多头排列:435
3000000个字符串值...
填充字符串数组:5553
填充字符串字典:449
排序字符串数组:11695
搜索字符串字典:194
搜索字符串数组:10594
千万长值...
填充长字典:521
填充多头排列:47
搜索龙字典:202
搜索多头排列:1181
千万字符串值...
填充字符串数组:18119
填充字符串字典:1088
排序字符串数组:28174
搜索字符串字典:747
搜索字符串数组:26503
而对于比较,以下是节目(10万条记录和查找)的最后一次运行探查器输出。 我强调了相关的功能。 他们相当密切与上面的秒表计时指标达成一致。
你可以看到,词典的查找比二进制搜索快得多,并且(预期)的差异更加明显较大的集合。 所以,如果你有一个合理的散列函数(除了少数碰撞相当快),哈希查找应为打在这个范围内收集二进制搜索。
Answer 3:
由鲍比,比尔和科尔宾答案是错的。 O(1)不大于O(log n)的一个固定/界n慢:
数(n)是恒定的,所以它依赖于一定的时间。
而对于一个缓慢的散列函数,听说过MD5的?
默认字符串哈希算法可能触及的所有字符,可以得到比一般的慢容易100倍比较长字符串键。 去过也做过。
你也许可以(部分)使用基数。 如果你能在256块大致相同大小的块分手了,你看2K到40K的二进制搜索。 这很可能会提供更好的性能。
[编辑]太多人否决他们不明白。
字符串比较二进制搜索排序集合有一个非常有趣的特性:他们得到的越慢越贴近目标。 首先,他们会打破的第一个字符,只有在最后结束。 假设恒定的时间对他们来说是不正确。
Answer 4:
唯一合理的这个问题的答案是:这要看。 这取决于你的数据,你的数据的形状,你的哈希实现,您的二进制搜索实现的规模,并在您的数据存在(即使它不是在问题中提到)。 一对夫妇其他的答案说的那样多,所以我可以删除此。 但是,它可能是不错的分享我从反馈学会了我原来的答复。
- 我写道,“ 散列算法是O(1),而二分查找是O(log n)的。” -正如在评论中指出,大O符号估计的复杂性,而不是速度。 这是千真万确的。 值得一提的是,我们通常使用的复杂性来获得的算法的时间和空间要求感。 所以,虽然这是愚蠢的假设的复杂性是严格相同的速度,在你的脑海里没有时间或空间估计的复杂性是不寻常的。 我的建议:避免大O符号。
- 我写的,“ 所以,当n趋近于无穷大 ......” -这是关于我可能已经包含在回答最愚蠢的事情。 英飞尼迪无关,与你的问题。 你提到的上限为10万元。 忽略无穷大。 正如评论者所指出的,非常大的数字会制造种种用哈希问题。 (非常大的数字不作二进制搜索在公园里散步无论是。)我的建议:除非你的意思是无限不提无穷大。
- 同样来自评论:谨防默认字符串哈希(?你哈希字符串你不提。),数据库索引通常是B树(深思)。 我的建议:考虑所有的选择。 考虑其他的数据结构和方法......就像一个老式的特里 (用于存储和检索字符串)或R-树 (空间数据)或MA-FSA (最小非循环有限状态自动机-小存储空间)。
鉴于意见,你可能会认为,谁使用哈希表的人精神错乱。 是哈希表鲁莽和危险? 难道这些人疯了吗?
原来,他们不是。 正如二叉树擅长某些事情(在序遍历数据,存储效率),哈希表有他们眼前一亮为好。 特别是,他们可以在减少读取所需的获取你的数据的数量非常不错。 散列算法可以生成一个位置,并直接跳转到它在内存或磁盘上,而各比较期间的二进制搜索读取数据,以决定下一步读什么。 每个读出具有用于高速缓存未命中是幅值(或更多)比CPU指令慢的顺序的可能性。
这并不是说哈希表比二进制搜索更好。 他们不是。 这也并不是说所有的散列值和二进制搜索的实现是相同的。 他们不是。 如果我有一个观点,那就是:有原因的存在,这两种方法。 它是由你来决定哪些是最适合你的需求。
原来的答案:
散列算法是O(1),而二进制搜索是O(log n)的。 所以,当n趋于无穷大,哈希性能提高相对于二进制搜索。 您的里程将取决于n变化,你的哈希实施和您的二进制搜索实现。
O对有趣的讨论,(1) 。 转述:
O(1)并不意味着瞬间。 这意味着,随着n的大小增长的性能不会改变。 你可以设计一个哈希算法,这么慢没有人会使用它,它仍然是O(1)。 我相当肯定的.NET / C#不从成本高昂的散列吃亏,但是;)
Answer 5:
如果你的对象集是真正静止不变的,你可以使用一个完美的哈希得到O(1)性能保证。 我见过的gperf几次提到,虽然我从来没有机会使用它自己。
Answer 6:
哈希值是通常速度更快,虽然二进制搜索有更好的最坏情况下的特性。 哈希访问通常是计算得到的哈希值,以确定哪些“桶”的记录将在,所以表现一般取决于记录如何分布均匀,用于搜索桶的方法。 一个坏的哈希函数通过桶(留下了一大堆的记录数桶)与线性搜索将导致一个缓慢的搜索。 (在第三手,如果你正在读一个磁盘而不是内存,哈希桶很可能是连续的,而二叉树几乎保证非本地访问。)
如果你想快速通常使用的哈希值。 如果你真的想保证有界的表现,可以用该二叉树去。
Answer 7:
惊讶没有人提到杜鹃散列,它提供了保证O(1)和,与完美散列,能够使用所有它分配,存储器中的哪里作为完美散列可以与保证O(1)但浪费的较大部分结束其分配。 需要提醒的? 插入时间可能会非常缓慢,特别是因为元件数目的增加,由于所有的优化的过程中插入阶段进行。
我相信这个版本的一些在硬件路由器用于IP查找。
见链接文本
Answer 8:
词典/ Hashtable的是使用更多的内存和需要更多的时间来填充比较阵列。 但是,搜索完成的字典更快,而不是数组中的二进制搜索。
这里有10万元的Int64项搜索和填充数字。 另外一个示例代码,你可以自己运行。
字典内存:462836
阵列内存:88376
填充字典:402
填充阵列:23
检索字典:176
搜索阵列:680
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace BinaryVsDictionary
{
internal class Program
{
private const long Capacity = 10000000;
private static readonly Dictionary<long, long> Dict = new Dictionary<long, long>(Int16.MaxValue);
private static readonly long[] Arr = new long[Capacity];
private static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (long i = 0; i < Capacity; i++)
{
Dict.Add(i, i);
}
stopwatch.Stop();
Console.WriteLine("Populate Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (long i = 0; i < Capacity; i++)
{
Arr[i] = i;
}
stopwatch.Stop();
Console.WriteLine("Populate Array: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (long i = 0; i < Capacity; i++)
{
long value = Dict[i];
// Console.WriteLine(value + " : " + RandomNumbers[i]);
}
stopwatch.Stop();
Console.WriteLine("Search Dictionary: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (long i = 0; i < Capacity; i++)
{
long value = BinarySearch(Arr, 0, Capacity, i);
// Console.WriteLine(value + " : " + RandomNumbers[i]);
}
stopwatch.Stop();
Console.WriteLine("Search Array: " + stopwatch.ElapsedMilliseconds);
Console.ReadLine();
}
private static long BinarySearch(long[] arr, long low, long hi, long value)
{
while (low <= hi)
{
long median = low + ((hi - low) >> 1);
if (arr[median] == value)
{
return median;
}
if (arr[median] < value)
{
low = median + 1;
}
else
{
hi = median - 1;
}
}
return ~low;
}
}
}
Answer 9:
我强烈怀疑,在集大小1M〜的问题,哈希会更快。
只为数字:
二进制搜索将需要〜20比较(2 ^ 20 == 1M)
哈希查找需要的搜索键1次散列计算,并可能屈指可数的比较之后,以解决可能发生的碰撞
编辑:数字:
for (int i = 0; i < 1000 * 1000; i++) {
c.GetHashCode();
}
for (int i = 0; i < 1000 * 1000; i++) {
for (int j = 0; j < 20; j++)
c.CompareTo(d);
}
次:C = “ABCDE”,d = “rwerij” 哈希码:0.0012秒。 比较2.4秒。
免责声明:实际上标杆哈希查找与二进制查找可能比这个并不完全相关的测试更好。 我甚至不知道,如果GetHashCode获取memoized下引擎罩
Answer 10:
我会说这主要取决于哈希的性能和比较的方法。 例如,当使用字符串键时很长,而随机的,一个比较总是会产生一个非常快的结果,而是一个默认的散列函数将处理整个字符串。
但在大多数情况下,哈希映射应该会更快。
Answer 11:
我不知道为什么没有人提到完美的哈希 。
这只是如果你的数据集是固定的,很长一段时间有关,但它的作用是分析数据,构建确保没有冲突完美的哈希函数。
漂亮整洁的,如果你的数据集是恒定的,相对于应用程序运行时间计算功能的时间很短。
Answer 12:
这取决于你如何处理的哈希表副本(如果有的话)。 如果你想允许哈希键重复(无散列函数是完美的),它仍然是(1)主键查找O,但“正确”值后面的搜索可能是昂贵的。 答然后,theorically大部分时间,哈希更快。 YMMV取决于它的数据,你放在那里?
Answer 13:
在这里它描述了如何散列建成因为按键的宇宙是相当大,散列函数是建立“非常射”,这样的冲突很少发生的访问时间为一个哈希表是不是O(1)其实......这是基于一些概率的东西。 但是,它是合理地说,一个哈希访问时间几乎总是小于时间O(log_2(N))
Answer 14:
当然,哈希最快的这么大的数据集。
一种方法来加快它甚至更多,因为数据很少变化,就是用程序生成特殊的代码做搜索是一个巨大的switch语句(如果你的编译器可以处理它)的第一层,然后岔开搜索所产生的水桶。
Answer 15:
答案取决于。 让我们觉得元素“n”的数量是非常大的。 如果你擅长写一个更好的散列函数的碰撞较轻,则散列是最好的。 请注意,正在以搜索进行Hash函数只有一次,并将其引导到相应的水桶。 因此,这不是一个大的开销,如果n为高。
问题Hashtable中:但在哈希表的问题是,如果散列函数是不好的(更多的碰撞发生),那么搜索不是O(1)。 它趋向于为O(n),因为在水桶搜索是线性搜索。 可以比二叉树最差。 问题二叉树:二叉树,如果树是不均衡的,它也倾向于为O(n)。 例如,如果你插入1,2,3,4,5的二叉树,这将是更可能的列表。 所以,如果你能看到一个很好的散列方法,使用哈希表如果没有,你最好使用二进制树。
Answer 16:
这更多的是比尔的答案评论,因为他的回答有这么多的upvotes即使它是错误的。 所以我不得不张贴此。
我看到很多的讨论,关于什么是哈希表中查找的最坏情况的复杂性,以及什么被认为是平摊分析/什么不是。 请检查下面的链接。
哈希表运行的复杂性(插入,搜索和删除)
最坏的情况下复杂度为O(N),而不是O(1),而不是什么Bill说。 因而他的O(1)复杂性不摊销,因为这种分析只可用于最坏的情况下(也是他自己的维基百科的链接是这么说的)
https://en.wikipedia.org/wiki/Hash_table
https://en.wikipedia.org/wiki/Amortized_analysis
文章来源: Which is faster, Hash lookup or Binary search?