由C# yield return引发的思考
前言
当我们编写 C# 代码时,经常需要处理大量的数据集合。在传统的方式中,我们往往需要先将整个数据集合加载到内存中,然后再进行操作。但是如果数据集合非常大,这种方式就会导致内存占用过高,甚至可能导致程序崩溃。
C# 中的yield return
机制可以帮助我们解决这个问题。通过使用yield return
,我们可以将数据集合按需生成,而不是一次性生成整个数据集合。这样可以大大减少内存占用,并且提高程序的性能。
在本文中,我们将深入讨论 C# 中yield return
的机制和用法,帮助您更好地理解这个强大的功能,并在实际开发中灵活使用它。
使用方式
上面我们提到了yield return
将数据集合按需生成,而不是一次性生成整个数据集合。接下来通过一个简单的示例,我们看一下它的工作方式是什么样的,以便加深对它的理解
foreach (var num in GetInts())
{Console.WriteLine("外部遍历了:{0}", num);
}IEnumerable<int> GetInts()
{for (int i = 0; i < 5; i++){Console.WriteLine("内部遍历了:{0}", i);yield return i;}
}
首先,在GetInts
方法中,我们使用yield return
关键字来定义一个迭代器。这个迭代器可以按需生成整数序列。在每次循环时,使用yield return
返回当前的整数。通过1foreach
循环来遍历 GetInts
方法返回的整数序列。在迭代时GetInts
方法会被执行,但是不会将整个序列加载到内存中。而是在需要时,按需生成序列中的每个元素。在每次迭代时,会输出当前迭代的整数对应的信息。所以输出的结果为
内部遍历了:0
外部遍历了:0
内部遍历了:1
外部遍历了:1
内部遍历了:2
外部遍历了:2
内部遍历了:3
外部遍历了:3
内部遍历了:4
外部遍历了:4
可以看到,整数序列是按需生成的,并且在每次生成时都会输出相应的信息。这种方式可以大大减少内存占用,并且提高程序的性能。当然从c# 8
开始异步迭代的方式同样支持
await foreach (var num in GetIntsAsync())
{Console.WriteLine("外部遍历了:{0}", num);
}async IAsyncEnumerable<int> GetIntsAsync()
{for (int i = 0; i < 5; i++){await Task.Yield();Console.WriteLine("内部遍历了:{0}", i);yield return i;}
}
和上面不同的是,如果需要用异步的方式,我们需要返回IAsyncEnumerable
类型,这种方式的执行结果和上面同步的方式执行的结果是一致的,我们就不做展示了。上面我们的示例都是基于循环持续迭代的,其实使用yield return
的方式还可以按需的方式去输出,这种方式适合灵活迭代的方式。如下示例所示
foreach (var num in GetInts())
{Console.WriteLine("外部遍历了:{0}", num);
}IEnumerable<int> GetInts()
{Console.WriteLine("内部遍历了:0");yield return 0;Console.WriteLine("内部遍历了:1");yield return 1;Console.WriteLine("内部遍历了:2");yield return 2;
}
foreach
循环每次会调用GetInts()
方法,GetInts()
方法的内部便使用yield return
关键字返回一个结果。每次遍历都会去执行下一个yield return
。所以上面代码输出的结果是
内部遍历了:0
外部遍历了:0
内部遍历了:1
外部遍历了:1
内部遍历了:2
外部遍历了:2
探究本质
上面我们展示了yield return
如何使用的示例,它是一种延迟加载的机制,它可以让我们逐个地处理数据,而不是一次性地将所有数据读取到内存中。接下来我们就来探究一下神奇操作的背后到底是如何实现的,方便让大家更清晰的了解迭代体系相关。
foreach本质
首先我们来看一下foreach
为什么可以遍历,也就是如果可以被foreach
遍历的对象,被遍历的操作需要满足哪些条件,这个时候我们可以反编译工具来看一下编译后的代码是什么样子的,相信大家最熟悉的就是List<T>
集合的遍历方式了,那我们就用List<T>
的示例来演示一下
List<int> ints = new List<int>();
foreach(int item in ints)
{Console.WriteLine(item);
}
上面的这段代码很简单,我们也没有给它任何初始化的数据,这样可以排除干扰,让我们能更清晰的看到反编译的结果,排除其他干扰。它反编译后的代码是这样的
List<int> list = new List<int>();
List<int>.Enumerator enumerator = list.GetEnumerator();
try
{while (enumerator.MoveNext()){int current = enumerator.Current;Console.WriteLine(current);}
}
finally
{((IDisposable)enumerator).Dispose();
}
可以反编译代码的工具有很多,我用的比较多的一般是
ILSpy
、dnSpy
、dotPeek
和在线c#
反编译网站sharplab.io,其中dnSpy
还可以调试反编译的代码。
通过上面的反编译之后的代码我们可以看到foreach
会变编译成一个固定的结构
Enumerator enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{var current = enumerator.Current;
}
通过这段固定的结构我们总结一下foreach
的工作原理
- 可以被
foreach
的对象需要要包含GetEnumerator()
方法 - 迭代器对象包含
MoveNext()
方法和Current
属性 MoveNext()
方法返回bool
类型,判断是否可以继续迭代。Current
属性返回当前的迭代结果。
我们可以看一下List<T>
类可迭代的源码结构是如何实现的
public class List<T> : IList<T>, IList, IReadOnlyList<T>
{public Enumerator GetEnumerator() => new Enumerator(this);IEnumerator<T> IEnumerable<T>.GetEnumerator() => Count == 0 ? SZGenericArrayEnumerator<T>.Empty : GetEnumerator();IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<T>)this).GetEnumerator();public struct Enumerator : IEnumerator<T>, IEnumerator{public T Current => _current!;public bool MoveNext(){}}
}
这里涉及到了两个核心的接口IEnumerable<
和IEnumerator
,他们两个定义了可以实现迭代的能力抽象,实现方式如下
public interface IEnumerable
{IEnumerator GetEnumerator();
}public interface IEnumerator
{bool MoveNext();object Current{ get; }void Reset();
}
如果类实现IEnumerable
接口并实现了GetEnumerator()
方法便可以被foreach
,迭代的对象是IEnumerator
类型,包含可MoveNext()
方法和Current
属性。上面的接口是原始对象的方式,这种操作都是针对object
类型集合对象。我们实际开发过程中大多数都是使用的泛型集合,当然也有对应的实现方式,如下所示
public interface IEnumerable<out T> : IEnumerable
{new IEnumerator<T> GetEnumerator();
}public interface IEnumerator<out T> : IDisposable, IEnumerator
{new T Current{ get; }
}
可以被
foreach
迭代并不意味着一定要去实现IEnumerable
接口,这只是给我们提供了一个可以被迭代的抽象的能力。只要类中包含GetEnumerator()
方法并返回一个迭代器,迭代器里包含返回bool
类型的MoveNext()
方法和获取当前迭代对象的Current
属性即可。
yield return本质
上面我们看到了可以被foreach
迭代的本质是什么,那么yield return
的返回值可以被IEnumerable<T>
接收说明其中必有蹊跷,我们反编译一下我们上面的示例看一下反编译之后代码,为了方便大家对比反编译结果,这里我把上面的示例再次粘贴一下
foreach (var num in GetInts())
{Console.WriteLine("外部遍历了:{0}", num);
}IEnumerable<int> GetInts()
{for (int i = 0; i < 5; i++){Console.WriteLine("内部遍历了:{0}", i);yield return i;}
}
它的反编译结果,这里咱们就不全部展示了,只展示一下核心的逻辑
//foeach编译后的结果
IEnumerator<int> enumerator = GetInts().GetEnumerator();
try
{while (enumerator.MoveNext()){int current = enumerator.Current;Console.WriteLine("外部遍历了:{0}", current);}
}
finally
{if (enumerator != null){enumerator.Dispose();}
}//GetInts方法编译后的结果
private IEnumerable<int> GetInts()
{<GetInts>d__1 <GetInts>d__ = new <GetInts>d__1(-2);<GetInts>d__.<>4__this = this;return <GetInts>d__;
}
这里我们可以看到GetInts()
方法里原来的代码不见了,而是多了一个<GetInts>d__1
l类型,也就是说yield return
本质是语法糖
。我们看一下<GetInts>d__1
类的实现
//生成的类即实现了IEnumerable接口也实现了IEnumerator接口
//说明它既包含了GetEnumerator()方法,也包含MoveNext()方法和Current属性
private sealed class <>GetIntsd__1 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{private int <>1__state;//当前迭代结果private int <>2__current;private int <>l__initialThreadId;public C <>4__this;private int <i>5__1;//当前迭代到的结果int IEnumerator<int>.Current{get{ return <>2__current; }}//当前迭代到的结果object IEnumerator.Current{get{ return <>2__current; }}//构造函数包含状态字段,变向说明靠状态机去实现核心流程流转public <GetInts>d__1(int <>1__state){this.<>1__state = <>1__state;<>l__initialThreadId = Environment.CurrentManagedThreadId;}//核心方法MoveNextprivate bool MoveNext(){int num = <>1__state;if (num != 0){if (num != 1){return false;}//控制状态<>1__state = -1;//自增 也就是代码里循环的i++<i>5__1++;}else{<>1__state = -1;<i>5__1 = 0;}//循环终止条件 上面循环里的i<5if (<i>5__1 < 5){Console.WriteLine("内部遍历了:{0}", <i>5__1);//把当前迭代结果赋值给Current属性<>2__current = <i>5__1;<>1__state = 1;//说明可以继续迭代return true;}//迭代结束return false;}//IEnumerator的MoveNext方法bool IEnumerator.MoveNext(){return this.MoveNext();}//IEnumerable的IEnumerable方法IEnumerator<int> IEnumerable<int>.IEnumerable(){//实例化<GetInts>d__1实例<GetInts>d__1 <GetInts>d__;if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId){<>1__state = 0;<GetInts>d__ = this;}else{//给状态机初始化<GetInts>d__ = new <GetInts>d__1(0);<GetInts>d__.<>4__this = <>4__this;}//因为<GetInts>d__1实现了IEnumerator接口所以可以直接返回return <GetInts>d__;}IEnumerator IEnumerable.GetEnumerator(){//因为<GetInts>d__1实现了IEnumerator接口所以可以直接转换return ((IEnumerable<int>)this).GetEnumerator();}void IEnumerator.Reset(){}void IDisposable.Dispose(){}
}
通过它生成的类我们可以看到,该类即实现了IEnumerable
接口也实现了IEnumerator
接口说明它既包含了GetEnumerator()
方法,也包含MoveNext()
方法和Current
属性。用这一个类就可以满足可被foeach
别带的核心结构。我们手动写的for
代码被包含到了MoveNext()
方法里,它包含了定义的状态机制代码,并且根据当前的状态机代码将迭代移动到下一个元素。我们大概讲解一下我们的for
代码被翻译到MoveNext()
方法里的执行流程
- 首次迭代时
<>1__state
被初始化成0,代表首个被迭代的元素,这个时候Current
初始值为0,循环控制变量<i>5__1
初始值也为0。 - 判断是否满足终止条件,不满足则执行循环里的逻辑。并更改装填机
<>1__state
为1,代表首次迭代执行完成。 - 循环控制变量
<i>5__1
继续自增并更改并更改装填机<>1__state
为-1,代表可持续迭代。并循环执行循环体的自定义逻辑。 - 不满足迭代条件则返回
false
,也就是代表了MoveNext()
以不满足迭代条件while (enumerator.MoveNext())
逻辑终止。
上面我们还展示了另一种yield return
的方式,就是同一个方法里包含多个yield return
的形式
IEnumerable<int> GetInts()
{Console.WriteLine("内部遍历了:0");yield return 0;Console.WriteLine("内部遍历了:1");yield return 1;Console.WriteLine("内部遍历了:2");yield return 2;
}
上面这段代码反编译的结果如下所示,这里咱们只展示核心的方法MoveNext()
的实现
private bool MoveNext()
{switch (<>1__state){default:return false;case 0:<>1__state = -1;Console.WriteLine("内部遍历了:0");<>2__current = 0;<>1__state = 1;return true;case 1:<>1__state = -1;Console.WriteLine("内部遍历了:1");<>2__current = 1;<>1__state = 2;return true;case 2:<>1__state = -1;Console.WriteLine("内部遍历了:2");<>2__current = 2;<>1__state = 3;return true;case 3:<>1__state = -1;return false;}
}
通过编译后的代码我们可以看到,多个yield return
的形式会被编译成switch...case
的形式,有几个yield return
则会编译成n+1
个case
,多出来的一个case
则代表的MoveNext()
终止条件,也就是返回false
的条件。其它的case
则返回true
表示可以继续迭代。
IAsyncEnumerable接口
上面我们展示了同步yield return
方式,c# 8
开始新增了IAsyncEnumerable<T>
接口,用于完成异步迭代,也就是迭代器逻辑里包含异步逻辑的场景。IAsyncEnumerable<T>
接口的实现代码如下所示
public interface IAsyncEnumerable<out T>
{IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
}public interface IAsyncEnumerator<out T> : IAsyncDisposable
{ValueTask<bool> MoveNextAsync();T Current { get; }
}
它最大的不同则是同步的IEnumerator
包含的是MoveNext()
方法返回的是bool
,IAsyncEnumerator
接口包含的是MoveNextAsync()
异步方法,返回的是ValueTask<bool>
类型。所以上面的示例代码
await foreach (var num in GetIntsAsync())
{Console.WriteLine("外部遍历了:{0}", num);
}
所以这里的await
虽然是加在foreach
上面,但是实际作用的则是每一次迭代执行的MoveNextAsync()
方法。可以大致理解为下面的工作方式
IAsyncEnumerator<int> enumerator = list.GetAsyncEnumerator();
while (enumerator.MoveNextAsync().GetAwaiter().GetResult())
{var current = enumerator.Current;
}
当然,实际编译成的代码并不是这个样子的,我们在之前的文章<研究c#异步操作async await状态机的总结>一文中讲解过async await
会被编译成IAsyncStateMachine
异步状态机,所以IAsyncEnumerator<T>
结合yield return
的实现比同步的方式更加复杂而且包含更多的代码,不过实现原理可以结合同步的方式类比一下,但是要同时了解异步状态机的实现,这里咱们就不过多展示异步yield return
的编译后实现了,有兴趣的同学可以自行了解一下。
foreach增强
c# 9
增加了对foreach的增强的功能,即通过扩展方法的形式,对原本具备包含foreach
能力的对象增加GetEnumerator()
方法,使得普通类在不具备foreach
的能力的情况下也可以使用来迭代。它的使用方式如下
Foo foo = new Foo();
foreach (int item in foo)
{Console.WriteLine(item);
}public class Foo
{public List<int> Ints { get; set; } = new List<int>();
}public static class Bar
{//给Foo定义扩展方法public static IEnumerator<int> GetEnumerator(this Foo foo){foreach (int item in foo.Ints){yield return item;}}
}
这个功能确实比较强大,满足开放封闭原则,我们可以在不修改原始代码的情况,增强代码的功能,可以说是非常的实用。我们来看一下它的编译后的结果是啥
Foo foo = new Foo();
IEnumerator<int> enumerator = Bar.GetEnumerator(foo);
try
{while (enumerator.MoveNext()){int current = enumerator.Current;Console.WriteLine(current);}
}
finally
{if (enumerator != null){enumerator.Dispose();}
}
这里我们看到扩展方法GetEnumerator()
本质也是语法糖,会把扩展能力编译成扩展类.GetEnumerator(被扩展实例)
的方式。也就是我们写代码时候的原始方式,只是编译器帮我们生成了它的调用方式。接下来我们看一下GetEnumerator()
扩展方法编译成了什么
public static IEnumerator<int> GetEnumerator(Foo foo)
{<GetEnumerator>d__0 <GetEnumerator>d__ = new <GetEnumerator>d__0(0);<GetEnumerator>d__.foo = foo;return <GetEnumerator>d__;
}
看到这个代码是不是觉得很眼熟了,不错和上面yield return本质
这一节里讲到的语法糖生成方式是一样的了,同样的编译时候也是生成了一个对应类,这里的类是<GetEnumerator>d__0
,我们看一下该类的结构
private sealed class <GetEnumerator>d__0 : IEnumerator<int>, IEnumerator, IDisposable
{private int <>1__state;private int <>2__current;public Foo foo;private List<int>.Enumerator <>s__1;private int <item>5__2;int IEnumerator<int>.Current{get{ return <>2__current; }}object IEnumerator.Current{get{ return <>2__current; }}public <GetEnumerator>d__0(int <>1__state){this.<>1__state = <>1__state;}private bool MoveNext(){try{int num = <>1__state;if (num != 0){if (num != 1){return false;}<>1__state = -3;}else{<>1__state = -1;//因为示例中的Ints我们使用的是List<T><>s__1 = foo.Ints.GetEnumerator();<>1__state = -3;}//因为上面的扩展方法里使用的是foreach遍历方式//这里也被编译成了实际生产方式if (<>s__1.MoveNext()){<item>5__2 = <>s__1.Current;<>2__current = <item>5__2;<>1__state = 1;return true;}<>m__Finally1();<>s__1 = default(List<int>.Enumerator);return false;}catch{((IDisposable)this).Dispose();throw;}}bool IEnumerator.MoveNext(){return this.MoveNext();}void IDisposable.Dispose(){}void IEnumerator.Reset(){}private void <>m__Finally1(){}
}
看到编译器生成的代码,我们可以看到yield return
生成的代码结构都是一样的,只是MoveNext()
里的逻辑取决于我们写代码时候的具体逻辑,不同的逻辑生成不同的代码。这里咱们就不在讲解它生成的代码了,因为和上面咱们讲解的代码逻辑是差不多的。
总结
通过本文我们介绍了c#
中的yield return
语法,并探讨了由它带来的一些思考。我们通过一些简单的例子,展示了yield return
的使用方式,知道了迭代器来是如何按需处理大量数据。同时,我们通过分析foreach
迭代和yield return
语法的本质,讲解了它们的实现原理和底层机制。好在涉及到的知识整体比较简单,仔细阅读相关实现代码的话相信会了解背后的实现原理,这里就不过多赘述了。
当你遇到挑战和困难时,请不要轻易放弃。无论你面对的是什么,只要你肯努力去尝试,去探索,去追求,你一定能够克服困难,走向成功。记住,成功不是一蹴而就的,它需要我们不断努力和坚持。相信自己,相信自己的能力,相信自己的潜力,你一定能够成为更好的自己。
相关文章:

由C# yield return引发的思考
前言 当我们编写 C# 代码时,经常需要处理大量的数据集合。在传统的方式中,我们往往需要先将整个数据集合加载到内存中,然后再进行操作。但是如果数据集合非常大,这种方式就会导致内存占用过高,甚至可能导致程序崩溃。 …...

【问题解决】EasyExcel导出数据,并将数据中的实体类url转为图片
EasyExcel导出数据,并将数据中的实体类url转为图片 在导出excel数据时,用户要求把存储二维码url转为图片保存,然后研究了一下具体实现。 代码展示: public void exportData(String pointName, String districtName, String str…...

winform植物大战僵尸
winform植物大战僵尸 植物大战僵尸源码 半成品 需要的拿去学习 登陆注册选择关卡 向日葵 豌豆射手 双枪豌豆射手 项目获取: 项目获取:typora: typora/img (gitee.com) 备用项目获取链接1:yifeiyixiang/kamo: 源码下载 (github.com) 备用…...

Pointnet++改进即插即用系列:全网首发UIB轻量化模块
简介:1.该教程提供大量的首发改进的方式,降低上手难度,多种结构改进,助力寻找创新点!2.本篇文章对Pointnet++特征提取模块进行改进,加入UIB,提升性能。3.专栏持续更新,紧随最新的研究内容。 目录 1.理论介绍 2.修改步骤 2.1 步骤一 2.2 步骤二 2.3 步骤三...

【视频格式转换】【ffmepg】对mp4文件进行重新编码输出新的mp4文件
【视频格式转换】【ffmepg】对mp4文件进行重新编码输出新的mp4文件 背景 之前开发调试了个能正常调用ffmpeg解码mp4文件得到yuv数据的testbed(把ffmpeg开源库移植并交叉编译到一个嵌入式平台),用了好几年了,今天用来挂测一批新的采集视频mp4文件&#x…...

mysql基础概念
文章目录 登录mysqlmysql和mysqld数据库操作主流数据库MYSQL架构SQL分类 登录mysql 登录mysql连接服务器,mysql连接时可以指明主机用-h选项,然后就可以指定主机Ip地址,-P可以指定端口号 -u指定登录用户 -P指定登录密码 查看系统中有无mysql&…...

成功案例(IF=7.3)| 转录组+蛋白质组+代谢组联合分析分析揭示胰腺癌中TAM2相关的糖酵解和丙酮酸代谢重构
研究背景 肿瘤的进展和发展需要癌细胞的代谢重编程,癌细胞能量代谢模式的改变可以满足快速增殖和适应肿瘤微环境的需要。肿瘤微环境(TME)中的代谢状态受到多种因素的影响,包括血管生成、与其他细胞的相互作用和系统代谢。代谢异质…...

【C++ | 函数】默认参数、哑元参数、函数重载、内联函数
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 ⏰发布时间⏰:2024-05-04 1…...

Spring事件
📝个人主页:五敷有你 🔥系列专栏:Spring⛺️稳中求进,晒太阳 Spring事件 简洁 Spring Event(Application Event)就是一个观察者模式,一个bean处理完任务后希望通知其他Bean的…...

mysql安装及基础设置
关系型数据库 MySQL是一种关系型数据库管理系统,采用了关系模型来组织数据的数据库,关系数据库将数据保存在不同的表中,用户通过查询 sql 来检索数据库中的数据。 yum 方式安装 mysql # yum -y install mysql-server # systemctl start my…...

【prometheus】Pushgateway安装和使用
目录 一、Pushgateway概述 1.1 Pushgateway简介 1.2 Pushgateway优点 1.3 pushgateway缺点 二、测试环境 三、安装测试 3.1 pushgateway安装 3.2 prometheus添加pushgateway 3.3 推送指定的数据格式到pushgateway 1.添加单条数据 2.添加复杂数据 3.SDk-prometheus-…...

【无标题】vue webrtc 播放rtsp视频流
最近有个小活其中有涉及播放大华及海康摄像头视频流的需求,经调查发现可以使用webrtc来实现相关功能,记录一下,步骤如下: 1、下载webrtc :Releases mpromonet/webrtc-streamer GitHub winows下下载&…...

redis进阶--IDEA环境
目录 一、解决redis服务器端口问题 二、java环境下使用redis 三、javaSpringt环境下使用redis 四、redis持久化 1、持久化概念 2、redis持久化策略 3、RDB策略 4、AOF策略 5、混合持久化策略 五、redis事务 1、数据库事务 2、redis事务特点 3、redis事务的作用 4…...

Llama3-Tutorial之LMDeploy高效部署Llama3实践
Llama3-Tutorial之LMDeploy高效部署Llama3实践 Llama 3 近期重磅发布,发布了 8B 和 70B 参数量的模型,lmdeploy团队对 Llama 3 部署进行了光速支持!!! 书生浦语和机智流社区同学光速投稿了 LMDeploy 高效量化部署 Llam…...

SK Hynix 探索超低温技术,开启400层以上3D NAND制造新时代
随着存储技术的飞速发展,SK Hynix作为韩国存储巨头,正以前沿的制造技术引领行业变革。据韩国媒体TheElec独家报道,SK Hynix正积极研究在超低温条件下生产3D NAND闪存的可能性,此举有望助力其下一代产品突破400层的技术瓶颈&#x…...

【OceanBase诊断调优】—— 如何排查 server 断连接问题
本文介绍如何排查 server 断连接问题。 断开连接的常见原因 协议层异常 发送报文时遇到发生一些非预期的错误,server 将会发生主动断开连接。 事务异常 包括 rollback 失败或 commit 失败。 Query 异常 已输出行数据,但 server 内部发生错误。 Proce…...

基于Vant UI的微信小程序开发(随时更新的写手)
基于Vant UI的微信小程序开发✨ (一)悬浮浮动1、效果图:只要无脑引用样式就可以了2、页面代码3、js代码4、样式代码 (二)底部跳转1、效果图:点击我要发布跳转到发布的页面2、js代码3、页面代码4、app.json代…...

力扣数据库题库学习(5.7日)--1757. 可回收且低脂的产品
1757. 可回收且低脂的产品 问题链接💦 思路分析 编写解决方案找出既是低脂又是可回收的产品编号。 返回结果 无顺序要求 。看示例: 输入: Products 表: ----------------------------------- | product_id | low_fats | recy…...

支付宝——图技术在金融反欺诈中的应用
目录 图在金融反欺诈中的应用背景 图驱动的感知研判决策处置 图在金融反欺诈中的演进 总结和展望...

【Docker学习】docker run的端口映射-p和-P选项
docker run的端口映射选项分为-p(小写,全称--publish),-P(大写,全称--publish-all),之前认为只有改变容器发布给宿主机的默认端口号才会进行-p的设置,而不改变默认端口号…...

乡村振兴与城乡融合发展:加强城乡间经济、文化、社会等方面的交流与合作,推动城乡一体化发展,实现美丽乡村共荣
目录 一、引言 二、乡村振兴与城乡融合发展的意义 三、城乡交流合作的现状与挑战 (一)现状 (二)挑战 四、加强城乡交流合作的策略与路径 (一)完善城乡交流合作机制 (二)推动…...

什么是职称评审?如何申报?怎么获取职称电子证书?
目录 一、什么是职称?什么是职称评审? 二、申报人申报职称评审要经过哪些流程?...

PC小程序解密及反编译
一、小程序包解密 小程序原始加密包位置C:\Users\administrator\Documents\WeChat Files\Applet\wx234324324324 二、wxappUnpacker反编译 npm install./bingo D:\temp\小程序包解密\wxpack\wx234324324324.wxapkg 三、查看反编译后的文件...

【吃透Java手写】4-Tomcat-简易版
【吃透Java手写】Tomcat-简易版-源码解析 1 准备工作1.1 引入依赖1.2 创建一个Tomcat的启动类 2 线程池技术回顾2.1 线程池的使用流程2.2 线程池的参数2.2.1 任务队列(workQueue)2.2.2 线程工厂(threadFactory)2.2.3 拒绝策略&…...

开发中的一些专业术语,POJO、PO...
在 Java 开发中,以下是常见的设计模式和概念: PO(Persistent Object):持久化对象,也称为实体类或数据对象。它是与数据库表结构对应的类,通常用于表示持久化数据的实体。PO 类的属性与数据库表的…...

79.网络游戏逆向分析与漏洞攻防-移动系统分析-利用数据包实现人物走路
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 如果看不懂、不知道现在做的什么,那就跟着做完看效果,代码看不懂是正常的,只要会抄就行,抄着抄着就能懂了 内容…...

JS基础:输出信息的5种方式详解
你好,我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃-大专生,一枚程序媛,感谢关注。回复 “前端基础题”,可免费获得前端基础 100 题汇总,回复 “前端基础路线”,可获取完整web基础…...

教你解决PUBG绝地求生延迟高 网络延迟高的问题
在《绝地求生》(PUBG)这款备受全球玩家追捧的战术竞技游戏中,其逼真的战场环境和心跳加速的生存竞赛无不让人为之痴迷。不过,有些玩家在经历了一场惊心动魄的对局后,却面临了一个不大不小的麻烦:比赛圆满落…...

【QT教程】QT6与C++17 QT与C++新特性
QT6与C17 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C扩展开发视频课程 免费QT视频课程 您可以看免费1000个QT技术视频 免费QT视频课程 QT统计图和QT数据可视化视频免费看 免费QT视频课程 QT性能优化视频免费看 免费QT视频…...

多线程三种实现
多线程 线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。 (理解:应用软件中互相独立,可以同时运行的功能) 进程 进程是程序的基本执行实体。(理解&#…...