C# 有关List<T>的Contains与Equals方法
【以下内容仅为本人在学习中的所感所想,本人水平有限目前尚处学习阶段,如有错误及不妥之处还请各位大佬指正,请谅解,谢谢!】
!!!观前提醒!!!
【本文内容可能较为复杂,虽然我已经以较为清晰的方式展现我的思想,但可能依旧容易引起思维混乱,若感到混乱或不舒服的情况,可直接转跳至文末的总结处;也可以先看完结论再来阅读文章】
引言
List作为一种动态存储结构,可以代替数组,还可以将其当作链表使用。本文将介绍C#中List的相关内容,重点介绍其包含的Contains与Equals方法,并针对集合的比较与去重进行分析,提供可行的解决方案。
起因
题目链接:6049. 含最多 K 个可整除元素的子数组 - 力扣(LeetCode) (leetcode-cn.com)
实际上就是在一个数组的所有非空“子数组”中,统计其中满足要求的子数组的数量。
经过与结果
想法:枚举每一个子数组,判断其是否符合要求;若符合则判断该子数组是否应经被统计过;未统计则加入,已统计则跳过。
结果:(我真的是匪夷所思啊!!!)
【注:将HashSet换为List运行结果相同】 当换用字符串为元素后,结果却没有问题且AC通过
第一部分 关于List
【注:此部分属于介绍内容,非本文重点,可转跳至第二部分】
在分析上面问题之前,我们先对List有一个认知。List是一个C#中最常见的可伸缩数组组件,即动态数据存储结构。我们常常用它来替代数组,并且因为它的长度是动态的,所以我们在写的时候不用手动去分配的大小;甚至有时我们也会拿它当链表使用。
属性
(一)Count
定义:获取 List<T>中包含的元素数。
(二)Capacity
(1)定义:获取或设置该内部数据结构在不调整大小的情况下能够容纳的元素总数。
(2)可能触发的异常:
ArgumentOutOfRangeException:Capacity 被设置为一个小于现有长度的值。
OutOfMemoryException:系统上没有足够的可用内存。
小结
(1)Capacity是可能需要调整大小之前存储的元素数目,以4为基数,每次递增4;Count是实际位于其中List<T>元素的数目。
(2)Capacity始终大于等于Count。如果在添加元素时Count超过Capacity,则会在添加元素前自动重新分配Capacity的值;在清空List<T>或删除元素后Capacity的值不会变化。
(3)可通过TrimExcess()方法删除多余的预留空间,即使Capacity的值等于Count的值。
(3)获取此属性的值的运算时间复杂度为 O(1)。
(三)Item[index]
(1)定义:获取或设置指定索引处的元素。
(2)可能触发的异常:
ArgumentOutOfRangeException:index小于0或大于等于 Count。
常用方法
(一)添加
(1)Add(T) 将对象添加到末尾
(2)AddRange(List<T>) 将集合添加到末尾【注:List<T>非空】
(3)Insert(int index, T) 将元素插入指定索引处
(4)InsertRange(int index, List<T>) 将集合插入指定索引处【注:List<T>非空】
(二)删除
(1)Remove(T) 移除指定对象的第一个匹配项
(2)RemoveAll(Predicate<T> match) 移除与match相匹配的所有元素
(3)RemoveAt(int num) 移除指定s索引处的元素
(4)RemoveRange(int index, int count) 移除指定范围内的元素
(三)查找
(1)Find(Predicate<T> match) 搜索并返回第一个与match相匹配的元素
(2)FindIndex(Predicate<T> match) 返回第一个与match相匹配的元素的索引值
(3)FindIndex(int startIndex, Predicate<T> match) 从startIndex开始搜索,返回第一个与match相匹配的元素的索引值
(4)FindIndex(int startIndex, int count, Predicate<T> match) 从startIndex开始搜索count位,返回第一个与match相匹配的元素的索引值
【注:FindLast、FindLastIndex从后往前搜索,其余部分相同】
(5)IndexOf(T) 搜索指定对象,返回第一个匹配项从零开始的索引
(6)Find(T, int index) 在[index, count)范围内搜索指定对象,返回第一个匹配项从零开始的索引
(7)Find(T, int index, int count) 在[index, index + count]范围内搜索指定对象,返回第一个匹配项从零开始的索引
(四)排序
(1)Sort() 使用默认比较器(升序)对List进行排序
(2)Sort(IComparer<T>) 使用指定比较器对List进行排序
(3)Sort(int index, int count, IComparer<T>) 在指定范围内使用指定比较器(不写,则为默认比较器)对List进行排序
第二部分 关于List之间的包含与比较
(一)包含——Contains
【注:元素之间的比较较为简单,在此不做叙述】
C#中变量可分为值类型和引用类型。值类型储存在栈中,引用类型储存在堆中,引用类型的内存地址储存在栈中。其中:泛型List的Contains在比较值类型时,直接比较值,但在比较引用类型时,比较的是引用地址。
虽然明白了Contains的比较原理,但这时候又出现了一个问题:List<T>是引用类型,string也是引用类型,那为什么在判断string是否相同时可以达到预期效果而判断List<T>时不能达到预期效果呢?针对这个问题,我们直接查看一下这两个类型所声明的变量的内存地址。
(1) 泛型类型为List<T>【此时,内部储存的元素为集合】
可以发现:
a. 两个字符串值相同,则其内存地址相同
b. 两个List<T>值相同,但其内存地址不同
c. 当泛型类型为集合时,即使两个集合中储存的元素值相同且元素内存地址相同,但这两个集合本身的内存地址不同,即list1的地址不同于list2。而根据Contains的查找规律,无法将这两个集合视为同一个集合。
所以我们所认为的集合相同是内部元素相同,而C#所认为的集合相同是内存地址的相同。
(2)泛型类型为string【此时,内部储存的元素为字符串】
内部元素不同:
内部元素相同:
可以发现:
a. 两个字符串值相同,则其内存地址相同
b. 当泛型类型为字符串时,只有两个字符串的内存地址相同,即str1的地址等同于str2,所以可将这两个元素视为同一个元素。
(3)泛型类型为自定义类型【此时,内部储存的元素为自定义对象】
通过上面这个例子可以发现:
a. 两个自定义对象值相同,但其内存地址不同
b. 对象再传递时,传递的是内存地址
c. 当泛型类型为自定义对象类型时,只有两个对象的内存地址相同时,才判定这两个对象是同一个对象。
小结
至此,我们得出List. Contains()方法的执行原理:
a. 在查找是否包含时,查找的是针对定义的泛型T,所存储的对象,与对象中再存储的元素无关。
b. 在查找是否包含值类型时,直接比较值;查找引用类型时,先比较内存地址,若内存地址相同则返回True;否则再比较值。
(二)比较——Equals
(1)泛型类型为List<T>【此时,内部储存的元素为集合】
(2)泛型类型为string【此时,内部储存的元素为字符串】
内部元素不同:
内部元素相同:
直接使集合本身相同:
(3)泛型类型为自定义类型【此时,内部储存的元素为自定义对象】
小结
a. 通过以上两个例子,我们可以总结出List. Equals()方法的执行原理:在比较值类型时,直接比较值;在比较引用类型时,只比较内存地址。
回到开头的题目
为什么第一种方法是错的,因为执行Contains操作的对象是是两个集合,虽然两个集合内部存储元素相同,但两个集合本身的内存地址是不同的,所以总会被判定为是不同的元素,因而不能用于判重;
第二种方法使用字符串,操作对象即为字符串,相同的字符串值,内存地址相同,所以可以用来判重。
总结
a. 不论是Contains方法还是Equals方法,首先要明确操作的对象,即要查找或比较的是哪两个对象。
b. 确定对象后,判断其内存地址是否相同,相同返回True,不同返回False。
c. 一般地,引用类型传递时,传递的是内存地址,且相同值的引用类型内存地址不同;特别地,当字符串值相同时,内存地址也相同。
【感谢您可以抽出时间阅读到这里,第一篇博客可能会有许多不妥之处;受限于水平,许多地方可能存在错误,还请各位大佬留言指正,请见谅,谢谢!】
C# 有关List<T>的Contains与Equals方法的更多相关文章
- List<T>Find方法,FindAll方法,Contains方法,Equals方法
假如传入的T是一个类, List<MessageInfos> MessageInfos = new List<MessageInfos>(); MessageInfos= Me ...
- [java] 更好的书写equals方法-汇率换算器的实现(4)
[java] 更好的书写equals方法-汇率换算器的实现(4) // */ // ]]> [java] 更好的书写equals方法-汇率换算器的实现(4) Table of Content ...
- java中的equals()方法重写
如何java中默认的equals方法跟实际不符的话,需要重写equals方法.例如: public class TestEquals { public static void main(String[ ...
- java提高篇(十三)-----equals()方法总结
equals() 超类Object中有这个equals()方法,该方法主要用于比较两个对象是否相等.该方法的源码如下: public boolean equals(Object obj) { retu ...
- Java中“==”与equals方法的区别
1. 用“==”比较两个变量,如果两个变量是基本类型变量,且都是数值类,则值相等就返回true 如果两个变量是引用型变量,则两个对象的地址一样,即指向同一个对象,则返回true 2.equals:St ...
- equals()方法
equals()方法是根类Object中的一个方法,子类可以根据需要重写该方法(比如:String类). 一.Object类中的equals()方法实现如下: public boolean equal ...
- C#中==与Equals方法的区别
using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace Console ...
- java重写equals方法
@Override public int hashCode() { return task.getId(); } @Override public boolean equals(Object obj) ...
- 重写Object类中的equals方法
Object是所有类的父亲,这个类有很多方法,我们都可以直接调用,但有些方法并不适合,例如下面的student类 public class Student { //姓名.学号.年纪 private S ...
- java中hashCode方法与equals方法的用法总结
首先,想要明白hashCode的作用,必须要先知道Java中的集合. 总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set. 前者集合内的元素是有序的,元素可以重 ...
随机推荐
- python 装饰器函数基础知识
1.装饰器的本质--一个闭包函数 2.装饰器的功能--在不改变原函数及其调用方式情况下对原函数功能进行拓展 3.带参数和返回值的装饰器 def timer(func): @wraps(func) #使 ...
- kafka中consumer group 是什么概念?
同样是逻辑上的概念,是Kafka实现单播和广播两种消息模型的手段.同一个topic的数据,会广播给不同的group:同一个group中的worker,只有一个worker能拿到这个数据.换句话说,对于 ...
- 解释 Java 堆空间及 GC?
当通过 Java 命令启动 Java 进程的时候,会为它分配内存.内存的一部分用于 创建堆空间,当程序中创建对象的时候,就从对空间中分配内存.GC 是 JVM 内 部的一个进程,回收无效对象的内存用于 ...
- 描述 Java 中的重载和重写?
重载和重写都允许你用相同的名称来实现不同的功能,但是重载是编译时活动, 而重写是运行时活动.你可以在同一个类中重载方法,但是只能在子类中重写方 法.重写必须要有继承.
- SpringCloud个人笔记-04-Stream初体验
sb_cloud_stream Spring Cloud Stream 是一个构建消息驱动微服务的框架 应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream ...
- 在IE中设置在序列化没有版本号就提示
- 浏览器中唤起native app || 跳转到应用商城下载
前段时间遇到一个小需求:要求在分享出来的h5页面中,有一个立即打开的按钮,如果本地安装了我们的app,那么点击就直接唤起本地app,如果没有安装,则跳转到下载. 因为从来没有做过这个需求,因此这注定是 ...
- Day10 - JS 实现 Checkbox 中按住 Shift 的多选功能
Day10 - JS 实现 Checkbox 中按住 Shift 的多选功能 作者:liyuechun 简介:JavaScript30 是 Wes Bos 推出的一个 30 天挑战.项目免费提供了 3 ...
- WordPress 网站开发“微信小程序“实战(二)
原文链接:https://devework.com/wordpres...,转载请用明链注明来源,谢谢! 本文是"WordPress 开发微信小程序"系列的第二篇,本文记录的是开发 ...
- 如何实现多个接口Implementing Multiple Interface
4.实现多个接口Implementing Multiple Interface 接口的优势:马克-to-win:类可以实现多个接口.与之相反,类只能继承一个超类(抽象类或其他类). A class c ...