数组还是HashSet?
我记得大约在半年前,有个朋友问我一个问题,现在有一个选型:
一个性能敏感场景,有一个集合,需要确定某一个元素在不在这个集合中,我是用数组直接
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.IndexOf
、for
、IEnumerable.Contains
。 - 大于32个元素的场景,速度最快是
HashSet<T>
然后是Array.IndexOf
、IEnumerable.Contains
、for
。
从这个上面来看,大于32个元素就不合适直接用for
比较了。不过这些差别都很小,除非是性能非常敏感的场景,可以忽略不计,本文解决了笔者的一些困扰,简单记录一下。
数组还是HashSet?的更多相关文章
- 2. 三数之和(数组、hashset)
思路及算法: 该题与第一题的"两数之和"相似,三数之和为0,不就是两数之和为第三个数的相反数吗?因为不能重复,所以,首先进行了一遍排序:其次,在枚举的时候判断了本次的第三个数的值是 ...
- C# 数组、HashSet等内存耗尽的解决办法
在C#中,如果数据量太大,就会出现 'System.OutOfMemoryException' 异常. 解决办法来自于Stack Overflow和MSDN https://docs.micro ...
- 2.请介绍一下List和ArrayList的区别,ArrayList和HashSet区别
第一问: List是接口,ArrayList实现了List接口. 第二问: ArrayList实现了List接口,HashSet实现了Set接口,List和Set都是继承Collection接口. A ...
- HashSet非常的消耗空间,TreeSet因为有排序功能,因此资源消耗非常的高,我们应该尽量少使用
注:HashMap底层也是用数组,HashSet底层实际上也是HashMap,HashSet类中有HashMap属性(我们如何在API中查属性).HashSet实际上为(key.null)类型的Has ...
- 5.秋招复习简单整理之请介绍一下List和ArrayList的区别,arrayList和HashSet区别?
第一问:List是接口,ArrayList是List的实现类. 第二问:ArrayList是List的实现类,HashSet是Set的实现类,List和Set都实现了Collection接口. Arr ...
- JAVA的面向对象编程--------课堂笔记
面向对象主要针对面向过程. 面向过程的基本单元是函数. 什么是对象:EVERYTHING IS OBJECT(万物皆对象) 所有的事物都有两个方面: 有什么(属性):用来描述对象. 能够做什么 ...
- Java琐碎知识点
jps命令是JDK1.5提供的一条显示当前用户的所有java进程pid的指令,类似Linux上的ps命令简化版,Windows和linux/unix平台都可以用比较常用的参数:-q:只显示pid,不显 ...
- linkin大话数据结构--Map
Map 映射关系,也有人称为字典,Map集合里存在两组值,一组是key,一组是value.Map里的key不允许重复.通过key总能找到唯一的value与之对应.Map里的key集存储方式和对应的Se ...
- java库中的具体的集合
1.ArrayList 一种可以动态增长和缩减的索引序列:速度较慢适合用于不修改太多的元素 采用的数组 2.LinkEdList 一种可以在任何位置进行高效的插入和删除操作的有序序列,适合于 ...
随机推荐
- 【java】学习路线2-构造、Scanner包导入、字符串操作、数组、引用类型
请先查看前置知识: [JAVA]基础1-字符串.堆.栈.静态与引用类型 https://www.cnblogs.com/remyuu/p/15990274.html import java.util. ...
- C# winfrom ListView控件实现自由设置每一行字体及背景色等
背景:公司经常会需要将日志信息,输出到一个对话框中显示出来.之前一直采用的listbox控件,操作简单,使用方便,但是遗憾的是,不能自由控制每一行的状态. 于是想了如下几个方案: (1)重绘listb ...
- 7个自定义定时任务并发送消息至邮箱或企业微信案例(crontab和at)
前言 更好熟悉掌握at.crontab定时自定义任务用法. 实验at.crontab定时自定义任务运用场景案例. 作业.笔记需要. 定时计划任务相关命令及配置文件简要说明 at 工具 由包 at 提供 ...
- Rsync数据备份工具
Rsync数据备份工具 1.Rsync基本概述 rsync是一款开源的备份工具,可以在不同主机之间进行同步(windows和Linux之间 Mac和 Linux Linux和Linux),可实现全量备 ...
- Linux之主从数据库(1+X)
主从数据库搭建 改主机名 配置网络 配置yum源(下载mysql) 写域名解析文件 主从同步:(备份,负载(读)) 第一步:数据库的初始化,修改配置文件,定义server-id(所有节点),开启二进制 ...
- HBase概念入门
HBase简介 HBase基于Google的BigTable论文而来,是一个分布式海量列式非关系型数据库系统,可以提供大规模数据集的实时随机读写. 下面通过一个小场景认识HBase存储.同样的一个数据 ...
- 第十二章 Kubernetes的服务暴露插件--traefik
1.前言 之前部署的coredns实现了k8s的服务在集群内可以被自动发现,那么如何使得服务在k8s集群外被使用和访问呢? 使用nodeport星的Service:此方法只能使用iptables模型, ...
- K8S_三种Port区别总结
nodePort: 外部流量访问K8S集群中Service入口的一种方式 比如外部用户要访问k8s集群中的一个Web应用,那么我们可以配置对应service的type=NodePort,nodePor ...
- 解决element-ui中组件【el-upload】一次性上传多张图片的问题
element-ui 中的组件 el-upload默认的行为是一张图片请求一次,在项目需求中,通常是多张图片要求只向后台请求一次,下面的做法就是为了实现这样的需求 前端 <el-upload r ...
- 新增一个Redis 从节点为什么与主节点的key数量不一样呢?
在日常的 Redis 运维过程中,经常会发生重载 RDB 文件操作,主要情形有: 主从架构如果主库宕机做高可用切换,原从库会挂载新主库重新获取数据 主库 QPS 超过10万,需要做读写分离,重新添加从 ...