.NET CORE 面试题及答案

CORE

1.POP、OOP、AOP区别。AOP解决什么问题

POP是面向过程 耦合度高 OOP是面向对象 具有封装 继承 多态的特性 AOP面向切面

OOP

封装,隐藏细节,减少耦合,便于维护
继承,代码重用
多态,多种状态

AOP

理解:将模块化的程序中涉及多个模块的公共部分进一步提取成模块,提高程序的模块化
优点:提高可维护性,降低耦合度

2.AOP的实现

过滤器 拦截器 缓存

值类型和引用类型的转换

public class Main {
   public static void main(String[] args) {
       Integer i = 10;
       int n = i;
   }
}

4.抽象类和接口的区别和使用场景

1.抽象类的使用场景

既想约束子类具有共同的行为(但不再乎其如何实现),又想拥有缺省的方法,又能拥有实例变量
如:模板方法设计模式,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤的具体实现。

2.接口的应用场景

① 约束多个实现类具有统一的行为,但是不在乎每个实现类如何具体实现
② 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
③ 实现类需要具备很多不同的功能,但各个功能之间可能没有任何联系。
④ 使用接口的引用调用具体实现类中实现的方法(多态)

5.锁:乐观锁,悲观锁 举例说明应用场景

悲观锁,顾名思义就是很悲观,意思就是我每次去拿数据的时候都会上锁,这个时候如果别人也来拿这个数据,就会阻塞,一直等到我获取结束释放锁之后,别人才会去拿锁接着去执行!

乐观锁其实就是很乐观,我每次去拿数据的时候,不会上锁,只有在更新的时候,会结合版本号这样的方式,判断当前有没有人在更新,如果有人更新,我就不会被修改,否则修改!这样就提高了吞吐量!

悲观锁和乐观锁并没有优先使用之分,只有适不适合之分,悲观锁适用于写的场景比较多,也就是冲突比较多的时候,而乐观锁适用于写的场景比较少,冲突比较少的情况!

使用 FOR UPDATE

6.什么是死锁?如何保证你实现的锁结果不发生死锁

多个进程或线程互相等待对方的资源,在得到新的资源之前不会释放自己的资源,这样就形成了循环等待,这种现象被称为死锁。

预防死锁

1、线程一次请求所有资源。
2、当占有一个资源,无法请求到新的资源就释放占有的资源,等待一定时间后再次重新请求。
3、资源进行编号,必须按照顺序请求资源。

7.数组和链表的区别

(1)数组的元素个数是固定的,而链表的结点个数可按需要增减。

(2)数组元素的存储单元在定义时分配,链表节点的存储单元在执行时动态向系统申请。

(3)数组的元素顺序关系由元素在数组中的位置(即下标)确定,链表中的节点关系由节点所包含的指针来体现。

(4)对于不是固定长度的列表,用可能最大长度的数组来描述,会浪费许多的存储空间。

(5)对于元素的插入、删除操作非常频繁的列表处理场合,用数组表示列表也不是不合适。若用链表实现,会使程序结构清晰,处理的方法也比较简便。

8.WebAPI 和 webservice的区别

1、webapi用的是http协议,webservice用的是soap协议。

2、webapi无状态,相对webservice更轻量级。webapi支持get、post等http操作。

web计算平台包含了广泛的功能,其中的大部分均可以通过API(应用程序编程接口)访问。从简 单的社会书签服务del.icio.us,到复杂得多的amazon s3全虚拟化存储平台,想想能用这些web api做点什么,真是惊人。 在本贴中,我把web平台归为6个基本设施,并简要概述些相关产品。其间的线索是这些产品都提供了API,这意味着他们本身可以被其他服务整合。

WebService是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的交互操作的应用程序。Web Service技术, 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件, 就可相互交换数据或集成。依据Web Service规范实施的应用之间, 无论它们所使用的语言、 平台或内部协议是什么, 都可以相互交换数据。

SOAP是简单对象访问协议是交换数据的一种协议规范,是一种轻量的、简单的、基于XML(标准通用标记语言下的一个子集)的协议,它被设计成在WEB上交换结构化的和固化的信息。

9.什么是线程安全和线程非安全

多个线程在并行执行且使用到了共享数据的操作中,最终能正确执行得到正确结果的就是线程安全的,反之就是线程不安全的

多线程开发过程中对共享数据进行操作引起变量引用中前后不一致会形成计算错误就是非线程安全,可以通过共享数据加信号量锁、原子性、副本解决。

10.C# 常用数据结构

数组、链表、队列、哈希表

哈希表(Hash table,也叫散列表),是一种有限连续的地址空间,用以存储按散列函数计算得到相应散列地址的数据记录。通常散列表的存储空间是一个一维数组,散列地址是数组的下标
优点:
如果关键字已知则存取速度极快,支持高效的数据插入、删除和查找操作。
缺点:
不支持快速顺序遍历散列表中的数据。
使用场景:
哈希表适用于那种查找性能要求高,数据元素之间无逻辑关系要求的情况,同时哈希表在海量数据处理中有着广泛应用。

11.Dictionary的实现和使用场景

1、Dictionary是一个泛型

2、他本身有集合的功能,有时候也可以把它看成数组

3、构造:Dictionary<[key], [value]>
4、它可以存储一个Key值和一个泛型,然后通过某一个一定的[key]去找到对应的值

5、Key不允许重复就像数据库主键一样,但是value值可以

搭建数据字典:因为有唯一的Key值就像数据库索引一样可以快速定位数据进行读取,还有一个可以存储各种类型的值,所以一般用Dictionary来搭建一个字典库,比如库存,价格,单据数据等。一次性读取数据后进行数据存储在其他地方进行调用,可以减少对数据库的访问减少数据库服务器的压力

12.Entity Framework 如何实现 left join

Linq表达式

	var query=(from a in db.TableA
                  join b in db.TableB
                  on a.ID equals b.NodeID into c
                  from x in c.DefaultIfEmpty()
                  where a.ID==param
                  select new
                  {
                          a,
                          x.Key
                  }).FirstOrDefault();

Lambda表达式 读取指定返回列表字段的左连接信息:

	var GJoinList = db.Sys_User.GroupJoin(db.Sys_Department, u => u.DepartmentId, d => d.DepartmentId, (u,d) => new { UserId=u.UserId, Account=u.Account, RealName=u.RealName, EnabledMark=u.EnabledMark, DeleteMark=u.DeleteMark,DepartmentName = d.FirstOrDefault(x=>x.DepartmentId==u.DepartmentId).FullName}).Select(o=>o);

13.描述一下依赖注入后的服务生命周期

单实例服务, 通过add singleton方法来添加。在注册时即创建服务, 在随后的请求中都使用这一个服务。

短暂服务, 通过add transient方法来添加。是一种轻量级的服务,用于无状态服务的操作。

作用域服务,一个新的请求会创建一个服务实例。使用add scoped方法来添加。

14.IOC容器中实现了IDispose接口的类,需要主动释放吗

正常来说不需要,除非出现了异常可能需要主动释放。

15.列举一下.net mvc 中AOP的示例,以及作用和场景

AuthorizationFilter 鉴权授权

ResourceFilter 资源过滤器

ExceptionFilter 异常过滤器

ActionFilter 动作/方法过滤器

ResultFilter 结果过滤器

注释Attribute 属性
实现过滤器
	public class MyAction :Attribute,IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context)
        {
            var controllerName = context.RouteData.Values["controller"];
            var actionName = context.RouteData.Values["action"];
            Console.WriteLine($"行为过滤器OnActionExecuted作用于{controllerName }控制器下的{actionName }方法运行之后</br>", Encoding.Default);
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var controllerName = context.RouteData.Values["controller"];
            var actionName = context.RouteData.Values["action"];
            Console.WriteLine($"行为过滤器OnActionExecuting作用于{controllerName }控制器下的{actionName }方法运行之前</br>", Encoding.UTF8);
        }
    }
services.AddTransient(typeof(MyAction));将过滤器注册成瞬态服务
[MyAction]通过标记来使用

16.描述一下管道模式,以及在.net core 中的使用

请求管道描述的是一个请求进到我们的后端应用,后端应用如何处理的过程,从接收到请求,之后请求怎么流转,经过哪些处理,最后怎么返回响应。请求管道就是一次请求在后端应用的生命周期。了解请求管道,有助于我们明白后端应用是怎么工作的,我们的代码是怎么工作的,在我们的业务代码执行前后经过哪些步骤,有助于我们之后更好的实现一些AOP操作。

当一个http请求被送入到HttpRuntime之后,这个Http请求会继续被送入到一个被称之为HttpApplication Factory的一个容器当中,而这个容器会给出一个HttpApplication实例来处理传递进来的http请求,而后这个Http请求会依次进入到如下几个容器中:HttpModule --> HttpHandler Factory --> HttpHandler。当系统内部的HttpHandler的ProcessRequest方法处理完毕之后,整个Http Request就被处理完成

 	// .NET Core3.1默认代码
     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
     public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
     {
         if (env.IsDevelopment())
         {
             // 中间件
             app.UseDeveloperExceptionPage();
         }
         app.UseRouting();

         app.UseAuthorization();

         app.UseEndpoints(endpoints =>
         {
             endpoints.MapControllers();
         });
     }

注册

终结者模式(Run())
Run 终结式只是执行,没有去调用Next ,一般作为终结点。
所谓Run终结式注册,其实只是一个扩展方法,最终还是调用Use方法
Use 方法注册
use 方式注册中间件得出的结论是:Use注册动作 不是终结点 ,执行next,就可以执行下一个中间件
如果不执行,就等于Run
app.Use(async (context, next) =>
			{
				await context.Response.WriteAsync("Hello 1 开始");
				await next();//调用下一个中间件
				await context.Response.WriteAsync("Hello 1 结束");
			});
			app.Use(async (context, next) =>
			{
				await context.Response.WriteAsync("Hello 2 开始");
				await next();
			});
封装中间件
    public class CustomMiddleWare
    {
        private readonly RequestDelegate _next;
        //依赖注入
        public CustomMiddleWare(RequestDelegate next)
        {
            this._next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            await context.Response.WriteAsync($"{nameof(CustomMiddleWare)},Hello World1!
"
); await _next(context); await context.Response.WriteAsync($"{nameof(CustomMiddleWare)},Hello World2!
"
); } }
注册使用
	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		app.UseMiddleware<CustomMiddleWare>();
	}
进一步封装
使用扩展方法,将这个类中的逻辑作为IApplicationBuilder的扩展方法(ApplicationBuilder 应用程序生成器)
	public static class MiddleExtend
	{
		public static IApplicationBuilder UseCustomMiddleWare(this IApplicationBuilder builder)
		{
			return builder.UseMiddleware<CustomMiddleWare>();
		}
	}
注册使用
	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		app.UseCustomMiddleWare();
	}

17.谈谈.net中的GC,垃圾回收策略,如何回收非托管资源

手动强制回收:GC.Collect() GC.Collect(0/1/2)0:新生代,1:旧生代,2:持久代

托管:可借助GC从内存中释放的数据对象(以下要描述的内容点)

非托管:必须手工借助Dispose释放资源(实现自IDisposable)的对象

根据实例的生命周期和对象的调用频率来判断堆中的数据是否为新生代、旧生代、持久代(静态)来选择回收内存。

数据库:

1.事务的特性ACID

1. 原子性(Atomicity)

事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。

回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

2. 一致性(Consistency)

数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。

3. 隔离性(Isolation)

一个事务所做的修改在最终提交以前,对其它事务是不可见的。

4. 持久性(Durability)

一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。使用重做日志来保证持久性。

2.简单的理解事务的隔离级别,以及你对他的理解

1. read uncommited(会出现脏读问题)

读未提交,就是一个事务可以读取另一个事务未提交的数据
举例:

小明的工资是20000,人事在输入工资的时候多打了个0,但是还没有提交,此时小明会查到自己的工资变成了20000

问题:会出现脏读,即:一个事务读取另一个事务未提交的数据

2. read commited(会出现不可重复读问题)

读已提交,一个事务只能读取另一个事务已提交的事务
举例:

程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的
问题:解决了脏读问题,但是会出现不可重复读问题,即:出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。

3. Repeatable read(幻读问题)

重复读,就是在开始读取数据(事务开启)时,不再允许修改操作
事例:

程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。
分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,**不可重复读对应的是修改,即UPDATE操作。**但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

4. Serializable 序列化

Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

3.索引的优缺点,使用场景

增加查询速度、增加外键关联效率、增减分组排序的速度,使用唯一索引可以保证数据的唯一性。缺点是影响增删效率,占有一定物理空间。

4.唯一索引,主键索引,聚集索引,非聚集索引

唯一索引

唯一索引是不允许其中任何两行具有相同索引值的索引。当现有数据中存在重复的键值时,大多数数据库不允许将新创建的唯一索引与表一起保存。数据库还可能防止添加将在表中创建重复键值的新数据。例如,如果在employee表中职员的姓(lname)上创建了唯一索引,则任何两个员工都不能同姓。

主键索引

数据库表经常有一列或多列组合,其值唯一标识表中的每一行。该列称为表的主键。在数据库关系图中为表定义主键将自动创建主键索引,主键索引是唯一索引的特定类型。该索引要求主键中的每个值都唯一。当在查询中使用主键索引时,它还允许对数据的快速访问。

聚集索引

在聚集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同。一个表只能包含一个聚集索引。如果某索引不是聚集索引,则表中行的物理顺序与键值的逻辑顺序不匹配。与非聚集索引相比,聚集索引通常提供更快的数据访问速度。聚集索引和非聚集索引的区别,如字典默认按字母顺序排序,读者如知道某个字的读音可根据字母顺序快速定位。因此聚集索引和表的内容是在一起的。如读者需查询某个生僻字,则需按字典前面的索引,举例按偏旁进行定位,找到该字对应的页数,再打开对应页数找到该字。这种通过两个地方而查询到某个字的方式就如非聚集索引。

5.什么是SQL注入式攻击?如何避免?

1.所谓SQL注入式攻击,就是攻击者把SQL命令插入到Web表单的输入域或页面请求的查询字符串,欺骗服务器执行恶意的SQL命令。

2.在某些表单中,用户输入的内容直接用来构造(或者影响)动态SQL命令,或作为存储过程的输入参数,这类表单特别容易受到SQL注入式攻击。

限制或替换出现的SQL关键字 尽量使用存储过程执行查询 限制查询参数的长度 单引号替换成双引号。

6.索引最左匹配原则

在建立联合索引时,都遵循从左往右的优先级,最左优先,当出现范围查询(> < between like等等)时停止匹配。

7.索引失效举例

!=

<>

not

缓存:

1.缓存的意义,缓存处理流程

通过提高服务的性能从而提高应用的用户体验。

系统性能指标:响应时间、延迟时间、吞吐量、并发用户数量和资源利用率

吞吐量:系统在单位时间内处理的请求的数量

提高查询数据的效率,降低数据库的压力。

2.用过哪些缓存组件

Redis

3.Redis的存储类型

1.string(字符串)

String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。
常规key-value缓存应用;

2.list(列表)

Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。
使用List结构,我们可以轻松地实现最新消息排行等功能。

3.hash(散列)

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
存储部分变更的数据,如用户信息等。

4.sets (集合)

set就是一个集合,集合的概念就是一堆不重复值的组合。利用Redis提供的set数据结构,可以存储一些集合性的数据。set中的元素是没有顺序的。

5.sorted set(有序集合)

和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。

4.缓存穿透,缓存击穿,缓存雪崩,缓存降级

缓存穿透

缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍。如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而宕机崩溃。
在缓存中添加一个有生命周期的缓存数据 值设置为空NULL

缓存击穿

缓存击穿跟缓存雪崩有点类似,缓存雪崩是大规模的key失效,而缓存击穿是某个热点的key失效,大并发集中对其进行请求,就会造成大量请求读缓存没读到数据,从而导致高并发访问数据库,引起数据库压力剧增。这种现象就叫做缓存击穿。
生命周期设置永不过期、缓存失效后降低对失效数据请求的线程数量

缓存雪崩

缓存中大量数据的生命周期过期导致大量数据失效,请求都指向了数据库,高并发的时候会增加数据库的压力,引起数据库崩溃。避免大量数据在同一时间过期,对热点数据永远不过期

缓存降级

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。
在项目实战中通常会将部分热点数据缓存到服务的内存中,这样一旦缓存出现异常,可以直接使用服务的内存数据,从而避免数据库遭受巨大压力。

你可能感兴趣的:(.netcore,面试,c#)