首页 > 其他分享 >数组还是HashSet?

数组还是HashSet?

时间:2022-11-11 09:34:38浏览次数:36  
标签:Contains 还是 HashSet arrays 元素 数组 public

我记得大约在半年前,有个朋友问我一个问题,现在有一个选型:

一个性能敏感场景,有一个集合,需要确定某一个元素在不在这个集合中,我是用数组直接Contains还是使用HashSet<T>.Contains

大家肯定想都不用想,都选使用HashSet<T>,毕竟HashSet<T>的时间复杂度是O(1),但是后面又附加了一个条件:

这个集合的元素很少,就4-5个。

那这时候就有一些动摇了,只有4-5个元素,是不是用数组Contains或者直接遍历会不会更快一些?当时我也觉得可能元素很少,用数组就够了。

而最近在编写代码时,又遇到了同样的场景,我决定来做一下实验,看看元素很少的情况下,是不是使用数组优于HashSet<T>

测试

我构建了一个测试,分别尝试在不同的容量下,查找一个元素,使用数组和HashSet的区别,代码如下所示:

[GcForce(true)]
[MemoryDiagnoser]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class BenchHashSet
{
	private HashSet<string> _hashSet;
	private string[] _strings;

	[Params(1,2,4,64,512,1024)]
	public int Size { get; set; }

	[GlobalSetup]
	public void Setup()
	{
		_strings = Enumerable.Range(0, Size).Select(s => s.ToString()).ToArray();
		_hashSet = new HashSet<string>(_strings);
	}

	[Benchmark(Baseline = true)]
	public bool EnumerableContains() => _strings.Contains("8192");

	[Benchmark]
	public bool HashSetContains() => _hashSet.Contains("8192");
}

大家猜猜结果怎么样,就算Size只为1,那么HashSet也比数组Contains遍历快40%。

那么故事就这么结束了吗?所以无论如何场景我们都直接无脑使用HashSet就行了吗?大家看滑动条就知道,故事没有这么简单。

刚刚我们是引用类型的比较,那值类型怎么样?结论就是一样的结果,就算只有1个元素也比数组的Contains快。

那么问题出在哪里?点进去看一下数组Contains方法的实现就清楚了,这个东西使用的是Enumerable迭代器匹配。

那么我们直接来个原始的,Array.IndexOf匹配和for循环匹配试试,于是有了如下代码:

[GcForce(true)]
[MemoryDiagnoser]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class BenchHashSetValueType
{
	private HashSet<int> _hashSet;
	private int[] _arrays;

	[Params(1,4,16,32,64)]
	public int Size { get; set; }
	

	[GlobalSetup]
	public void Setup()
	{
		_arrays = Enumerable.Range(0, Size).ToArray();
		_hashSet = new HashSet<int>(_arrays);
	}

	[Benchmark(Baseline = true)]
	public bool EnumerableContains() => _arrays.Contains(42);
	
	[Benchmark]
	public bool ArrayContains() => Array.IndexOf(_arrays,42) > -1;

	[Benchmark]
	public bool ForContains()
	{
		for (int i = 0; i < _arrays.Length; i++)
		{
			if (_arrays[i] == 42) return true;
		}

		return false;
	}

	[Benchmark]
	public bool HashSetContains() => _hashSet.Contains(42);
}

接下来结果就和我们预想的差不多了,在数组元素小的时候,使用原始的for循环比较会快,然后HashSet就变为最快的了,在更多元素的场景中Array.IndexOf会比for更快:

至于为什么在元素多的情况Array.IndexOf会比for更快,那是因为Array.IndexOf底层使用了SIMD来优化,在之前的文章中,我们多次提到了SIMD,这里就不赘述了。

既然如此我们再来确认一下,到底多少个元素以内用for会更快,可以看到16个元素以内,for循环会快于HashSet:

总结

所以我们应该选择HashSet<T>还是数组呢?这个就需要分情况简单的总结一下:

  • 在小于16个元素场景,使用for循环匹配会比较快。
  • 16-32个元素的场景,速度最快是HashSet<T>然后是Array.IndexOfforIEnumerable.Contains
  • 大于32个元素的场景,速度最快是HashSet<T>然后是Array.IndexOfIEnumerable.Containsfor

从这个上面来看,大于32个元素就不合适直接用for比较了。不过这些差别都很小,除非是性能非常敏感的场景,可以忽略不计,本文解决了笔者的一些困扰,简单记录一下。

标签:Contains,还是,HashSet,arrays,元素,数组,public
From: https://www.cnblogs.com/InCerry/p/you_should_use_array_contains_when_size_small.html

相关文章

  • 74 寻找数组的中心下标
    题目74寻找数组的中心下标给你一个整数数组nums,请计算数组的中心下标。数组中心下标是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。如果中......
  • C/C++:探究二维数组的数组名
    C/C++:探究二维数组的数组名与数组指针先提一嘴:一维数组的数组名对于一个一维数组而言,其数组名是该数组的首地址,也就是一个数组首元素的指针,如下:#include<stdio.h>int......
  • 560.和为k的子数组
    给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。示例1:输入:nums=[1,1,1],k=2输出:2示例2:输入:nums=[1,2,3......
  • 力扣 81. 搜索旋转排序数组 II
    81.搜索旋转排序数组II已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。在传递给函数之前,nums 在预先未知的某个下标 k(0<=k<nums.leng......
  • 类内初始化数组
    1,问题:就是在类内定义了一个数组,但是我又不想用for循环一个个元素去初始化,于是我去网上寻找答案。2,网上大多数答案:在类内创建数组时选择static修饰,也就是将这个数组变为......
  • 941 有效的山脉数组
    题目941有效的山脉数组给定一个整数数组arr,如果它是有效的山脉数组就返回true,否则返回false。让我们回顾一下,如果arr满足下述条件,那么它是一个山脉数组:arr.leng......
  • 数组为函数参数
    1、数组引用作为函数形参,链接1)输入必须为10个元素!2)可以将数组定义为类型voidprint(int(&arr)[10]){for(autoi:arr){cout<<i<<endl;}}......
  • 不修改数组找出重复的数字
    14.不修改数组找出重复的数字题给定一个长度为n+1的数组nums,数组中所有的数均在1∼n的范围内,其中n≥1。请找出数组中任意一个重复的数,但不能修改输入的数组。数据......
  • 查找字符串数组中的最长公共前缀
     import java.util.*;public class Solution {    /**     *      * @param strs string字符串一维数组      * @return string......
  • 算法 Notes|LeetCode 26. 删除排序数组中的重复项 - easy
    历史LeetCode刷题文章:​​算法Notes|LeetCode349.两个数组的交集-easy​​​​算法Notes|LeetCode14.最长公共前缀-easy​​​​算法Notes|LeetCode1.两数之和......