面试题汇总

文章目录

面试题

多看几篇文章的解释,自己去理解,自己去总结,缺少总结的经验。

1 数据库三大范式是什么

https://zhuanlan.zhihu.com/p/618012849
    
关系型数据库中,关于数据表设计的基本原则,规则就称为范式,范式是我们在设计数据库结构过程中需要遵循的规则和指导方法。
# 第一范式:1NF 是指数据库表的每一列都是不可分割
    -每列的值具有原子性,不可再分割。
    -每个字段的值都只能是单一值。
    
# 第二范式(2NF)是在第一范式(1NF)的基础上建立起来得,满足第二范式(2NF)必须先满足第一范式(1NF)
	-如果表是单主键,那么主键以外的列必须完全依赖于主键,其它列需要跟主键有直接关系。
	-如果表是复合主键(复合主键),那么主键以外的列必须完全依赖于主键,不能仅依赖主键的一部分。
	### 解释
    表的主键是学生id,有一栏是学校地址,它跟学校有直接关系,但是跟学生id没有直接关联,是间接关联,不符合2NF。
	学生id和课程id联合主键,非主键列【分数】完全依赖于主键【学生id和课程id,学生id和课程id两个值才能决定score的值。 但是【课程名字】只依赖于课程id,与学生id没有依赖关系,它不完全依赖于主键,只依赖于主键的一部分,不符合2NF。

# 第三范式(3NF)是在第二范式的基础上建立起来的,即满足第三范式必须要先满足第二范式
    第三范式(3NF)要求:表中的非主键列必须和主键直接相关而不能间接相关(第三范式是对第二范式的补充);也就是说:非主键列之间不能相关依赖,不存在传递依赖
    ### 解释
    表中【部门名称】依赖于【部门id】,而【部门id】依赖于【id主键】,从而推出【部门名称】依赖于id(主键)
    【部门名称】不直接依赖于主键,而是通过依赖于非主键列而依赖于主键,属于传递依赖,不符合3NF。应该拆开分成两个表,一张人员表,一张部门表。外键关联。# 建立外键的关系就是为了符合第三范式。
    
在设计数据库结构的时候,要尽量遵守三范式,如果不遵守,必须有⾜够的理由。⽐如性能。事实上我们经常会为了性能⽽妥协数据库的设计。

2 mysql有哪些索引类型,分别有什么作用

# 主键索引(聚簇索引)
	主键,即便表不建立主键,也会有个隐藏字段是主键,是主键索引,mysql是基于
主键索引构建的b+树,如果没有主键,如果按主键搜索,速度是最快的--->一定会有主键索引

# 辅助索引(普通索引)
	咱们给某个自己加索引,django  index=True,它没有任何限制,唯一的任务就
是加快系统对数据的访问速度。通过该字段查询,会提高速度,如果字段 变化小(性别,
年龄),不要建立普通索引,(性别只有两种状态,建立普通索引,没有任何用处)
 	建立普通索引:
    -orm中使用 index=True
	-sql语句中用 index 关键字:
    	create index index_id on ta_student(id);
        index_id 索引名字 
        ta_student 表名 
        (id)id字段建普通索引
        
# 唯一索引(unique)
    不是为了提高访问速度,而是为了避免数据出现重复
    唯一索引通常使用 UNIQUE 关键字
    create unique index index_id on tb_student(id);
    CREATE UNIQUE INDEX index_id ON tb_student(id);
        index_id 索引名字
        ta_student 表名 
        (id)id字段建唯一索引
    
    	
# 组合索引(联合索引)
	由多个列构成的索引
    	-django 中:
        class Meta:
            index_together = ["store", "sensor"]

        -CREATE INDEX index_name
		ON table_name (column1, column2, column3); 
        create index index_name on tablie_name()
    
# 全文索引-->基本不用--->
    全文索引主要用来查找文本中的关键字,只能在 CHAR、VARCHAR 或 TEXT 类型的列上创建。
    在 MySQL 中只有 MyISAM 存储引擎支持全文索引。
    全文索引允许在索引列中插入重复值和空值。
    不过对于大容量的数据表,生成全文索引非常消耗时间和硬盘空间。
    创建全文索引使用 FULLTEXT 关键字
MySQL5.6及之后的版本默认的存储引擎是InnoDB。InnoDB存储引擎规定了所有的表都必须
有且只有一个主键。
b+树是什么?

# 索引的缺点:
第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空
间,如果要建立聚簇索引,那么需要的空间就会更大。
第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了
数据的维护速度。

3 事务的特性和隔离级别

隔离级别脏读不可重复读幻读
未提交读(Read uncommitted)
已提交读(Read committed)×
可重复读(Repeatable read)××
可串行化(Serializable )×××
https://www.cnblogs.com/liuqingzheng/p/16480047.html
    
事务是⼀个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执⾏的结果必须使
数据库从⼀种⼀致性状态变到另⼀种⼀致性状态。事务是逻辑上的⼀组操作,要么都执⾏,要么都不执⾏。

# 事务四大特性(ACID)
原子性(Atomicity):事务是最⼩的执⾏单位,不允许分割。数据库把'要么全做,要么全不做'
的这种规则称为原子性
一致性(Consistency):执⾏事务前后,数据保持⼀致,多个事务对同⼀个数据读取的结果是相
同的;可以并发着执行多个事务
隔离性(Isolation):并发访问数据库时,⼀个⽤户的事务不被其他事务所⼲扰,各并发事务之
间数据库是独⽴的。事务之间相互隔离,不受影响,这与事务的隔离级别密切相关--->事务隔离级别
持久性(Durable):⼀个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发⽣故
障也不应该对其有任何影响。


# 隔离级别--->为了保证四大特性中的隔离性而有的
    -Read uncommitted(读未提交)-ru  :一个事物读到了另一个事务未提交的数据,最低的隔离级别
    -Read committed(读已提交)-rc:如果设置了这个级别一个事物读不到另一个事务未提交的数据。
写事务提交之前不允许其他事务的读操作(加锁来保证)
    -Repeatable read(可重复读取)-rr:在开始读取数据(事务开启)时,不再允许修改操作,这样
就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别
    -Serializable(串行化):求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,效
率比较低。
    
这⾥需要注意的是:Mysql 默认采⽤的 REPEATABLE_READ(可重复读)隔离级别 
Oracle 默认采⽤的 READ_COMMITTED(读取已提交)隔离级别

补充

事务隔离机制的实现基于锁机制和并发调度。其中并发调度使⽤的是MVVC(多版本并发控制),通过保存
修改的旧版本信息来⽀持并发⼀致性读和回滚等特性。
因为隔离级别越低,事务请求的锁越少,所以⼤部分数据库系统的隔离级别都是READ-COMMITTED(读取提
交内容):,但是你要知道的是InnoDB 存储引擎默认使⽤ REPEATABLE-READ(可重读)并不会有任何性能损失。

InnoDB 存储引擎在 分布式事务 的情况下⼀般会⽤到SERIALIZABLE(可串⾏化)隔离级别。

4 脏读、不可重复读、幻读,MySQL5.7之后的默认的事务隔离级别

https://www.cnblogs.com/liuqingzheng/p/16480047.html
    
    5.6:不多了
    5.7:比较多
    8.0:比较多
	PostgreSQL:安装,插入数据,查询数据,集成到django
    oracle:
        
        
# 脏读,不可重复读,幻读  出现的原因是什么?
	-事务隔离级别不同,就会出现不同的问题
    
# 脏读:
	脏读指的是读当前事务到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可
能最终不会存到数据库中,也就是不存在的数据(如果没有回滚,就是存在的数据)。读到了'并不一定
最终存在'的数据,这就是脏读。
    # 事务1和事务2并发执行,事务1改了数据,事务2读取了以后,但事务1进行了回滚,导致事务
2读取的数据有误。如果事务1修改后直接提交,事务2就没有问题。
 
# 不可重复读:
	-解释:不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一
批数据出现不一致的情况
	-导致的原因:事务 A 多次读取同一数据,但事务 B 在事务A多次读取的过程中,对数据作了更
新并提交,导致事务A多次读取同一数据时,结果 不一致
	# 事务1读取了数据,事务2修改了数据并且提交了,接着事务1再次读取,发现两次的数据不相同
        
        
# 幻读:解决了不可重复读的问题了
	#幻读错误的理解
    幻读是 事务A 执行两次 select 操作得到不同的数据集,即 select 1 得到 10 条记录,select 2 
得到 15 条记录。 这其实并不是幻读,既然第一次和第二次读取的不一致,那不还是不可重复读吗,所
以这是不可重复读的一种。

    #正确的理解
    幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果
所表征的数据状态无法支撑后续的业务操作。(第一次查询的时候,没有id3的,想要插入一条id3的
数据,结果插入不了,报错了,说已经存在了# 事务2已经插入了id为3的数据)

    更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此
记录已存在,无法插入,此时就发生了幻读。
    
    
# 每个隔离级别解决了什么问题
	-Read uncommitted(读未提交)-ru:存在脏读,不可重复读,幻读
    -Read committed(读已提交)-rc:解决了脏读,但是存在不可重复读和幻读
    -Repeatable read(可重复读取)-rr:解决了脏读,不可重复读问题,存在幻读问题
    -Serializable(串行化):解决了脏读,不可重复读和幻读问题,牺牲了效率,效率就太低了(但是
redis就是串行的,redis是纯内存操作)
    
    
# mysql 5.7 默认隔离级别
	-REPEATABLE READ(可重复读Repeatable Read)-RR-解决了脏读和不可重复读,存在幻读
    
# Oracle仅支持两种隔离级别:
	Read Committed:读已提交,存在不可重复读问题和幻读
    Serializable:串行化,默认基本为RC  
    

# redis为什么速度快?
redis是串行的(单线程操作),但是为什么redis的速度快?redis是纯内存操作
-速度快:
    -1 纯内存存储(核心)
    -2 使用了IO多路复用的网络模型
    -3 数据操作是单线程,避免了线程间切换,而且没有锁,也不会数据错乱
-支持持久化
-纯内存,可以存到硬盘上,防止数据丢失
-redis又被称之为 缓存数据库

#### ---------->redis的慢查询问题
    

MySQL5.7之后的默认的事务隔离级别是可重复读(Repeatable Read)。

在可重复读隔离级别下,MySQL 通过 MVCC(Multi-Version Concurrency Control)机制实现并发控制。
具体来说,MySQL 在每个数据行上都会存储一个版本号,用于记录该数据行的修改历史。在执行事务期间,MySQL 会根据事务的启动时间创建一个快照,用于记录当前事务读取的数据版本。这样,当其他事务对数据进行修改时,可重复读事务仍然可以读取到自己事务启动时的数据版本,避免了脏读和不可重复读问题。

在可重复读隔离级别下,MySQL 会对每个读取操作使用共享锁(Shared Lock),并在事务结束时释放锁。对于写入操作,MySQL 会对每个写入操作使用排他锁(Exclusive Lock),并在事务结束时释放锁。

由于可重复读隔离级别是通过 MVCC 机制实现的,所以可以避免脏读和不可重复读问题。但是,仍然可能出现幻读问题,即一个事务在读取数据时,发现该数据行已经被其他事务插入或删除。

为了避免幻读问题,MySQL 提供了一种特殊的锁定机制,称为 Next-Key Locking。该机制使用了行级锁定和间隙锁定,可以保证在同一事务中多次读取同一范围内的数据时,结果是一致的。同时,Next-Key Locking 也可以避免幻读问题。

总之,MySQL 默认的事务隔离级别是可重复读。在可重复读隔离级别下,MySQL 通过 MVCC 机制实现并发控制,使用共享锁和排他锁来实现读写操作的并发性。虽然可以避免脏读和不可重复读问题,但是仍然可能出现幻读问题。为了避免幻读问题,MySQL 提供了 Next-Key Locking 锁定机制。

5 什么是qps,tps,并发量,pv,uv

https://www.cnblogs.com/liuqingzheng/articles/16207660.html
    
# qps:
Queries Per Second,每秒查询率,一台服务器每秒能够响应的查询次数,每秒的响应请求数
    
是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
它是衡量系统吞吐量的一个常用指标,即服务器在一秒的时间内处理了多少个请求。
QPS 的值越大表示服务器的吞吐量越大,同时服务器的负荷往往也越高。

-如何估算自己项目的QPS?
使用日志估算即可,比如在中间件里记录访问日志,最终统计1s内有多少个访问,qps就是多大;
        
# TPS:
# transaction处理,会报;汇报
Transactions Per Second,是每秒处理的事务数,包括一条消息入和一条消息出,加上一次用户数据库访问
TPS 的过程包括:客户端请求服务端、服务端内部处理、服务端返回客户端。
例如,访问一个 Index 页面会请求服务器 3 次,包括一次 html,一次 css,一次 js,那么访问这一个页面就会产生一个T,产生三个Q
    
# 并发量
系统同时处理的请求或事务数,可以直接理解为:系统同时处理的请求数量
QPS = 并发量 / 平均响应时间
例如当前系统QPS为1w,每个请求的响应时间都是2s,那么并发量就是2w 
    
# PV
# view阅读,看
PV(Page View):页面访问量,即页面浏览量或点击量,用户每次刷新即被计算一次。可以统计服务一天的访问日志得到。
    
# UV
# unique 独一无二的,独特的;非常特别的
UV(Unique Visitor):独立访客,统计1天内访问某站点的用户数。可以统计服务一天的访问日志并根据用户的唯一标识去重得到。

# DAU(日活)
DAU(Daily Active User),日活跃用户数量。常用于反映网站、app、网游的运营情况。
DAU通常统计一日(统计日)之内,登录或使用了某个产品的用户数(去除重复登录的用户),与UV概念相似

# MAU(月活)
MAU(Month Active User):月活跃用户数量,指网站、app等去重后的月活跃用户数量

http://www.gxitsky.com/2022/02/09/ArchitectureDesign-06-qps-tps-pv/

6 什么是接口幂等性问题,如何解决?

-幂等:幂等(idempotent、idempotence)是一个数学与计算机学概念
-一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同
-接口幂等性:无论调用多少次,产生的效果是一样的
-get 获取数据天然幂等
-put 修改数据天然幂等
	-'修改库存(数字加减):不幂等'
-delete 删除 天然幂等
-post 新增数据,会'出现不幂等'的情况,要把它做成幂等性的
    	
# 解决方案:
重点就是:做一个唯一标识,可以获取到原来的数据跟版本号,这样就可以区分开每次操作,是不是都是第一次的请求操作。

# token机制
    1、下单接口的前一个接口,只要一访问,后端生成一个随机字符串,存到redis中,把随机字符串返回给前端
    2、然后调用业务接口请求时,把随机字符串携带过去,一般放在请求头中。
    3、服务器判断随机字符串是否存在redis中,存在表示第一次请求,然后 redis 删除随机字符串,继续执行业务。
    4、如果判断随机字符串不存在redis中,就表示是重复操作,直接返回重复标记给client(客户),这样就保证了业务代码,不被重复执行
    
# 乐观锁机制--->更新的场景中
update t_goods(表名) set count = count -1 , version = version + 1 where good_id=2 and version = 1
	根据version版本,也就是在操作库存前先获取当前商品的version版本号,然后操作的时候带上此version号。我们梳理下,我们第一次操作库存时,得到version为1,调用库存服务version变成了2;但返回给订单服务出现了问题,订单服务又一次发起调用库存服务,当订单服务传入的version还是1,再执行上面的sql语句时,就不会执行;因为version已经变为2了,where条件就不成立。这样就保证了不管调用几次,只会真正的处理一次。
'不懂乐观锁的情况下就不要说'


# 唯一主键
这个机制是利用了数据库的主键唯一约束的特性,解决了在 insert 场景时幂等问题。但主键的要求不是自增的主键,这样就需要业务生成全局唯一的主键
    
# 唯一ID(unique)
调用接口时,生成一个唯一id,redis 将数据保存到集合中(去重),存在即处理过
    
# 防重表
使用订单号 orderNo 做为去重表的唯一索引,把唯一索引插入去重表,再进行业务操作,且他们在同一个事务中。这个保证了重复请求时,因为去重表有唯一约束,导致请求失败,避免了幂等问题。这里要注意的是,去重表和业务表应该在同一库中,这样就保证了在同一个事务,即使业务操作失败了,也会把去重表的数据回滚。这个很好的保证了数据一致性。
    
# 前端:
按钮只能点击一次

7 什么是gil锁,有什么作用

# 什么是GIL锁
1 GIL:Global Interpreter Lock又称全局解释器锁。本质就是一个互斥锁
2 保证了cpython解释器一个进程中的每个线程必须获得这把锁才能执行,不获得不能执行
3 使得在同一进程内任何时刻仅有一个线程在执行
4 gil锁只针对于cpython解释器。解释器有JPython、PyPy、CPython、IPython 
"""只需要记住:同一时刻多个线程只能有一个线程在运行,其他线程都处于等待状态"""


# 为什么要使用Cpython解释器?
	Python是一门解释器语言,代码想运行,必须通过解释器执行。python的底层代码是用c语言写的,而Cpython解释器也是用C语言开发的,它的兼容性最好。CPython 的生态系统最为丰富,Python 的第三方库和框架大多数都是以 CPython 为基础开发的。
    
    
# 为什么要有gil锁?
	-python是动态强类型语言,因为有垃圾回收机制,如果同一个进程下有多个线程同时在执行,垃圾回收是垃圾回收线程【同一个进程下变量是共享的】,该线程做垃圾回收时,如果其他线程在运行,就可能会出并发安全的问题【数据安全的问题】,由于当时,只有单核cpu【即便开启多线程,同一时刻,也只有一个线程在运行】,作者就强行做了一个GIL锁,保证在一个进程内,同一时刻只有一个线程执行,目的是为了防止垃圾回收线程做垃圾回收时,出现数据紊乱问题,所以加了gil锁。   ---> '8 垃圾回收'
	-垃圾回收是垃圾回收线程,它在执行的时候,其他线程是不能执行的,而限于当时的条件,只有单核cpu,所以作者直接做了个GIL锁,保证一个进程内同一时刻只有一个线程在执行

优缺点

# 优点:
同一进程下,线程之间是串行执行,不会出现抢夺别的线程的资源或数据的问题。
# 缺点:
想要利用多核的优势,只能开启多个线程,这样比较浪费资源。

8 python的垃圾回收机制是什么样的

python中一切皆对象

-https://www.jianshu.com/p/52ab26890114
-https://blog.csdn.net/naer_chongya/article/details/131211682
-什么是垃圾回收?
    -编程语言在运行过程中会定义变量--->申请了内存空间--->后期变量不用了--->这个内存空间应该释放掉
    -有些编程语言,这个操作,需要程序员自己做(C语言)
    -像java,python,go这些语言,都自带垃圾回收机制,可以自动回收内存空间,gc机制(Garbage Collction)
        
# 不同语言垃圾回收的方式是不一样的,python是使用如下三种方式做gc,以引用计数为主,标记-清除和分代回收两个算法为辅
(1)引用计数算法(reference counting)-每个对象都有一个引用次数的计数属性,如果对象被引用了,那这个数就会 加1,如果引用被删除,引用计数就会 减1,那么当该对象的引用计数为0时,就说明这个对象没有被使用,垃圾回收线程就会把它回收掉,释放内存
    -有问题:循环引用问题--->回收不了
    
(2) 标记-清除算法(Mark and Sweep);
    -是为了解决引用计数无法回收循环引用的问题
    -对象之间通过引用连在一起,节点就是各个对象,从一个根对象向下找对象,可以到达的标记为活动对象,不能到达的是非活动对象,而非活动对象就是需要被清除的

(3) 分代回收算法(Generational garbage collector)
    -分代回收是解决垃圾回收效率问题
    -算法原理是Python把对象的生命周期分为三代,分别是第0代、第1代、第2代。每一代使用双向链表来标记这些对象。每一代链表都有总数阈值,当达到阈值的时候就会出发GC回收,将需要清除的清除掉,不需要清除的移到下一代。以此类推,第2代中的对象存活周期最长的对象

            
# 总结:
python垃圾回收最核心是:引用计数---->标记清除解决引用计数的循环引用问题--->分代回收解决垃圾回收的效率问题
        

循环引用问题
标记清除问题

-python使用引用计数为主,标记清楚和隔代回收为辅来进行内存管理。所有python脚本中创建的对象,都会配备一个引用计数,来记录有多少个指针来指向它。当对象的引用技术为0时,会自动释放其所占用的内存。
假设有2个python线程同时引用一个数据(a=100,引用计数为1),
2个线程都会去操作该数据,由于多线程对同一个资源的竞争,实际上引用计数为3,
但是由于没有GIL锁,导致引用计数只增加1(引用计数为2)
这造成的后果是,当第1个线程结束时,会把引用计数减少为1;当第2个线程结束时,会把引用计数减少为0;
当下一个线程再次视图访问这个数据时,就无法找到有效的内存了

9 解释为什么计算密集型用多进程,io密集型用多线程

计算密集型用多进程,io密集型用多线程--->只针对于cpython解释器(其他语言,都开多线程即可,能够利用多核优势)
	-计算是消耗cpu的:代码执行,算术,for都是计算
    -io不消耗cpu:读写文件,读写数据库,网络操作都是io
	-如果遇到io,该线程会释放cpu的执行权限,cpu转而去执行别的线程

# 由于python有gil锁,开启多条线程,同一时刻,只能有一条线程在执行
# 计算密集型
    -如果是计算密集型,开了多线程,同一时刻,只有一个线程在执行
    -多核cpu,就会浪费多核优势
    -如果是计算密集型,我们希望,多个核(cpu),都干活,同一个进程下绕不过gil锁
    -所以我们开启多进程,gil锁只能锁住某个进程中得线程,开启多个进程,就能利用多核优势
    
# io密集型 
    -io密集型--->只要遇到io,cpu就会释放执行权限
    -进程内开了多个io线程,线程多半都在等待,开启多进程是不能提高效率的,反而开启进程很耗费资源,所以使用多线程即可

10 为什么有了gil锁还要互斥锁

# 1 为什么有了gil锁还要互斥锁
	-并发和并行
    -不能控制什么时候释放gil锁
    -GIL锁太大了,不能单独锁住共享资源
    
    
    # 1 GIL 本身就是大的互斥锁
    # 2 同一个进程下,资源是共享的---->多条线程可以操作同一个变量
    # 3 多个线程操作同一个变量,就会出现数据安全问题
   	# 4 临界区:指一段代码或一段程序片段,需要在同一时间只能被一个线程执行--->多个线程操作临界区,会出现并发安全问题
 	# 错误的理解
    	-有了gil锁,同一时刻,只有一个线程执行,临界区代码,不也只有一个线程在执行吗?只有一个线程在执行,临界区就是安全的,不会出现数据错乱,所以没有必要再加互斥锁了
    # 正确的解释:
    	gil锁释放不是我们控制的,比如在执行临界区中间,释放了,就会数据错乱问题
    # 什么时候会释放gil锁?
    	-1 线程遇到io
        -2 时间片轮转,时间片到了,就会自动切换


是因为GIL锁的释放不是我们能够控制的,(线程遇到io,时间片轮转,时间片到了,就会自动释放),如果我们在执行临界区中间,gil锁被自动释放了,就会产生数据错乱问题。


多个线程的运行,会抢GIL锁,抢到锁的线程(假设该线程是线程1)会执行自己的代码。遇到阻塞,CPU会切换到下一个线程,此时GIL锁会自动释放。所以任务中间有阻塞的情况,GIL锁并不能够保证会一次性执行完内部的代码。所以想要保证代码层面的数据安全,就需要自己加锁。

多个线程的运行,会抢GIL锁,抢到锁的线程(假设该线程是线程1)会执行自己的代码。第一行代码就是上锁,'互斥锁'。之后的代码遇到阻塞,CPU会切换到下一个线程,此时GIL锁会自动释放,但是'互斥锁'还在,不会执行其他线程。只有这个线程执行结束,互斥锁释放,才会执行下一个进程。所以想要保证代码层面的数据安全,就需要自己加锁

互斥锁能够防止多个线程同时修改共享数据导致的数据冲突问题。

# 拓展
	不同进程下,资源是隔离的
    进程间通信:IPC 机制
    	-消息队列:可以java进程跟python进程之间通信
        -Queue,可以两个python间进程之间通信

小案例

    进程中有个变量a=0
    
    临界区:a+=1
    
    线程1要计算: a+=1  
    	1 线程1 拿到gil  
        2 读取a=0
        3 假设时间片到了,释放gil,释放cpu
      	4 等待下次被调度执行
        10 轮到它了,获取gil锁
        11 继续往下执行:计算a+1
        12 把结果赋值给a ,a=1
    	13 释放gil锁
     线程2要计算: a+=1 
        5 线程2获得了gil锁
        6 读取a=0
        7 计算a+1
        8 把结果赋值给a ,a=1
        9 释放gil锁
    
    # 互斥锁保证数据安全
    a=0
    线程1要计算: a+=1  
    	1 线程1 拿到gil  
        # 加锁
        2 读取a=0
        3 假设时间片到了,释放gil,释放cpu
      	4 等待下次被调度执行
        
        7 轮到它了,获取gil锁
        8 继续往下执行:计算a+1
        9 把结果赋值给a ,a=1
    	10 释放gil锁
     线程2要计算: a+=1 
        5 线程2获得了gil锁
        #获取锁,获取不到
        6 释放gil锁
        11 获得gil锁
        
        #加锁
        12 读取a=0
        13 计算a+1
        14 把结果赋值给a ,a=1
        15 释放锁
        16 释放gil锁
    
    # gil锁并不锁住临界区,临界区需要我们自己用互斥锁加锁
    

11 进程,线程和协程

  • 代码如何实现
  • 你在哪里用过
# 进程间通信需要使用管道,消息队列实现
# 线程间通信用什么?
# 协程会出现并发安全的问题吗?

###### 进程线程协程概念
进程:是资源分配的最小单位,一个应用程序运行起来,至少有一个进程,进程管理器中就可以看到一个个的进程
线程:是cpu调度,执行的最小单位,一个进程下至少有一个线程
协程:单线程下的并发,程序层面控制的任务切换(程序层面耗费的资源,要比操作系统层面耗费的资源小,所以使用协程的速度要比切换进程要快一些)

#####如果具体使用
# 开启多进程两种方式
-1 写一个类,继承Process,重写类的run方法--->实例化得到对象,对象.start 开启了进程
-2 通过Process类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程

# 开启线程两种方式
-1 写一个类,继承Thread,重写类的run方法--->实例化得到对象,对象.start 开启了进程
-2 通过Thread类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程

# 开启协程
-1 早期之前:借助于第三方gevent,基于greelet写的
-2 定义协程函数: asyncawait 关键字,不借助于第三方,开启协程 asyncio 包
-必须写在一个函数前, async def task()---->这个函数执行的结果是协程函数
-await 只要是io操作的代码,前面必须加 await
async def task1():
    a+=1
    time.sleep()
    a+=10

async def task2():
    a+=2
    await 遇到io
    a+=20    

# 什么场景用过
# 多进程
    -我一般遇到计算密集型的操作,我会开多进程,
    
    

    
    
    
# 多线程
    -io密集型的操作,我一般开多线程
    -闲来无事,爬别人数据,喜欢开多进程,爬虫io居多
-程序中,异步做一件事情,也可以开多线程
    比如一个视图函数,异步的吧数据写的文件中
    异步的发送钉钉通知
    异步的发送邮件
    -但实际上,在项目中,不需要我们开启进程线程,可以借助于第三方的框架比如celery就可以做异步的操作
    而celery的worker,就是进程线程架构
    -django框架,是支持并发,我们没有开启多进程和多线程,但是符合uwsgi的web服务器在进入djagno框架之前,开启了进程和线程来执行视图函数

自己总结

# 进程
	1.进程是正在运行的程序,是动态的。进程的目的是为了表达特定的状态。
    2.一个py文件就是一个进程,是主线程。文件中再开一个线程就是子线程。
    3.消耗的资源比较多。
    4.进程是资源单位,表示一块内存空间。是资源分配的基本单位。
    5.不同进程之间的数据是隔离的。
    
from multiprocessing import Process
import time

def task(name):
    print('task is running')
    time.sleep(3)
    print('task is over',name)

if __name__ == '__main__':
    p1 = Process(target=task, args=('jason',))
    p1.start()
    print('主')

    
from multiprocessing import Process
class MyProcess(Process):  # 继承Process类,自己创建类
    def __init__(self, name, age):  # 传参数需要用__init__方法
        super().__init__()  # 一点要放在前面
        self.name = name
        self.age = age
        
    def run(self):  # 必须写run()方法
        print('run is running')
        time.sleep(3)
        print('run is over', self.name, self.age)

if __name__ == '__main__':
    obj = MyProcess('jason', 123)  
    obj.start()
    print('主')
    
# 线程
	1.线程是实际做事的人。
    2.一个进程中可以开多个线程,但至少有一个线程。如果一个进程中只有一个线程,就叫主线程,其他叫子线程。
    3.消耗的资源次之。
    4.线程是执行单位,表示真正的代码执行。是CPU执行的最小单位。
    5.同一个进程下的多个线程数据是共享的。
    
from threading import Thread
def write():
    with open('a.txt', 'w', encoding='utf-8') as f:
        f.write('hello')

if __name__ == '__main__':  
    t = Thread(target=write, )
    t.start()  # 异步操作
    print(123)
第二种方法:
class MyThread(Thread):
    def run(self):
        print('run is running')

obj = MyThread()
obj.start()
print('主线程')

# 协程
	1.协程是程序员编写代码,欺骗CPU,让CPU觉得我们的代码中没有IO操作,让CPU跳来跳去(跳过IO),让CPU一直执行本次代码。
    2.进程和线程是由操作系统调度的。而协程是程序员自己写的代码。
	3.消耗的资源最小。进程 >>> 线程 >>> 协程(程序员调度的)
    4.协程是单线程下的并发。
    5.需要安装第三方模块gevent.
    
    
    实际编码中没有用过

12 什么是鸭子类型

# 鸭子类型是python语言面向对象中的一个概念
# 面向对象三大特性
	-继承
    -封装
    -多态
# 多态和多态性?
	# 多态:同一类事物的多种形态
    -现实生活中
    	-水:冰,水,水蒸气
        -动物:人,狗,猫,猪
	-程序中
    	-Animal类:子类人,子类狗,子类猫
     # 多态性:不考虑对象具体类型的情况下使用对象
     -程序中
        -len()内置函数--->传参数:字符串对象,列表对象,字典对象
        -len(字典/字符串)
     -为什么能这样用?就是因为多态的存在
    	字符串,列表,字典---->属于同一类事物--->有长度的这一类事物
        
        
# 鸭子类型
	-走路像鸭子,说话像鸭子,我们就可以叫它叫鸭子
    -解释:鸭子类型是python面向对象中描述接口的一个概念,区分与其他编程语言,
    	比如java:实现类型,必须显示的继承这个类
        而python:实现类型,遵循鸭子类型,不需要显示的继承一个类,只要类中有对应的属性跟方法,我们就称这几个类的对象为同一种类型

自己总结

鸭子类型是
我们不需要继承共同的父类,只要我跟这个类有相同的方法和属性,就可以说我跟这个类是同一个类。
# python中用鸭子类型实现多态。在不考虑对象具体的类型下,直接调用对应的方法或者属性。如果这个对象有这个方法,程序就正常运行,如果没有这个方法,程序就报错。
python中的len方法就是一个多态实例,大多数的数据类型都可以使用len方法。


# 为什么要使用鸭子类型
python中的参数是无类型的,如果这个参数有这个方法,程序就正常运行,如果没有这个方法,程序就报错。
python中的len方法就是一个多态实例,大多数的数据类型都可以使用len方法。

多态性指的是可以在不用考虑对象具体类型的情况下而直接使用对象,这就需要在设计时,把对象的使用方法统一成一种:例如cat、dog、pig都是动物,但凡是动物肯定有talk方法,于是我们可以不用考虑它们三者的具体是什么类型的动物,而直接使用

抽象类
综上我们得知,多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象,可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名

'''
	需要掌握:
	1.如何限制抽象类
	2.抽象类的特点:只能被继承了,不能被实例化(面试题)
	3.抽象类可以限制子类里面必须要有父类定义的方法,
'''
import abc  # abstract class  抽象类

'''抽象类的特点:只能被继承了,不能被实例化'''
class Animal(metaclass=abc.ABCMeta):  # 这个类就变成了抽象类
    @abc.abstractmethod
    def speak(self):  # 类里面的方法就变成了抽象方法
        pass  

https://www.cnblogs.com/zjyao/p/17255594.html

13 什么是猴子补丁,有什么用途

1 什么是猴子补丁,有什么用途

# 解释
	-Python猴子补丁(Monkey Patch)是一种在运行时动态修改代码的技术。通在不修改源代码的情况下,改变代码的执行方式或增加功能
	-Monkey Patching是在 运行时(run time) 动态替换属性(attributes)或方法
	-Python的类是可变的(mutable),方法(methods)只是类的属性(attributes);这允许我们在 运行时(run time) 修改其行为。这被称为猴子补丁(Monkey Patching), 它指的是偷偷地更改代码。
# 哪里用过
	# 1 位置
	import pymysql
    pymysql.instal_as_mysqldb()  # 动态的替换 原来使用 mysqldb链接数据库的类 conn
    # 2 位置
    gevent.monkey.patch_all() # 动态的替换 原来阻塞的io方法 ,全都替换成自己写的,不阻塞的 time,socket...
    # 3 这种情况  --->json --->内置模板--->没关注性能高低
    	-如果有性能高的json转换模块--->想替换--->如果都换,每个地方都要改,太麻烦了
        -借助于猴子补丁--->程序运行一开始,动态的把json替换成ujson
        json.loads()
    
    
# c的功能(一切皆对象)
拥有在模块运行时替换的功能, 例如: 一个函数对象赋值给另外一个函数对象(把函数原本的执行的功能给替换了)

    
猴子补丁(Monkey Patch)是一种编程概念,它指的是在运行时动态地修改或扩展现有代码的行为,通常通过修改或替换已存在的代码或函数来实现。猴子补丁的名称源自于类似于猴子随意修改或扩展其他物体的行为。

猴子补丁常被用于动态地修改第三方库或框架的行为,以满足特定需求或修复问题。通过猴子补丁,开发者可以在不修改原始代码的情况下对其进行修改或扩展,使其具有更灵活、适应性更强的特性。

猴子补丁的使用场景包括但不限于以下几个方面:

修复bug:当第三方库或框架存在bug时,可以使用猴子补丁动态地修改相关代码,以避免等待官方修复或维护的延迟。
功能扩展:通过猴子补丁,可以为现有代码添加新的功能或修改现有功能,以满足自己的需求。
应急措施:在应对紧急问题或临时需求时,使用猴子补丁可以快速地修改代码,而不需要等待正式的发布或更新。

使用场景:pymysql
跟装饰器实现代码的方式是不一样的

14 什么是反射,python中如何使用反射

# 反射就是通过字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员
# 反射:是程序在运行过程中通过字符串来操作对象的属性和方法


# python 提供了一些反射方法
-getattr:获取对象属性,当获取的属性不存在的时候,会报错。
如果给了第三个参数,当属性不存在的时候,第三个参数就是默认值,但不会改变对象字典
-setattr:给对象增加属性, 属性名不存在,增加属性。存在,修改。
-hasattr:判断某个函数或者变量是否存在
-delattr:删除模块中某个变量或者函数

# 反射在Python中的应用场景非常广泛,例如:
动态加载模块和类:使用反射可以在运行时动态加载模块和类,以便于程序更加灵活和可扩展。
动态修改对象属性和方法:使用反射可以在运行时动态修改对象的属性和方法,以便于程序更加灵活。
实现插件系统:使用反射可以实现插件系统,允许程序在运行时动态加载和卸载插件。
实现ORM框架:使用反射可以实现ORM框架,允许程序在运行时动态地将Python对象映射到数据库中的表格。

# 应用一:
视图类的源码中,先判断浏览器的请求是不是8中请求中的一种,然后利用反射,调用类中与请求方式同名的方法名。
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
View的dispatch和APIView的dispatch方法都是这么写的
# 应用二:
drf封装了新的request为什么还能使用老的request的属性和方法,通过反射获取老的request中的属性
return getattr(self._request, attr)
# 应用三:
反序列化校验中局部钩子中需要重写validate_字段方法,内部就是使用getattr让序列化对象调用局部钩子,在字段的校验通过后直接加括号执行自己的序列化类中的这个validate_字段名方法,并把经过校验的字段传入进去。
validate_method = getattr(self, 'validate_' + field.field_name, None)
# 应用四:反序列化的保存
要重写update方法,这个里面就可以直接使用setattr,校验完成的字段,需要写入表中。
def update(self, instance, validated_data):
  for item in validated_data:  # {"name":"jinping","price":55}
      setattr(instance, item, validated_data[item])
  instance.save()
  return instance

15 http和https的区别

# https://zhuanlan.zhihu.com/p/561907474

# 名字
# HTTP协议,英文全称是Hyper Text Transfer Protocol,是超文本传输协议
# HTTPS协议,英文全称Hyper Text Transfer Protocol over SecureSocket Layer,超文本传输安全协议
# 默认端口
# HTTPS 和 HTTP 的默认端口不同(443和80)
# 核心区别
# HTTPS在 HTTP与 TCP 之间加入了一个加密/身份验证层,提供了身份验证与加密通讯
HTTP+ SSL / TLS,也就是在 http上又加了一层处理加密信息的模块,比 http安全,可防止数据在传输过程中被窃取、改变,确保数据的完整性
https=http+ssl


# HTTP 由于是明文传输,主要存在三大风险:窃听风险、篡改风险、冒充风险
# http和https的区别
    https更加安全,密文传输数据
    http不安全,明文传输数据
    https协议监听的默认端口是:443
    
    
HTTP(Hypertext Transfer Protocol)和HTTPS(HTTP Secure)是两种常见的网络传输协议,用于在客户端和服务器之间传输数据。它们的主要区别包括以下几个方面:

1. 安全性:
   - HTTP是明文传输协议,数据以纯文本形式传输,不经过加密。这就意味着敏感信息(如登录凭证、支付信息等)在传输过程中容易被截获和窃取。
   - HTTPS通过使用SSL(Secure Sockets Layer)或TLS(Transport Layer Security)协议,在HTTP的基础上增加了数据加密和身份验证的功能。客户端和服务器之间的通信通过加密进行,提供了更高的安全性,使得数据难以被窃取或篡改。

2. 默认端口:
   - HTTP使用默认端口80进行通信。
   - HTTPS使用默认端口443进行通信。

3. 证书:
   - HTTPS需要使用数字证书,用于验证服务器的身份。证书由受信任的第三方机构颁发,用于加密通信和证明服务器身份的真实性。
   - HTTP不需要证书,因为没有加密和身份验证的要求。

4. 运行成本:
   - HTTPS的配置和维护相对复杂,涉及证书的申请、安装、更新等过程。此外,加密通信的过程会稍微增加服务器的负担,有可能导致一定的性能损失。
   - HTTP不需要配置证书,相对较为简单,且通信过程无额外的加密处理,因此运行成本较低。

总的来说,HTTP适用于对安全性要求较低的场景,如普通网页浏览,因为它的通信速度较快、部署简单;而HTTPS适用于对数据安全要求较高的场景,如在线支付、敏感数据传输,因为它提供了加密和身份验证的保护。随着互联网安全意识的提高,越来越多的网站和应用程序选择使用HTTPS来确保数据的安全传输。

16 从浏览器输入一个地址,到看到页面信息,经历的过程(经典)

1 从浏览器输入一个地址,到看到页面信息,经历的过程
	1 在浏览器中输入的是:【地址,不带端口,默认是80端口】域名--->要做域名解析(DNS解析)--->把域名解析成ip地址+端口的形式---dns解析--->(浏览器缓存(一旦之前访问过这个地址,浏览器会自动加缓存,再访问-->直接从缓存中获取-->F5强制刷新或者浏览器有无痕)-->dns解析:先解析本地host文件,上一级递归解析服务 ,13台根dns)-->如果解析不到--->页面就会报错
    2 解析完后,向解析出的域名和端口,准备建立TCP连接,可靠链接,tcp处于传输层,进行3次握手,链接简历	
    3 像解析出的地址发送http的get请求--->http协议又有很多东西,暂时先不说
    4 如果后端服务是使用nginx转发,做反向代理服务器,nginx把http请求转发给web框架(django,flask)-->django请求生命周期--->分离项目和混合项目
    5 后端服务器以http响应的形式返回给客户端浏览器
    6 客户端浏览器把http响应体的内容展示在浏览器上,但是http响应还有:状态码,响应头。。
    7 四次挥手断开tcp连接--->这个链接不一定会断开--->http协议版本
    # https://blog.csdn.net/m0_52165864/article/details/126313277

1.首先,在浏览器地址栏种输入url地址(这个地址叫域名)

2.浏览器先查看浏览器缓存、路由器缓存、如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳转到第三步。


3.在发送http请求前,需要域名解析(DNS解析),解析获取相应的IP地址。
4.浏览器向服务器发起tcp连接,与浏览器tcp三次握手。
5.握手成功后,浏览器向服务器发送http请求,请求数据包。
6.服务器处理收到的请求,将数据返回至浏览器。
7.浏览器收到HTTP响应。
8.读取页面内容,浏览器渲染,解析html源码。
9.生成DOM树,解析css样式,js交互。
10客户端和服务器交互
11.ajax查询

多路复用

17 左连接,右连接,内连接,全连接:MySQL不能直接支持

-数据通常不在同一张表中,这就涉及到连表操作,而表间连接方式有很多
-内连接:把两张表中共有的数据,连接到一起
-左连接:以左表为基准,把左表所有数据都展示,有可能右表没有,用空补齐
-右连接:以右表为基准,把右表所有数据都展示,有可能左表没有,用空补齐
-全连接:以左右两表数据作为基准,左右两表数据都展示,有可能左或表没有,用空补齐


# 笛卡尔积
	select * from book,publish where book.publish_id=publish.id;
        第一个表
    id  name  age   publish_id
    1   xx    11      1
    2    yy    12     1
    
    第二个表
    id  name  age
    1   xx    11
    2    yy    12
    
    1   xx    11      1  1   xx    11
     1   xx    11    2    yy    12
    2    yy    12     1  1   xx    11
    2    yy    12     1
# 左右键连接
2 左连接,右连接,内连接,全连接:MySQL不能直接支持
	select * from  book left join publish on book.publish_id=publish.id;
    book.publish_id=99
    publish.id没有99

# 左连接:left join
以左表为基准,查询左表中所有的数据,右表没有的数据用NULL填充

# 右连接------>以右表为基准,查询右表中所有的数据,左表没有的数据用NULL填充right join

# 内连接:要两张表的共有数据inner join


# 全连接------>连接两个SQL语句的结果。union,不支持


建表的时候的强限制
逻辑外键

18 union和union all的区别?

-作用:select 出来结果,union,union all都是对结果进行合并,并对结果进行排序,求并集,字段类型和列都要一致才能用

-union 会去除重复的数据,去重和排序操作
-union all 不会去除重复的数据,不会去重和排序



select name,id form user;
id  name
1   lqz
2   zs
select name,id form book;
id  name
1   lqz
2   西游记


select name,id form user union all select name,id form book;
id  name
1   lqz
1   lqz
2   zs
2   西游记



select name,id form user union all select name,id form book;
id  name
1   lqz
2   zs
2   西游记

一、区别1:取结果的并集

1、union: 对两个结果集进行并集操作, 不包括重复行,相当于distinct, 同时进行默认规则的排序;

2、union all: 对两个结果集进行并集操作, 包括重复行, 即所有的结果全部显示, 不管是不是重复;

二、区别2:获取结果后的操作

1、union: 会对获取的结果进行排序操作

2、union all: 不会对获取的结果进行排序操作

三、区别31、union看到结果中ID=3的只有一条

select * from student2 where id < 4
union
select * from student2 where id > 2 and id < 6

2、union all 结果中ID=3的结果有两个

select * from student2 where id < 4
union all
select * from student2 where id > 2 and id < 6

四、总结
union all只是合并查询结果,并不会进行去重和排序操作,在没有去重的前提下,使用union all的执行效率要比union高

19 一句sql查询慢,如何排查优化

# 1 orm  原生sql
# 2 接口响应速度慢--->定位到是sql问题
	-索引优化:分析sql有没有走索引---->EXPLAIN SELECT * FROM orders WHERE name = 123;
	在表中添加合适的索引可以显著提升查询效率。可以通过 EXPLAIN 命令来查看查询计划,判断是否使用了索引,如果没有使用索引,就需要考虑添加索引
    -避免全表扫描:避免在大表上进行全表扫描,可以通过限制查询条件或者使用分页查询来解决
        -使用分页,避免全表搜索,扫码
    -优化查询语句:
        EXPLAIN SELECT * FROM orders WHERE name like 123%;
    	-优化sql,不要写 前面的模糊查询
        -尽量使用主键查询,尽量不模糊匹配
    -数据库表结构优化 
    	-大表拆成小表
        
    -做数据库读写分离
    -分库分表

自己总结

针对慢查询的 SQL 进行优化可以提高数据库查询性能和响应时间。以下是一些常见的优化技巧:
explain analyze

分析查询计划:使用 EXPLAIN 或 EXPLAIN ANALYZE 命令来查看查询计划,确定查询是否使用了正确的索引或是否存在性能瓶颈。

优化查询条件:避免在查询条件中使用 LIKE 或不必要的正则表达式,使用简单的比较运算符和相等条件可以提高查询性能。

使用索引:确保表中的列都有适当的索引,尤其是在查询中频繁使用的列。可以通过 CREATE INDEX 命令创建索引,也可以通过分析查询计划来确定是否使用了正确的索引。

避免全表扫描:避免对整个表进行扫描,可以通过优化查询条件或者创建索引来提高查询性能。

优化 JOIN 操作:在使用 JOIN 操作时,确保连接条件是正确的,并尽可能使用 INNER JOIN 而不是 OUTER JOIN,因为 INNER JOIN 的性能通常更好。

减少返回数据量:尽可能只返回需要的数据,可以通过使用 SELECT 子句来指定需要的列,并使用 LIMIT 限制返回的行数。

优化服务器设置:调整服务器的缓冲区、连接数、并发数等设置可以提高数据库性能。

使用缓存:如果查询结果不经常变化,可以考虑使用缓存来避免重复查询,提高响应速度。

20 tcp 三次握手和四次挥手

# tcp协议--->处于osi7层协议的传输层,可靠连接,使用三次握手,四次挥手保证了可靠连接,数据不会丢失
    
-SYN:SYN=1 表示要建立连接
-ACK:ACK=1 表示我收到了,允许
-seq:随机数,建立连接无论客户端还是服务端要建立连接就要要携带
-ack:回应请求就要加1返回
-FIN:表示断开连接
-三次握手:
    -第一次:喂(SYN=1),我是lqz(seq=随机数) 
    客户端:SYN_SEND状态
    服务端:没收到:listen 状态,收到了是:SYN_RCVD状态
    -第二次:收到(ACK=1),lqz啊(ack=随机数+1),(SYN=1),我是刘亦菲(seq=随机数1)
    服务端:SYN_RCVD状态
    客户端:没收到服务端返回的第二次:SYN_SEND状态,一旦收到就是established
    -第三次:收到(ACK=1),刘亦菲你好(ack=随机数1+1)
    客户端:连接建好的状态 established
    服务端:收到后,处于established

-大白话:三次握手
    第一次:客户端向服务端发送建立连接请求,【携带一个随机数】(SYN=1,seq=随机数)
    第二次:服务端回应客户端的建立连接请求(ACK=1,ack=随机数+1),服务端发送建立连接请求(SYN=1,seq=另一个随机数)
    第三次:客户端回应服务端的建立连接请求(ACK=1,ack=另一个随机数+1)

# 四次挥手:
	第一次:客户端向服务端发起断开连接的请求(FIN=1,seq=随机数)
    第二次:服务端收到后,回复这个请求(ACK=1,ack=随机数+1)
    
   	第三次:服务端向客户端发起断开连接的请求(FIN=1,seq=另一个随机数,ACK=1,ack=随机数+1)
    第四次:客户端收到后,回复这个请求(ACK=1,ack=另一个随机数+1#建立和端开链接过程中服务端和客户端的状态
LISTEN:等待从任何远端TCP 和端口的连接请求
SYN_SENT:发送完一个连接请求后等待一个匹配的连接请求。
SYN_RECEIVED:发送连接请求并且接收到匹配的连接请求以后等待连接请求确认。
ESTABLISHED:表示一个打开的连接,接收到的数据可以被投递给用户。连接的数据传输阶段的正常状态。

FIN_WAIT_1:等待远端TCP 的连接终止请求,或者等待之前发送的连接终止请求的确认。
FIN_WAIT_2:等待远端TCP 的连接终止请求。
CLOSE_WAIT:等待本地用户的连接终止请求。
CLOSING:等待远端TCP 的连接终止请求确认。
LAST_ACK:等待先前发送给远端TCP 的连接终止请求的确认(包括它字节的连接终止请求的确认)
TIME_WAIT:等待足够的时间过去以确保远端TCP 接收到它的连接终止请求的确认。

# 洪水攻击 ddos:
    同一时间有大量的客户端请求建立连接 会导致服务端一直处于SYN_RCVD状态,服务端接收到了大量的syn请求,处于rcvd状态
# TCP三次握⼿
第一步,客户端先向服务端发送请求链接消息,第二步服务端同意,向客户端发送允许链接消息。第三步服务端接着向客户端发送请求链接的消息,第四步客户端同意,也向服务端发送允许链接消息。其中第二步和第三步可以直接合并在一起。这就是TCP的三次握手。

# 四次挥⼿
第一步,客户端先向服务端发送请求断开链接,第二步,服务端同意,向客户端发送允许断开链接消息。第三步,服务端先运行一下自己的代码,看是否还有需要向客户端发送的消息,有就发送,接着向客户端发送请求断开链接的消息,第四步客户端同意,也向服务端发送允许断开链接。其中第二步和第三步中间有服务端要思考是否有要往客户端发送的消息,然后再向客户端发送断开链接,这其中肯定有一段时间的间隔。所以断开链接的时候,第二步和第三步是不能合并再一起的。这就是TCP的四次挥手

21 osi七层协议,哪七层,每层有哪些

# osi七层:                    应用层 表示层 会话层    传输层  网络层  数据链路层  物理连接层
# 五层结构(TCP/IP五层协议):  应用层(三个合成一个)    传输层  网络层  数据链路层  物理连接层

# 应用层 表示层 会话层(应用层)
	-应用层协议:HTTP,FTP,DNS,    SNMP,SMTP,Telnet
# 表示层:https=http+ssl  的加密
# 会话层:负责建立、管理和终止表示层实体之间的会话连接


# 传输层(运输层):
    -tcp协议:三次握手四次挥手可靠链接
    -udp协议:不可靠传输
	-端口:端口协议
# 网络层
	-ip地址协议:ip,点分十进制表示
    -icmp协议:ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用
    
# 数据链路层:
	-PPP:点对点协议(Point to Point Protocol,PPP)为在点对点连接上传输多协议数据包提供了一个标准方法
    -ARP:地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议
	-mac地址:以太网协议
	-数据帧:电信号的分组方式
# 物理层:
	-ISO2110,IEEE802,IEEE802.2
    -物理介质
    -网线
七层协议有:应表会传往数物
1.物理层,二进制,比特位(bit)
	基于电信号特性发送高低电频,高电压对应二进制1,低电压对应二进制0
2.数据链路层,以太网协议,数据帧
网卡上有mac地址

	必须规定电信号多少位一组,每组什么意思,对于数据的分组是由数据链路层来进行的。
    数据链路层统一使用的是以太网协议,
    以太网协议的核心思想是使用物理地址(MAC地址)来标识网络中的设备,通过物理地址来进行网络数据传输。以太网协议的优点是传输速度快、网络设备成本低、安装和使用简单。
    一个以太网数据被称为一个数据帧,数据帧是由head+data组成
    
3.网络层,IP协议,数据包
	网络层使用的是IP协议,规定了所有接入互联网的计算机都必须由一个ip地址。
    IP地址的特征:
    	点分十进制。0.0.0.0---225.225.225.225
    	点分十六进制
    一个IP地址就可以定位世界上任何一台计算机(arp协议,可以将IP地址转为物理地址(mac地址),这样就可以直接定位一台计算机了)
    
4.传输层,端口协议,数据段
	端口协议(TCP协议和UDP协议),一个端口代表一个软件。一台计算机上的每个软件都有端口号,是动态分配的
    端口号的使用:0-1024,系统软件。1024-8000,常用软件使用。8000之后的,就是程序员使用的了。
    
5.应用层
	就是具体的应用了。这才是我们程序员接触到的。分为两种,有客户端程序,用什么协议是由程序员决定的。还有浏览器程序,这种必须要符合浏览器的协议。eg:http协议

22 tcp和udp的区别?

-tcp是面向连接的可靠协议
-udp无连接的不可靠协议

-都是处于传输层

-比如:
	udp:一些聊天,dns协议用的udp协议
	tcp:http  mysql,redis客户端服务端通信
# udp用在哪里了?

# TCP协议又称为流水协议
	TCP协议是客户端和服务端建立双向通道,并且有反馈机制,比如,客户端向服务端发送消息时,客户端会先向服务端发送请求链接,服务端同意并且返回消息后,客户端才会开始发送消息。这样就使得TCP的数据通信更加可靠。
    
# UDP协议
	UDP协议是不需要建立双向链接的,客户端向服务端发送消息,是直接发送,不会在意是否发送成功的。可靠性

23 wsgi uwsgi uWSGI,cgi,fastcgi 分别是什么?

https://blog.csdn.net/m0_58987515/article/details/125678179
# CGI:通用网关接口(Common Gateway Interface/CGI),CGI描述了服务器(nginx,apache)和请求处理程序(django,flask,springboot  web框架)之间传输数据的一种标准
    	# 所有bs架构软件都是遵循CGI协议的
    	# 一句话总结: 一个标准,定义了客户端服务器之间如何传数据
        
# FastCGI:快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本
	# FastCGI致力于减少网页服务器与CGI程序之间互动的开销,从而使服务器可以同时处理更多的网页请求
    # 常见的fastcgi服务器:Apache,Nginx,Microsoft IIS
    # CGI的升级版
    
# WSGI:Python Web Server Gateway Interface,缩写为WSGI,Python定义的Web服务器和Web应用程序或框架之间的一种通用的接口
	#一句话总结: 为Python定义的web服务器和web框架之间的接口标准
    
    
#uWSGI:符合wsgi协议的web服务器,用c写的,性能比较高,咱们通常用来部署django,flask
	#一句话总结:一个Web Server(web服务器),即一个实现了WSGI协议的服务器,处理发来的请求及返回响应。

    # xml:socket 标签

# uwsgi:uWSGI服务器实现的独有的协议,用于定义传输信息的类型,是用于前端服务器与 uwsgi 的通信规范
	# 1、一句话总结: uWSGI自有的一个协议
        uWSGI:web服务器,等同于wsgiref
        uwsgi:uWSGI自有的协议
	

# 符合WSGI协议的web服务器
    wsgiref,werkzeug(一个是符合wsgi协议的web服务器+工具包(封装了一些东西))
    uWSGI 用c语言写的,性能比较高
    gunicorn:python写的
    
  
web服务器到底是什么?服务器中间件
客户端(浏览器,app)  跟   服务端(web框架)之间的东西,服务器中间件
# nginx  apache  是一类东西,就是做请求转发,符合fastcgi服务器
# uWSGI,gunicorn 只针对于python的web框架
# tomcat,jboss,weblogic 只针对java的web框架
# php-fpm  针对于php的框架
wagiref的作用:
1.请求来了,处理请求,封装数据格式
2.响应走的时候,把数据封装成HTTP格式的数据


WSGI是一个协议,wsgi server服务端 (比如uWSGI) 要和 wsgi application应用(比如django )交互,uwsgi需要将过来的请求转给django 处理,那么uWSGI 和 django的交互和调用就需要一个统一的规范,这个规范就是WSGI协议(Web Server Gateway Interface)

                          
uWSGI是一个Web服务器,包含wsgiref服务器和uwsgi服务器。它实现了WSGI协议,并供了一些额外的高级功能,比如为多个进程间共享内存提供支持、支持虚拟化、负载均衡等。

                          
uwsgi是uWSGI服务器的协议,它定义了uWSGI服务器和其他进程如Nginx、Apache等)之间的通信协议。uwsgi协议可以通过网络将请求转发给uWSGI服务器,由uWSGI处理后再将结果返回给其他进程。

24 如何自定制上下文管理器

# python的一种编程模式,用于进入和退出之后自动执行一些代码的场景

# 一个对象如果实现了__enter__和___exit__方法,那么这个对象就支持上下文管理协议,即with语句
# 上下文管理协议适用于那些进入和退出之后自动执行一些代码的场景,比如文件、网络连接、数据库连接或使用锁,使用事务的编码场景等
# 加锁的场景都可以写成上下文管理器

# session--->创建session对象--->最后用完要调用commit,close
# 如何使用

from sqlalchemy.orm import scoped_session
from sqlalchemy import create_engine
from models import Base, User
from sqlalchemy.orm import sessionmaker
from models import User,Boy
engine = create_engine(
    "mysql+pymysql://root:123@127.0.0.1:3306/db001?charset=utf8",
    max_overflow=0,  # 超过连接池大小外最多创建的连接
    pool_size=5,  # 连接池大小
    pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
    pool_recycle=-1  # 多久之后对线程池中的线程进行一次连接的回收(重置)
)
# Session = sessionmaker(bind=engine)  # 把引擎传入
session = scoped_session(Session)


class ComonSession:
    def __enter__(self):
        print('进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量')
        self.session=session
        return self.session
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.session.commit()
        self.session.close()


with ComonSession() as a:
    res=a.query(Boy).filter_by(name='lqz').all()
    print(res)

print('我完事了')


# 我就用上下文管理器管理类 sqlalchemy 的链接对象session

25 Python是值传递还是引用传递


# 严格意义上来说,python既不是值传递,也不是引用传递,python是自己的传递方式,规则是:
    如果传递的是不可变类型,在函数中修改,就不会影响原来的变量
    如果传递的是可变数据类型,在函数中修改,不会影响原来的变量,修改,而不是重新赋值

# python一切皆对象--->(你不要说)内部函数是一切皆引用(对象本质就是地址,就是引用)



# 什么是值,什么是引用
	-值就是一个变量=具体的值(一块内存空间放着这个变量的值)
    -引用是一个变量=内存地址(内存地址指向了值)
    -所有python才有了可变和不可变类型
# 什么是值传递 什么是引用传递
	-如果是值传递,函数中修改了传递的值,不会影响原来的
    -如果是引用传递,函数中修改了传递的引用,就会影响原来的
Python作为一门动态语言,变量本身的类型是不固定的,因此更加灵活

Python参数传递统一使用的是引用传递方式。因为Python对象分为可变对象(list,dict,set)和不可变对象(number,string,tuple),当传递的参数是可变对象的引用时,因为可变对象的值可以修改,因此可以通过修改参数值而修改原对象,这类似于C语言中的引用传递;当传递的参数是不可变对象的引用时,虽然传递的是引用,参数变量和原变量都指向同一内存地址,但是不可变对象无法修改,所以参数的重新赋值不会影响原对象,这类似于C语言中的值传递。 

26 什么是迭代器,生成器,装饰器

# 迭代器
迭代:一种不依赖于索引取值的方式,我们不需要关注它的位置,只要能够一个个取值,它就称之为迭代
	for循环, next()
    
可迭代对象:可以迭代的(fornext取值的)python中的对象称之为可迭代对象
	在python中可以被for循环或可以变量.next()取值的对象称之为可迭代对象
	有 :字典,列表,字符串,元组,集合,文件对象
    
迭代器:可迭代对象调用__iter__,就得到了迭代器,迭代器有__iter__和__next__方法
自定义迭代器:写个类,类中重写__iter__和__next__方法,这个类的对象就是迭代器

'''不依赖与索引取值'''
优点:迭代器对象表示的是一个数据流,可以在需要时才去调用next来获取一个值;但他只能按照顺序一个一个取值。


# 生成器
生成器:生成器本质就是迭代器,迭代器不一定是生成器
函数中只要有 yield 关键字,这个函数被调用 函数(),它就变成了生成器
生成器表达式,也可以做出生成器  (i+1 for i in [123])
比如有一堆数据,要放到列表中,但 你没放,而放到了生成器中
for 循环生成器--->可以# 惰性取值,可以节省内存
        
(作用:对象中的元素是按照某种算法推算出来的,在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。)

def add():
    print(1)
    yield
res=add()

# 在哪里用过生成器?
    -读取文件,for循环内部其实就是在用生成器
    -我猜测:django中orm 查询一个表所有内容 Book.objects.all()--->内部应该也是一个生成器
    -redis  hascan 和 hsacn_iter
    -类似于这种场景我是可以用到它的:比如我要取数据,但是数据量比较大,不要一次性把把数据取到内存中,而是一点点取值,这样就可以把它做成一个生成器,可以节约内存


# 装饰器
# 闭包函数:1 定义在函数内部  2 对外部作用域有引用
	多了一种给函数传参的方式
	典型应用就是装饰器
	所有语言都有闭包函数--->所有语言就可以实现装饰器-->但是没有装饰器的语法糖
        
def auth(a):
    def inner():
        print(1)
        print(a)
        
# 装饰器是闭包函数的一种特殊应用
本身是一个闭包函数,作用是 在不改变被装饰对象"内部代码""原有调用方式"的基础之上添加额外的功能。
使用场景:
    flask的路由就是基于装饰器
    django的信号也可以用装饰器方式注册
    django中局部去除csrf认证
    为接口记录访问日志
	插入日志、缓存、权限校验、认证...

27 23种设计模式

https://www.cnblogs.com/liuqingzheng/p/10038958.html

创建类设计模式(5种)
单例模式、工厂模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式
# 单例模式
	确保一个类只有一个实例(也就是类的对象),并且提供一个全局的访问点(外部通过这个访问点来访问该类的唯一实例)。通俗的说就是,无论类实例化多少次,都只有一个相同的对象产生,并且可以通过一个具柄去访问这个这个唯一实例。
	应用:
    应用框架的配置文件config(如flask,django框架的配置文件)
    线程池,数据库连接池的创建
    应用程序的日志应用

    
# 工厂模式
	定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。其通用类图如下。其产品类定义产品的公共属性和接口,工厂类定义产品实例化的“方式”。
    
    
结构类设计模式(7种)
# 代理模式
定义如下:为某对象提供一个代理,以控制对此对象的访问和控制。代理模式在使用过程中,应尽量对抽象主题类进行代理,而尽量不要对加过修饰和方法的子类代理。
应用场景:flask框架中的request对象就是代理模式,真正的request对象隐藏起来了。

# 装饰器模式
就是我们学习的装饰器,python的装饰器就是在用装饰器模式


行为类设计模式(11种)
# 观察者模式
观察者模式也叫发布-订阅模式,其定义如下:
定义对象间一种一对多的依赖关系,使得当该对象状态改变时,所有依赖于它的对象都会得到通知,并被自动更新。(只要你发生了变化,所有的对象都会被通知,自动产生改变)
使用场景:Django信号机制

# 迭代器模式 
它提供一种方法,访问一个容器对象中各个元素,而又不需要暴露对象的内部细节。
就是python中的迭代器跟生成器

28 django的信号用过吗?如何用,干过什么

https://www.cnblogs.com/arher/p/13996521.html
    
django提供的一种通知机制,他是设计模式观察者模式(发布订阅),在发生某种变化的时候,通知某个函数执行

内置信号:如果是内置信号用起来简单,只需要写个函数,跟内置信号绑定,只要信号被触发,函数就会执行,可以绑定多个函数
    	-绑定信号,在django中有两种方式
        	@receiver
            connect连接  # 信号.connect(函数)
自定义信号:就比内置信号多了两步:
	1 定义信号   
    import django.dispatch

action=django.dispatch.Signal(providing_args=["aaaa","bbbb"])
    2 绑定信号  
    3 触发信号  信号.send

使用场景:
	记录日志(book插入一条数据就记录日志)
    用户密码修改,发送邮件通知
    一旦生成订单,干xx事
    数据库中插入数据,把数据同步到别的位置
    用信号来更新接口缓存(双写一致性)

29 什么是深拷贝,什么是浅拷贝,如何使用

# 无论深拷贝还是浅拷贝都是用来  复制对象的
    
# 浅拷贝
是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。

# 深拷贝
不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象


# 如果是浅copy,只会复制一层,如果copy的对象中有可变数据类型,修改可变数据类型还会影响拷贝的对象
# 如果是深copy,完整复制,无论可变或不可变,都是创建出新的来,以后再改原对象,都不会对copy出的对象造成影响

# 赋值
变量直接赋值,b就是直接赋值给a的内存地址了。


综上所述:浅拷贝是只拷贝对象本身,拷贝出的对象仍然引用原来的引用
深拷贝是拷贝对象本身,包括对象的引用的对象也都要复制一份。

30 并发 并行

在 Python 中,并行和并发是处理多任务的两个关键概念。
# 并行:
	-并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑。是在同一时刻,执行多个任务,每个任务都有自己的处理器(cpu),它们之间是独立的,不会相互干扰。单个CPU无法实现并行,必须是多核CPU才能实现并行。
	-作用:可以在同一时间内获得更高的运行效率。应用是使用multiprocessing创建多进程,处理计算密集型任务。

# 并发:
	并发是指资源有限的情况下,两者交替轮流使用资源。是指在一段时间内,执行多个任务,每个任务都只分配到一定的时间片,然后被暂停,等待下一个时间片的到来,再继续执行。单个CPU可以实现 多个CPU肯定也可以。
    作用:并发适用于需要同时处理大量 I/O 密集型任务的场景,例如网络请求、数据库访问、打开文件等。并发可以开启threading (线程)和 asyncio(异步协程)# IO使用多线程

在 Python 中,需要根据实际需求选择合适的方式。在使用并行和并发的时候,需要注意性能和资源占用等问题,以避免出现过度消耗 CPU 和内存等问题。同时也需要注意线程安全和数据共享等问题,以避免出现死锁和数据竞争等问题。

# ----> 计算密集型使用多进程,io使用多线程

31 同步 异步

# 程序调用的角度

同步:提交完任务之后原地等待任务的返回结果 期间不做任何事#之前学习的都是同步
异步:提交完任务之后不愿地等待任务的返回结果,直接去做其他事,有结果自动通知

    # 同步:同步是一件事一件事的做;只有执行完前一个任务,才会执行下一个任务。同步意味着有序
    # 异步:当一个任务已经(开始)执行了,你无需等待该任务执行完成,就可以切换到另外一个任务上。异步意味着无序
    
    # 我们用过的异步:
    	-线程中的异步回调机制、上传文件
    	-借助于其他框架:scrapy,celery
        -fastapi:异步web框架
        -sanic:异步web框架

32 阻塞 非阻塞

# 程序执行的角度
阻塞:程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的
非阻塞:程序在等待某操作过程中,自身不被阻塞,可以继续运行干别的事情,则称该程序在该操作上是非阻塞的

# 应用
阻塞:查询数据库,读取文件,
非阻塞:高并发服务器,图形化界面,多线程


结合
同步阻塞 ---------------> 效率最低
同步非阻塞
异步阻塞
异步非阻塞----------------> 效率最高,使用率高

33 什么是IPC,如何进行进程间通信

# 线程间通信:
	-共享内存(共享变量)---> 线程间变量是共享的---> lock锁---> 临界区---> 数据错乱---> 互斥锁(加锁,牺牲了效率,保证了数据安全)---> 死锁问题---> 递归锁(可重入锁)--->各种各样的锁:https://zhuanlan.zhihu.com/p/489305763

            
-出现原因:进程之间的数据是隔离的
	eg:python定义了一个a=10,开了两个进程,一个进程中更改数据, a=a+1,但是另一个进程中取值a,还是10,或者是在进程外的全局中取a,也还是10。相当于复制了两份
        
-IPC:Inter-Process Communication,进程间通信
-两种情况:
    -同一台机器上的两个进程通信
    -不同机器上的两个进程进行通信
-如何通信:
    -pyton queue可以做进程间通信
    -消息队列:  redis就可以做消息队列,rabbitmq,kafka
    -socket套接字:
    	展现形式: 1 服务和服务之间通过接口调用(http调用,浏览器访问服务器,本质上就是进程间的通信,浏览器的进程,访问服务器的进程) 2 RPC调用:远程过程调用 3 socket客户端和服务端,一个机器启动了客户端,一个机器启动了服务端
    
    
拓展:
	-

34 正向代理,反向代理

https://www.cnblogs.com/liuqingzheng/p/10521675.html

代理其实就是一个中介,A和B本来可以直连,中间插入一个C,C就是中介

-正向代理:代理的是客户端   VPN  爬虫代理池
-反向代理:代理的是服务端   nginx(反向代理服务器)



# 看一下:https://blog.csdn.net/lsc_2019/article/details/124630025
-AB测试
1.正向代理 (翻墙)
	正向代理即代理,用于代表内部网络用户向Internet上的服务器(或称外部服务器,通常为Web服务器)发出连接请求,并接收响应结果,执行该代理功能的服务器称为代理服务器,此过程会隐藏客户端ip。使用代理服务器访问外部网络时,客户端必须在局域网设置中指明代理服务器的地址以及要代理的服务的端口号。
    可以隐藏客户端的真实 IP 地址。
    可以进行访问控制,限制客户端对内部网络资源的访问。
    可以进行缓存,提高客户端的访问速度。
    可以加密客户端请求,提高数据传输的安全性。
    可以有效地防止 DDoS 攻击等网络安全威胁。
    正向代理,(内部上网)客户端<>代理->服务端
    
2.反向代理 (负载)
	反向代理的方向与正向代理相反,指代表外部网络用户向内部服务器发出请求,即接收来自Internet上用户的连接请求,并将这些请求转发给内部网络上的服务器,然后将从内部服务器上得到的响应返回给Internet上请求连接的客户,执行反向代理服务的服务器称为反向代理服务器,此过程会隐藏服务端ip,反向代理服务器对外部用户表现为一个服务器。
    可以隐藏后端服务器的真实 IP 地址。
    可以进行负载均衡,将请求分摊到多个后端服务器上,提高系统的可靠性和性能。
    可以进行会话保持,确保客户端请求连续发送到同一个后端服务器上,保证用户体验。
    可以进行 SSL 加密和解密,提高数据传输的安全性。
    可以通过 CDN 等手段优化网络带宽使用,提高客户端访问速度。
    反向代理,用于公司集群架构中,客户端->代理<>服务端	
    
    
3.区别在于形式上服务的”对象”不一样
正向代理是代理客户端,比如VPN,爬虫,反向代理是代理服务端,比如nginx

35 什么是粘包

-是tcp协议的一种现象
-因为TCP是流式协议,tcp客户端发送的多个数据包就会像水流一样流向TCP服务端,多个数据包就会'粘'在一起,区分不开是几个数据包,造成了粘包现象
    -1 每个包设置结束标志    http协议采用这种  /r/n/r/n
    -2 每个包设置固定大小的头,头中包含包的大小 

36 http协议详情,http协议版本,http一些请求头

https://zhuanlan.zhihu.com/p/137679911
#1 http是超文本传输协议,它是基于Tcp之上的应用层协议(osi七层)
#2 特点:
  1 基于请求响应-->服务端不能主动给客户端推送消息--->'websocket协议'
  2 无状态--->不记录用户的状态,不能做会话保持--->'才出现了cookie,session,token'
	eg:我在购物车加了商品,下次想要下单,不知道谁下的单,所以不能做会话保持
  3 短链接/无连接
    客户端和服务端建立链接之后,客户端发送一些请求,服务端响应一次,然后断开
  4 基于tcp之上的应用层协议--->'osi七层'
#详情:
   -请求协议: 
      请求首行:请求方式(get,post,delete),请求地址,请求http协议版本号/r/n  --->'请求方式''请求头'
      请求头:key:value   (cookie,useragent,referer,x-forword-for)
      请求体:编码方式 --->'编码方式'
  -响应协议:
        	响应首行:http协议版本,响应状态码(1xx,2xx),响应单词描述---> '响应状态码''单词描述有什么,可以简单说一下'
            响应头:key:value (cookie,响应编码...)   跨域问题的响应头(自己设置的响应头) --->'自己设置的响应头这个看一下'
            响应体:html格式:浏览器中看到的   json格式给客户端使用
# 协议版本
   -0.9:
      HTTP协议的最初版本,功能简陋,仅支持请求方式GET,并且仅能请求访问HTML格式的资源
   -1.0:
       但是1.0版本的工作方式是每次TCP连接只能发送一个请求(默认短链接),当服务器响应后就会关闭这次连接,下一个请求需要再次建立TCP连接,就是不支持keep-alive
   -1.1:
      引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复用(多个请求同时用一个TCP),不用声明Connection: keep-alive。客户端和服务器发现对方一段时间没有活动,就可以主动关闭连接。
  -2.0:
      多路复用:对于 HTTP/1.x,即使开启了长连接,请求的发送也是串行发送的,在带宽足够的情况下,对带宽的利用率不够,HTTP/2.0 采用了多路复用的方式,可以并行发送多个请求,提高对带宽的利用率 
 -3.0:(蓝图)
   	HTTP3.0又称为HTTP Over QUIC,其弃用TCP协议,改为使用基于UDP协议的QUIC协议来实现
    
    
拓展:
1.websocket协议
2.cookie,为了解决http请求无状态无连接的特点的
	session,为了解决cookie不安全的问题,
    token,为了解决session对服务器的压力
3.osi七层
4.请求方式有哪些(options有跨域,就会先带着一个预检请求)
5.请求头有什么?你用过什么?
cookie,useragent,referer,x-forword-for

	Host: 
    Connection: keep-alive\r\n
    sec-ch-ua: "Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"\r\n
    sec-ch-ua-mobile: ?0\r\n
    sec-ch-ua-platform: "Windows"\r\n
    Upgrade-Insecure-Requests: 1\r\n
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
        
User-agent:判断客户端类型,做分析,可以做爬虫的反扒,没有带User-agent,就不让它访问
Content-Type:https://pythonjishu.com/pbzkhllcmfghjbx/
        
        
6.编码方式有什么?说5种也行,3种也行,你最常用的是那种?json格式,写前后端分离的接口,常用json格式的数据

7.响应状态码有

img

1.1 查看目前浏览器使用http版本

import socket

sock=socket.socket()
sock.bind(("127.0.0.1",8080))
sock.listen(5)

while 1:
    print("server waiting.....")
    conn,addr=sock.accept()
    data=conn.recv(1024)
    print("data", data)

    # 读取html文件
    with open("login.html","rb") as f:
        data=f.read()

    conn.send((b"HTTP/1.1 200 OK\r\nContent-type:text/html\r\n\r\n%s"%data))
    conn.close()
GET / HTTP/1.1\r\n  # 请求首行


Host: 127.0.0.1:8080\r\n  # 请求头kv键值对形式
Connection: keep-alive\r\n
sec-ch-ua: "Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"\r\n
sec-ch-ua-mobile: ?0\r\n
sec-ch-ua-platform: "Windows"\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\n
Sec-Fetch-Site: none\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Sec-Fetch-Dest: document\r\nAccept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9


\r\n\r\n

2 GET请求和POST请求的区别

post更安全(不会作为url的一部分)
post发送的数据更大(get有url长度限制)
post能发送更多的数据类型(get只能发送ASCII字符)
post比get慢
post用于修改和写入数据,get一般用于搜索排序和筛选之类的操作

响应状态码

请求正在处理:1xx

正常响应:2xx

200:常规请求
201:创建成功
重定向响应:3xx

301:永久重定向
302:暂时重定向
客户端错误:4xx

403:请求无权限
404:请求路径不存在
405:请求方法不存在
服务器异常:5xx

500:服务器异常

37 如何实现服务器给客户端发送消息,websocket是什么?用过吗

# 服务端主动向客户端发送消息

# http的无连接,基于请求响应
# 1 轮询:
客户端向服务端无限循环发送http请求,一旦服务端有最新消息,从当次http响应中带回,客户端就能收到变化
# 2 长轮回(web版微信采用此方式)
客户端和服务端保持一个长连接(http),等服务端有消息返回就断开,
如果没有消息,就会hold住,等待一定时间,然后再重新连接,也是个循环的过程
(之前秒杀向后端发起请求后,前端起了一个定时任务,每3秒向后端发起一个请求,有了响应就展示到页面上)

# 不好实现http主动向客户端发送请求,才出现的websocket协议
# 3 websocket协议
客户端发起HTTP握手,告诉服务端进行WebSocket协议通讯,并告知WebSocket协议版本。
服务端确认协议版本,升级为WebSocket协议。之后如果有数据需要推送,会主动推送给客户端


-websocket:一种通信协议,区别于http协议,可在单个TCP连接上进行全双工通信(双工通信:两方都能够发送消息。单工通信:只能一方给另一方发送消息)。允许服务端主动向客户端推送数据。浏览器和服务器只需要完成一次tcp连接,两者之间就可以建立持久性的连接,并进行双向数据传输
-没有跨域问题
-应用场景:
    	-只要涉及到服务端主动给客户端发送消息的场景,都可以用
        -实时聊天
        -服务端发生变化,主动向客户端推送一个通知
        -监控服务端的内存使用情况
-djanog中使用channles模块实现
-flask中使用模块

     
-https://zhuanlan.zhihu.com/p/371500343

38 悲观锁乐观锁使用场景


#无论是悲观锁还是乐观锁,都是人们定义出来的概念,仅仅是一种思想,与语言无关

# 什么是并发控制:
   -1 并发控制:当程序中出现并发问题时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的,这种手段就叫做并发控制
   -2 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题

# 悲观锁:
    悲观锁:当要对一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发,让并行变成串行
    
    -这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制【Pessimistic Concurrency Control,缩写PCC,又名悲观锁】
    - 之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。
    
    -悲观锁实现方式:
    	1 传统的关系型数据库(如mysql)使用这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
    	2 编程语言中的线程锁,比如python的互斥锁
    	3 分布式锁:redis实现的分布式锁等

# 乐观锁:
    通过程序实现(没有真正的一把锁),不会产生死锁
    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据,如果更新了,我们就不做修改
        
    -乐观锁实现方案:
    1 CAS(Compare And Swap) 即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS 操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B),执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作
  CAS会出现ABA问题,比如,你看到桌子上有100块钱,然后你去干其他事了,回来之后看到桌子上依然是100块钱,你就认为这100块没人动过,其实在你走的那段时间,别人已经拿走了100块,后来又还回来了。这就是ABA问题
  解决ABA问题:既然有人动了,那我们对数据加一个版本控制字段,只要有人动过这个数据,就把版本进行增加,我们看到桌子上有100块钱版本是1,回来后发现桌子上100没变,但是版本却是2,就立马明白100块有人动过
    2 版本号控制:在数据表中增加一个版本号字段,每次更新数据时将版本号加1,同时将当前版本号作为更新条件,如果当前版本号与更新时的版本号一致,则更新成功,否则更新失败
    3 时间戳方式:在数据表中增加一个时间戳字段,每次更新数据时将时间戳更新为当前时间戳,同时将当前时间戳作为更新条件,如果当前时间戳与更新时的时间戳一致,则更新成功,否则更新失败
        
        
        
        
        
# 悲观锁乐观锁使用场景
并发量:如果并发量不大,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题, 建议乐观锁
响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高
冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大
重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低
读多写少: 乐观锁适用于读多写少的应用场景,这样可以提高并发粒度
    
        
#django中使用悲观锁
#django中使用乐观

39 mysql的存储引擎

* MyISAM(重点)
    MyISAM storage engine                       
    MySQL5.5及之前的版本默认的存储引擎
    存取数据的速度快,但是功能较少,安全性较低
    它不支持事务,行锁,外键;支持表锁

* InnoDB(重点)
    Supports transactions, row-level locking, and foreign keys
    MySQL5.6及之后的版本默认的存储引擎
    存取速度没有MyISAM快,但是相对MyISAM安全性更高
    它支持事务,行锁,外键;

* MEMORY
    Hash based, stored in memory, useful for temporary tables
    数据存放在内存中,一旦断电,数据立马丢失,重启服务端数据就没了,不能长期保存数据,仅用于临时表数据存取
    
* BlackHole
	任何写入进去的数据都会立刻丢失,使用很少

了解不同存储引擎底层文件个数:

1. MyISAM引擎 产生3个文件
    .frm  >>> 表结构
    .MYD  >>> 存数据    d-> data
    .MYI  >>> 存索引   >>> 看成是目录  i-> index 
2. InnoDB 产生2个文件
    .frm  >>> 表结构
    .ibd  >>> 表结构+数据
3. MEMORY 产生1个文件
    .frm  >>> 表结构
    数据是保存在内存中,所以磁盘中没有。
4. BlackHole 产生1个文件
    .frm  >>> 表结构
    数据给了我之后,下一秒就删除了

40 git常用的命令

* git init  --- 初始化,在当前文件夹下就会创建出  .git 文件夹,这个就会被git管理
* git status  --- 红  绿
    - 如果是红色,表明是在工作发生了变化,没有提交到暂存区
    - 如果是绿色:表明,暂存区数据没有提交到版本库

* git add  --- 把工作区变更,提交到暂存区
* git commit -m '我的第一次提交'  ---- 把暂存区内容,提交到版本库
* git remote add 远程仓库名字 远程仓库地址  --- 添加远程仓库
* git push origin master   ---- 把本地仓库中所有的内容,提交到远程仓库
* git pull origin master   ---- 拉去仓库中最新的代码,否则提交不了

* git log  ---- 查看版本信息(提交过哪些版本,注释是什么)
* git reflog


* 忽略文件 .gitignore

41 jwt原理

JWT就是一段字符串,由三段信息构成的,第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature).
* 头部有:声明加密的算法 通常直接使用 HMAC SHA256、公司信息
* 荷载:存放有效信息的地方。用户名,用户id,exp: jwt的过期时间
* 签证:这个部分需要base64加密后的 header 和base64加密后的 payload 使用.连接组成的字符串,然后通过header中声明的加密方式(加盐secret),组合加密
然后就构成了jwt的第三部分。

开发流程
* 1)根据提交的用户、客户端信息,产生token,将token直接返回给客户端,存储客户端的cookies中(签发)
* 2)从请求中拿到携带的token,利用签发token的逆运算代码解析token,得到user对象,存储到request.user中,提供给之后的服务器代码使用(校验)

42 apiview的执行流程

# 总结:
    1  以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
    2  以后只要继承APIView的所有视图类的方法 中的request是新的request了
    3  在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
    4  期间除了各种错误,都会被异常捕获,统一处理


# 有了drf,后期都写CBV,都是继承APIView及其子类
# 执行流程:
	-入口:path('books/', BookView.as_view())---> 请求来了,执行BookView.as_view()(request)
    -as_view 是谁的? APIView的as_view
        @classmethod
        def as_view(cls, **initkwargs):
            # super()代指的是:父类对象  View类的对象
            # super()在哪里就从那个类开始找
            # View的as_view(**initkwargs)----> 执行结果是view,是View类的as_view方法中的view
            view = super().as_view(**initkwargs)
            view=csrf_exempt(view)  # 局部禁用csrf,跟加上csrf_exempt装饰器的作用是一样的
            return view
        
  	-path('books/', View类的as_view中的view,只是去掉了csrf的认证)
	-请求来了,执行 【View类的as_view中的view,只是去掉了csrf的认证(request)-执行:self.dispatch(request, *args, **kwargs), self要从根上找
	-self.dispatch 是APIView的dispatch,源码如下
        def dispatch(self, request, *args, **kwargs):
            # 等号右边括号内的request是老的request,(django.core.handlers.WSGIRequest)
            # 等号左边的request 是新的request,(rest_framework.request.Request)   
            # 把老的request封装到了新的request中了
            request = self.initialize_request(request, *args, **kwargs)
            self.request = request
            try:
                # 执行了认证,权限和频率。
                self.initial(request, *args, **kwargs)
                # 在执行视图类方法之前,去掉了csrf认证,包装了新的request,执行了认证频率和权限
                #### 执行请求方式字符串对应的方法
                if request.method.lower() in self.http_method_names:
                    handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
                else:
                    handler = self.http_method_not_allowed
                # 真正的执行自己类中的get()方法
                response = handler(request, *args, **kwargs)
            except Exception as exc:
                response = self.handle_exception(exc)
                
            # 无论是在三大认证,还是视图类的方法中,出现错误,都会被异常捕获,统一处理,全局逮异常
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response
    

43 如何提高项目的并发量

## 1 前端
	1 用cdn(分布式的内容分发网,比如七牛云)。静态资源,放到cdn上。使用第三方存储(js,css,静态图片)。这样就不会向我的后端发送请求,项目的并发量就会增多一些。
	2 用精灵图(一个大图,上面有很多小图,用定位,定位到小图),一次请求拿回来的图,以后好多位置可以用。
	3 前端缓存(响应头设置缓存时间)cache-control

## 2 高性能web服务器部署项目:nginx
	1 nginx的请求转发,nginx性能高,负载均衡
	2 nginx做集群,dns解析做负载 --> 往上软件就解决不了的,可以用,负载均衡硬件 f5
![](https://img2023.cnblogs.com/blog/3095768/202308/3095768-20230823212700936-1947134592.png)

	3 动静分离(nginx对静态文件的转发性能比较高,静态资源直接通过nginx转发;uwsgi只负责处理动态请求)加入有10个请求,8个是静态,2个是动态,这样只有2个才会进入我们的项目中
	4 集群化部署,一个项目部署在5台机器上
	5 拆服务(分布式,微服务)

![](https://img2023.cnblogs.com/blog/3095768/202308/3095768-20230823212705806-1061617546.png)


## 3 项目部署(使用性能高服务器部署项目:gevent+uwsgi)
	-https://zhuanlan.zhihu.com/p/358163330
	1 替代性能低的wsgiref部署项目-使用uwsgi(c写的wsgi服务器)部署
	2 使用 gunicorn(python写的wsgi服务器)部署

## 4 代码层面
	1 做缓存(redis)
	2 页面静态化(不适用于app和小程序),提前生成一个首页页面(被访问频繁的页面),数据不一致(当有数据增加,再重新生成一次这个页面,双协议一致性问题),可以每隔10秒中,再次生成一个页面。用异步:用celery,django的信号(异步操作),当对象保存时,重新生成静态页面
![](https://img2023.cnblogs.com/blog/3095768/202308/3095768-20230823212729077-2078926206.png)

	3 异步操作(celery),一个请求需要耗时3s,设计成异步 --> 请求来了--> 直接返回(任务已提交,请求正在处理,别的线程处理)
        小米秒杀:您正在排队(前端设置了定时,每隔5s,发送一个请求,查是否秒杀成功)
        保存视频,发送邮件,保存文章
        后台管理,统计最近三,六个月的订单量-->折线图,饼状图展示
	4  数据库优化,优化sql,外键尽量不建立,适当建索引,读写分离,数据库集群,分库分表
	5 优化代码,尽量不在for循环里查数据库(for循环的大小不好估计)--->算法
	6 换语言,python是解释性语言,运行时需要翻译。可以换成编译型语言

## 5 代码优化不了的,加机器
	-但是,如果加机器的成本超过了优化代码的成本。就要使用优化代码的方式
	-C10K问题:单台服务器如何支持10k个并发连接。
	https://blog.csdn.net/chinawangfei/article/details/102780959
  
  
## 6 IO 同步id,异步id,io多路复用

docker

1 docker是什么,docker的命令

Docker是一种开源的容器化平台,用于构建、打包和运行应用程序。

# docker的命令

2 镜像操作、容器操作

docker search 镜像名称  # 搜索镜像

docker pull centos:7  # 拉取镜像,如果不指定标签,会下载最新
docker images  # 查看本地镜像  
docker rmi 镜像ID   # 删除镜像,可以同时删多个,多个空格隔开
docker rmi `docker images -q`  # 删除所有镜像


# 容器操作
docker ps  docker ps  # 查看正在运行的容器
docker ps -a  # 查看所有容器
docker ps -l  # 查看最后一次运行的容器
    

docker start id/名字  # 启动停止的容器
docker stop 7d5e  # 停止运行的容器
docker stop `docker ps -q`  # 停止所有在运行的容器
docker run -di 镜像名:版本号  # 把镜像运行成容器
    -id  # 不会进入到容器内部
    -it  # 会进入到容器内部
    -v 宿主机目录:容器目录  # 目录映射
    -p 3307:3306  # 端口映射
docker exec -it 容器id 命令  # 进入到容器内部
docker rm 容器id  # 删除容器


docker cp 目录:目录 # 拷贝文件
docker inspect 容器名称  # 查看容器的详细描述



# 重点命令
docker commit 容器名/id 镜像名/id  # 把容器保存为镜像
docker save -o 压缩文件名称.tar 镜像名字  # 保存镜像为压缩文件,备份镜像
docker load -i centos_vim_image.tar  # 把备份的镜像恢复
docker tag 镜像名/镜像id 指定的远程仓库地址:版本  # 先给镜像打标签
docker push liuqingzheng/lqz_books:v1  # 上传镜像到私有仓库

docker build -t='镜像名' .   # 基于dockerfile构建镜像

3 docker-file

  • docker build -t=‘镜像名’ .
Dockerfile是由一系列命令和参数构成的脚本文件,这些命令应用于基础镜像并最终创建一个新的镜像

案例:构建一个djagno项目

第一步:有一个项目,pycharm开发着,开发完后
第二步:在项目路径下新建Dockerfile,写入
FROM python:3.8  # 基于哪个基础镜像来构建
MAINTAINER lqz  # 声明镜像的创建者,maintainer,维修工,维护员
WORKDIR /soft  # 设置工作目录,来到容器内部就是/soft下
EXPOSE 8080  # 暴露的端口
COPY ./requirements.txt /soft/requirements.txt  # 将./re文件复制一份到soft/re.txt中
RUN pip install -r requirements.txt -i https://pypi.doubanio.com/simple   # 执行命令,安装第三方模块的命令
CMD ["python","manage.py","runserver","0.0.0.0:8080"]  # 容器启动时需要执行的命令,没有设置cmd命令,那么进入容器后,就会执行centos:7的cmd命令(bin/bash)

第三步:把代码提交到git


------------------
第四步:上线人员:在上线机器上,把代码啦下来
git clone git@gitee.com:zh-jyao/books.git
第五步:构建镜像
cd books
docker build -t='django_books' .
第六步:运行容器
docker run -id --name=books -v /root/lqz/books:/soft -p 8080:8080 django_books:latest

docker run -id 
--name=books  # 名字
-v /root/lqz/books:/soft  # 目录映射
-p 8080:8080  # 端口映射
django_books:latest # 镜像名字

第七步:其它人访问宿主机的8080端口就能看到项目了


---------
第八步:开发人员继续提交代码
第九步:运维人员pull代码,重启容器,用户就可以看到最新的了
git pull origin master
重启docker容器即可(第三方依赖变了)-->重写构建镜像,运行容器
docker restart books

4 docker-compose

Docker Compose是一个能一次性定义和管理多个Docker容器的工具,单机容器编排【定义和管理】

docker-compose部署项目步骤
第一步:写完一个项目
第二步:编写Dockerfile
第三步:编写docker-compose.yml文件,几个镜像写几个服务
第四步:提交到git中

-----
第五步:远端机器中,把项目git clone 下来
第六步:docker-compose up

43 数据表清空的方式

delete,truncaket
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
J2EE面试题汇总是一个包括多个问题的汇总,其中涵盖了EJB与JAVA BEAN的区别、JAVA中的多态与继承、BS与CS的联系与区别等内容。其中,EJB与JAVA BEAN是SUN的不同组件规范,EJB是在容器中运行的,分布式的,而JAVA BEAN主要是一种可利用的组件,主要在客户端UI表现上。另外,JAVA中的多态与继承是面向对象编程的重要概念,多态允许一个变量或方法调用在不同的对象上具有不同的行为,而继承允许子类继承父类的属性和方法。BS与CS是指基于浏览器的系统和基于客户端的系统,两者之间联系是通过网络进行交互,区别在于BS系统的应用程序在服务器端运行,而CS系统的应用程序在客户端运行。总的来说,这个面试题汇总涉及了J2EE开发中的一些重要概念和技术,并且可以帮助面试者更好地了解J2EE的相关知识。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [常见J2EE面试题汇总](https://download.csdn.net/download/xujingming518/2649040)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [常见J2EE面试题汇总1](https://blog.csdn.net/ling_76539446/article/details/102759076)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值