2021年非常全的.NET Core面试题(二)

   日期:2024-12-27    作者:deerma 移动:http://ljhr2012.riyuangf.com/mobile/quote/61798.html

一.垃圾回收机制

2021年非常全的.NET Core面试题(二)

1. 简述一下一个引用对象的生命周期?

(创建>使用>释放)

new创建对象并分配内存

对象初始化

对象操作、使用

资源清理(非托管资源)

GC垃圾回收

 

2. 创建下面对象实例,需要申请多少内存空间?

40字节内存空间。

 

3. 什么是垃圾?

一个变量如果在其生存期内的某一时刻已经不再被引用,那么,这个对象就有可能成为垃圾。

 

4. GC是什么,简述一下GC的工作方式?

在公共语言运行时 (CLR) 中,垃圾回收器 (GC) 用作自动内存管理器。 垃圾回收器管理应用程序的内存分配和释放。

她的基本工作原理就是遍历托管堆中的对象,标记哪些被使用对象(哪些没人使用的就是所谓的垃圾),然后把可达对象转移到一个连续的地址空间(也叫压缩),其余的所有没用的对象内存被回收掉。

 

5. GC进行垃圾回收时的主要流程是?

(1)标记 ,找到并创建所有活动对象的列表。

(2)重定位 ,用于更新对将要压缩的对象的引用。

(3)压缩 ,用于回收由死对象占用的空间,并压缩幸存的对象。 压缩阶段将垃圾回收中幸存下来的对象移至段中时间较早的一端。

 

6. GC在哪些情况下回进行回收工作?

 

(1)系统具有低的物理内存。 这是通过 OS 的内存不足通知或主机指示的内存不足检测出来。

(2)由托管堆上已分配的对象使用的内存超出了可接受的阈值。 随着进程的运行,此阈值会不断地进行调整。

(3)调用 GC.Collect 方法。 几乎在所有情况下,你都不必调用此方法,因为垃圾回收器会持续运行。 此方法主要用于特殊情况和测试。

 

 

7. using() 语法是如何确保对象资源被释放的?如果内部出现异常依然会释放资源吗?

using() 只是一种语法形式,其本质还是try…finally的结构,可以保证Dispose始终会被执行。

 

8. 解释一下C#里的析构函数?为什么有些编程建议里不推荐使用析构函数呢?

C#里的析构函数其实就是终结器Finalize,因为长得像C++里的析构函数而已。

有些编程建议里不推荐使用析构函数要原因在于:第一是Finalize本身性能并不好;其次很多人搞不清楚Finalize的原理,可能会滥用,导致内存泄露,因此就干脆别用了

 

9. Finalize() 和 Dispose() 之间的区别?

Finalize() 和 Dispose()都是.NET中提供释放非托管资源的方式,他们的主要区别在于执行者和执行时间不同:

finalize由垃圾回收器调用;dispose由对象调用。

finalize无需担心因为没有调用finalize而使非托管资源得不到释放,而dispose必须手动调用。

finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;而dispose一调用便释放非托管资源。

只有class类型才能重写finalize,而结构不能;类和结构都能实现IDispose。

另外一个重点区别就是终结器会导致对象复活一次,也就说会被GC回收两次才最终完成回收工作,这也是有些人不建议开发人员使用终结器的主要原因。

 

10. Dispose和Finalize方法在何时被调用?

Dispose一调用便释放非托管资源;

Finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;

 

11. .NET中的托管堆中是否可能出现内存泄露的现象?

是的,可能会。比如:

不正确的使用静态字段,导致大量数据无法被GC释放;

没有正确执行Dispose(),非托管资源没有得到释放;

不正确的使用终结器Finalize(),导致无法正常释放资源;

其他不正确的引用,导致大量托管对象无法被GC释放;

 

12. 在托管堆上创建新对象有哪几种常见方式?

new一个对象;

字符串赋值,如string s1=”abc”;

值类型装箱;

 

 

二,什么是多态?

通过继承实现的不同对象,调用相同的方法,产生不同的执行结果.

C#支持两种类型的多态,编译时的多态和运行时的多态。

(1)编译时的多态:

编译时的多态是通过重载来实现的,对于非虚的成员来说,系统在编译时,根据传递的参数类型,个数以及返回类型的不同决定实现不同的操作.

重载:

public int Sum(int x,int y)

public int Sum(int x,int y,int z)

public double Sum (Double x,Double y)

重载特点:

方法名称必须相同

参数列表必须不同

返回值类型可以不同 

 

(2)运行时的多态:

运行时的多态是指系统直到运行时,才根据实际情况实现何种操作.

运行时的多态可以通过virtual-override(虚成员覆盖实现)以及abstract-override(抽象方法覆盖实现)两种方式来实现.

通过override实现覆写注意的几点

只有虚方法和抽象方法才能被覆写

子类和基类中的方法必须具有相同的方法名称,参数个数,参数类型以及返回值类型.

 

总结:

编译时的多态使运行速度更快,就像const编译时解析.

运行时的多态带来了高度灵活以及抽象的特点. 

 

 

三、锁,除了lock还有哪些锁?

基元线程同步构造分为:基元用户模式构造和基元内核模式构造,两种同步构造方式各有优缺点,而混合构造(如lock)就是综合两种构造模式的优点。

 

1、用户模式构造

(1) System.Threading.Interlocked:易失构造,它在包含一个简单数据类型的变量上执行原子性的读或写操作。

(2)Thread.VolatileRead 和 Thread.VolatileWrite:互锁构造,它在包含一个简单数据类型的变量上执行原子性的读和写操作。

 

2、内核模式构造的主要有两种方式,以及基于这两种方式的常见的锁:

 

基于事件:如AutoResetEvent、ManualResetEvent

基于信号量:如Semaphore

Mutex

 3、混合线程同步

SemaphoreSlim、ManualResetEventSlim、Monitor、ReadWriteLockSlim

 

 

--------------------------------------EF相关 ------------------------------------

 

四、使用EF update 怎么保证在并发时数据正确?

 

五、EF如何处理并发?

 

什么叫并发:当多个用户同时更新同一数据的时候,由于更新可能导致数据的不一致性,使得程序的业务数据发生错误,这种情况可以称之为并发。

 

并发又分为两种:乐观并发 与 悲观并发 

乐观并发:即系统允许多个用户同时修改同一条记录,系统会预先定义由数据并发所引起的并发异常处理模式,去处理修改后可能发生的冲突

 

当出现乐观并发时应该怎么处理呢,通常有如下三种处理方法 

a 保留最后一次对象修改的值

b 保留最初的修改值

c  合并修改值

 

悲观并发:在同一时刻只允许一个用户修改相同数据,直接用Lock 与 unLock就可以处理.

 

六、事务的四大特性:

1 、原子性 (atomicity):强调事务的不可分割.

事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

 

2 、一致性 (consistency):事务的执行的前后数据的完整性保持一致.

事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。

 

3 、隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰

一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

 

4 、持续性 (durability) :事务一旦结束,数据就持久到数据库

也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。 

 

七、那么在没有对事务进行隔离时会发生哪些危害了?

脏读:当一个事务读取数据修改后以经SaveChange但事务还没有提交,此时另外一个事务就读取了该数据,此时的数据就是脏数据,这一过程就是脏读

 

不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的这一过程就是不可重复读

 

幻读:一个事务针对一张表的所有数据进行读取修改,而此时另一个事务向表中插入了一条数据,则第一个事务数据不包含新数据,像出现幻觉一样,这一过程就是幻读。

 

 

避免不可重复读需要锁行。

避免幻影读则需要锁表。

 

八、EF四种隔离级别

01:Read uncommitted(读未提交):最低级别,任何情况都会发生。

02:Read Committed(读已提交):可避免脏读的发生。

03:Repeatable read(可重复读):可避免脏读、不可重复读的发生。

04:Serializable(串行化):避免脏读、不可重复读,幻读的发生。

 

四种隔离级别:Seralizable 级别最高,最低的是 Read uncommitted 级别,级别越高,执行效率就越低,隔离级别的设置只对当前链接有效,对.NET 操作数据库来说,一个 Connection 对象相当于一个链接。

01:MySQL 数据库的默认隔离级别是Repeatable read 级别。

02:Oracle数据库中,只支持 Seralizable 和 Read committed级别,默认的是 Read committed 级别。

03:SQL Server 数据库中,默认的是Read committed 级别。

http://www.zyiz.net/tech/detail-182425.html

 

九、efcore事务的几种方法?

1、默认事务(SaveChanges)

(1).默认情况下,如果数据库提供程序支持事务,单个 SaveChanges() 调用中的所有变更都会在一个事务中被提交。如果其中任何一个变更失败了, 那么事务就会回滚,没有任何变更会被应用到数据库。这意味着 SaveChanges() 能够确保要么成功保存,要么在发生错误时不对数据库做任何修改。

(2).关闭默认事务:context.Database.AutoTransactionsEnabled = false; 

 

2. DbContextTransaction

(1). 使用方式

  BeginTransaction开启事务、Commit提交事务、Rollback回滚事务、Dispose销毁,如果用Using包裹的话,不再需要手动Rollback,走完Using会自动回滚。如果不用Using包裹事务,就需要在Catch中手动RollBack回滚,并且最好最后手动的Dispose一下。(如SameDbContext文件夹中的Test1和Test2方法)

(2). 使用场景

 A. 同一个上下文多个SaveChanges的方法(如:自增主键后续要用到)、SaveChanges和EF调用SQL语句混用 

 

3. 环境事务 TransactionScope

(1)使用方式

  new TransactionScope创建事务、Complete提交事务、 Transaction.Current.Rollback();回滚事务、Dispose销毁对象。如果用Using包裹的话,不再需要手动Rollback,走完Using会自动回滚。如果不用Using包裹事务,就需要在Catch中手动RollBack回滚,并且最好最后手动的Dispose一下。TransactionScope有三个属性:IsolationLevel(隔离等级),Timeout(超时),TransactionScopeOption(选项)

 

(2)用途

A. 同一个上下文的事务。(多个SaveChanges(自增主键后续用到的情况)、SaveChanges和EF调用SQL语句混用)(如Test1方法)

B. 多种数据库技术访问同一个数据库的事务 (如Test2方法)

C. 同一个数据库多个不同的上下文是支持的(如Test3方法)

D. 不同数据库的上下文是不支持的,(如Test4方法,开启msdtc服务的步骤: cmd命令→ net start msdtc ,然后发现报错:This platform does not support distributed transactions.说明目前Core平台下不支持分布式事务)

参考:

http://www.zyiz.net/tech/detail-182188.html

http://www.zyiz.net/tech/detail-182154.html

 

十、使用EF update 怎么保证在并发时数据正确?

 

 

 1、RowVersion ( TimeStamp )  时间戳

EF实现Rowversion 并发控制 需要借助 TimeStamp 标示 ,并且一个类只能有 一个此标示,标示的必须是byte[]类型。使用Rowversion会对整行数据进行并发检测。

 

2、 ConcurrencyCheck

有些时候并不需要控制针对整条记录的并发,只需要控制某个列的数据不会出现脏操作就ok,这个时候 就使用ConcurrencyCheck 。你必须将ConcurrencyCheck特性添加到实体需要控制并发的非主键属性上,使实体框架可以将所有标记的列包含到更新语句的Where子句中。

3、 DbUpdateConcurrencyException

您可以通过EF实体框架引发的DbUpdateConcurrencyException异常处理来解决冲突。

 

 

------------------------------------------------------------------------------

 

二十、jwt的组成有那几部分?

 

  样式:"xxxxxxxxxxxx.xxxxxxxxxxxxx.xxxxxxxxxxxxxxxx"由三部分组成.

 

(1).Header头部:{"alg":"HS256","typ":"JWT"}基本组成,也可以自己添加别的内容,然后对最后的内容进行Base64编码.

 

(2).Payload负载:iss、sub、aud、exp、nbf、iat、jti基本参数,也可以自己添加别的内容,然后对最后的内容进行Base64编码.

 

(3).Signature签名:将Base64后的Header和Payload通过.组合起来,然后利用Hmacsha256+密钥进行加密。


二十一、jwt的安全性有哪些建议?

1、SecurityKey一定要保管好;

2、签名一定要有,校验签名并且不接受非法的签名;( 签名解决了数据传输过程中参数被篡改的风险)

3、payload增加时间戳减少重放攻击;

4、payload里不存放敏感信息,若有请用临时票据token形式的;

5、接口传输采用https协议;

https://www.jianshu.com/p/836df92c06eb

 

二十一、什么是面向对象?面向对象的三大特性和五大原则是什么?

 

面向对象的方法主要是把事物给对象化,包括其属性和行为。面向对象编程更贴近实际生活的思想。总体来说面向对象的底层还是面向过程,面向过程抽象成类,然后封装,方便使用就是面向对象(万物皆对象)。

 

三大基本特性:封装,继承,多态

封装

封装,就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

 

继承

继承,指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过 “继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用 基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。

 

多态

通过继承实现的不同对象,调用相同的方法,产生不同的执行结果.。

 

五大基本原则:SPR, OCP, LSP, DIP, ISP

单一职责原则SRP(Single Responsibility Principle)

是指一个类的功能要单一,不能包罗万象。如同一个人一样,分配的工作不能太多,否则一天到晚虽然忙忙碌碌的,但效率却高不起来。

 

开放封闭原则OCP(Open-Close Principle)

一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的。 

里式替换原则LSP(the Liskov Substitution Principle LSP)

子类应当可以替换父类并出现在父类能够出现的任何地方。 

 

依赖倒置原则DIP(the Dependency Inversion Principle DIP)

高层模块不应该依赖低层模块,二者都应该依赖其抽象。

 

接口分离原则ISP(the Interface Segregation Principle ISP)

模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来

 

二十二、sqlserver的触发器是什么?怎么使用触发器?

触发器是数据库中由一定时间触发的特殊的存储过程,他不是由程序掉用也不是手工启动的。触发器的执行可以由对一个表的insert,delete, update等操作来触发,触发器经常用于加强数据的完整性约束和业务规则等等。

http://www.zyiz.net/tech/detail-182423.html

http://www.zyiz.net/tech/detail-182424.html

 

二十二、sqlserver里的面试题集合:

1. 索引的作用?和它的优点缺点是什么?

索引就一种特殊的查询表,数据库的搜索引擎可以利用它加速对数据的检索。索引很类似与现实生活中书的目录,不需要查询整本书内容就可以找到想要的数据。缺点是它减慢了数据录入的速度,同时也增加了数据库的尺寸大小。

 

2. 介绍存储过程基本概念和 她的优缺点

存储过程是一个预编译的SQL语句,他的优点是允许模块化的设计,也就是说只需创建一次,在该程序中就可以调用多次。例如某次操作需要执行多次SQL,就可以把这个SQL做一个存储过程,因为存储过程是预编译的,所以使用存储过程比单纯SQL语句执行要快。缺点是可移植性差,交互性差。

 

3. 使用索引有哪些需要注意的地方?

创建索引的的字段尽量小,最好是数值,比如整形int等;

对于频繁修改的字段,尽量不要创建索引,维护索引的成本很高,而且更容易产生索引碎片;

定期的索引维护,如索引碎片的修复等;

不要建立或维护不必要的重复索引,会增加修改数据(新增、修改、删除数据)的成本;

使用唯一性高的字段创建索引,切不可在性别这样的低唯一性的字段上创建索引;

在SQL语句中,尽量不要在Where条件中使用函数、运算符或表达式计算,会造成索引无法正常使用;

应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描;

应尽量避免在 where 子句中使用!=或<>操作符,否则将导致引擎放弃使用索引而进行全表扫描;

 

4. 索引碎片是如何产生的?有什么危害?又该如何处理?

索引在使用一段时间后(主要是新增、修改、删除数据,如果该页已经存储满了,就要进行页的拆分,频繁的拆分,会产生较多的索引碎片)会产生索引碎片。

 

索引碎片会严重印象数据的查询效率,如果碎片太多,索引可能不会被使用。

 

碎片的处理方式主要有两种:

 

第一种是预防:设置页的填充因子

 

意思就是在页上设置一段空白区域,在新增数据的时候,可以使用这段空白区域,可以一定的避免页的拆分,从而减少索引碎片的产生。

 

填充因子就是用来描述这种页中填充数据的一个比例,一般默认是100%填充的。如果我们修改填充因子为80%,那么页在存储数据时,就会剩余20%的剩余空间,这样在下次插入的时候就不会拆分页了。 那么是不是我们可以把填充因子设置低一点,留更多的剩余空间,不是很好嘛?当然也不好,填充因子设置的低,会需要分配更多的存储空间,叶子节点的深度会增加,这样是会影响查询效率的,因此,这是要根据实际情况而定的。

 

那么一般我们是怎么设置填充因子的呢,主要根据表的读写比例而定的。如果读的多,填充因子可以设置高一点,如100%,读写各一半,可以80~90%;修改多可以设置50~70%。

 

第二种是索引修复:定期对索引进行检查、维护,写一段SQL检查索引的碎片比例,如果碎片过多,进行碎片修复或重建,定期执行即可。具体可以参考本文末尾的相关参考资料。

 

5. 锁的目的是什么?

主要解决多个用户同时对数据库的并发操作时会带来以下数据不一致的问题:

 

丢失更新,同时修改一条数据

读脏,A修改了数据后,B读取后A又取消了修改,B读脏

不可重复读,A用户读取数据,随后B用户读取该数据并修改,此时A用户再读取数据时发现前后两次的值不一致

还有一种是幻读,这个情况好像不多。

并发控制的主要方法是封锁,锁就是在一段时间内禁止用户做某些操作以避免产生数据不一致

 

6. 锁的粒度有哪些?

数据库锁:锁定整个数据库,这通常发生在整个数据库模式改变的时候。

表锁:锁定整个表,这包含了与该表相关联的所有数据相关的对象,包括实际的数据行(每一行)以及与该表相关联的所有索引中的键。

区段锁:锁定整个区段,因为一个区段由8页组成,所以区段锁定是指锁定控制了区段、控制了该区段内8个数据或索引页以及这8页中的所有数据行。

页锁:锁定该页中的所有数据或索引键。

行或行标识符:虽然从技术上将,锁是放在行标识符上的,但是本质上,它锁定了整个数据行。

 

7. 什么是事务?什么是锁?

事务就是被绑定在一起作为一个逻辑工作单元的SQL语句分组,如果任何一个语句操作失败那么整个操作就被失败,以后操作就会回滚到操作前状态,或者是上个节点。为了确保要么执行,要么不执行,就可以使用事务。要将所有组语句作为事务考虑,就需要通过ACID测试,即原子性,一致性,隔离性和持久性。

锁是实现事务的关键,锁可以保证事务的完整性和并发性。

 

8. 视图的作用,视图可以更改么?

视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。

 

视图的目的在于简化检索,保护数据,并不用于更新。

 

9. 什么是触发器(trigger)? 触发器有什么作用?

触发器是数据库中由一定时间触发的特殊的存储过程,他不是由程序掉用也不是手工启动的。触发器的执行可以由对一个表的insert,delete, update等操作来触发,触发器经常用于加强数据的完整性约束和业务规则等等。

 

10. SQL里面IN比较快还是EXISTS比较快?

这个题不能一概而论,要根据具体情况来看。IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。

 

如果查询语句使用了not in,那么对内外表都进行全表扫描,没有用到索引;而not exists的子查询依然能用到表上的索引。所以无论哪个表大,用not exists都比not in 要快。参考资料:http://www.zyiz.net/tech/detail-182426.html

 

11. 维护数据库的完整性和一致性,你喜欢用触发器还是自写业务逻辑?为什么?

尽可能使用约束,如check、主键、外键、非空字段等来约束。这样做效率最高,也最方便。其次是使用触发器,这种方法可以保证,无论什么业务系统访问数据库都可以保证数据的完整新和一致性。最后考虑的是自写业务逻辑,但这样做麻烦,编程复杂,效率低下。



二十三、oauth是什么?有哪些方式?

oauth是开发授权协议。主要用来颁发令牌(token)。有四种授权方式。

 

授权码(authorization-code)

隐藏式(implicit)

密码式(password):

客户端凭证(client credentials)

 

 

1、授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。


二十四、decimal的精度为什么比double高?

 

 

float 单精度浮点 32bit,

double 双精度浮点64bit,

decimal是高精度 128bit,浮点型。

float double 是 基本类型(primitive type),decimal不是。

float 有效数字7位,范围 ±1.5 × 10E−45 to ±3.4 × 10E38

double 有效数字15/16 位,范围 ±5.0 × 10 E−324 to ±1.7 × 10E308

decimal 有效数字 28/29 位,范围 ±1.0 × 10E−28 to ±7.9 × 10E28 

 

decimal的有效位数很大,达到了28位,但是表示的数据范围却比float和double类型小。使用的时候会对计算时的性能有影响。

数值存储范围越小的精度越高,存储数值范围越大,精度就越不准确,如果存储正常金额的情况下,使用money,好处在于可以存储不指定的小数点位数的数值,比较真实。如果对于既要求精度,又固定小数点位数的数值存储,采用decimal(numeric),优点在于可以自定义小数点位数,精度高。如特殊情况,如数值范围巨大只能用float(real)类型了,此类型一般不提倡使用。


特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。


举报收藏 0评论 0
0相关评论
相关最新动态
推荐最新动态
点击排行
{
网站首页  |  关于我们  |  联系方式  |  使用协议  |  隐私政策  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号