前言:欢迎各位留言补充遗漏的知识点,本文适用于初中级开发求职~

文章目录

      • 0. 开发者(程序猿)tip(杂)
      • 1.游戏后端架构设计
        • 1.1 设计方案
        • 1.2 web服务架构
        • 1.3 运维监控
      • 2 面试控场
        • 2.1 技术面
          • 2.1.1 专业能力
          • 2.1.2 综合素质
        • 2.2 leader面
        • 2.3 HR面
        • 2.4 自我介绍
        • 2.5 职业规划
        • 2.6 离职原因(丧命题)
        • 2.7 公司认同
      • 3 python
        • 3.1 语言特点
          • 3.1.1 设计哲学
          • 3.1.2 追根溯源
          • 3.1.3 python2 与 python3 对比
          • 3.1.4 QS:Python综述
          • 3.1.5 QS: 什么是解释型语言
        • 3.2 GIL
          • 3.2.2 QS:谈谈你对GIL的认识?
          • 3.2.3 QS:既然有GIL的存在,为什么我们在代码中我们自己经常还选择加锁(普通互斥锁)?
        • 3.3 python对象与存储
        • 3.4 python内存回收
          • 3.4.1 引用计数(主)
          • 3.4.2 标记清除(辅)
          • 3.4.3 分代回收(辅)
          • 3.4.4 内存池机制
          • 3.4.5 QS:Python内存泄漏
          • 3.4.6 内存管理调优手段
        • 3.5 python异常处理
          • 3.5.1 QS:列举常用异常
          • 3.5.2 python异常处理方式
        • 3.7 python正则处理
          • 3.7.1 常用正则(必须会!!!)
          • 3.7.2 常用正则函数比较
        • 3.8 python函数
          • 3.8.1 python内置函数
          • 3.8.2 需要关注的函数
          • 3.8.3 迭代器&生成器
          • 3.8.3 函数参数
        • 3.9 python模块
          • 3.9.1 python内置模块
          • 3.9.2 python第三方模块
        • 3.10 python面向对象
          • 3.10.1 装饰器
          • 3.10.2 设计模式
            • 3.10.2.1 单例模式
          • 3.10.3 面试题合集
        • 3.11 数据类型与运算符顺序
      • 4 系统编程
        • 4.1 进程
          • 4.1.1 QS:了解COW吗?
        • 4.2 线程
          • 4.2.1 QS:如何保障线程安全
        • 4.3 协程
          • 4.3.1 QS:协程的实现原理
        • 4.4 QS:对多进程,多线程,协程的理解
          • 4.4.1 QS:python中协程的实现原理(深度)
      • 5 DB
        • 5.1 DB-mysql
          • 5.1.1 QS:sql 与 nosql 对比
          • 5.1.2 QS:mysql有哪些存储引擎以及区别?
          • 5.1.3 索引
            • 5.1.3.1 索引的必要性(优势&作用)
            • 5.1.3.2 索引缺陷(劣势)
            • 5.1.3.3 B+树与B树(拓展,方便理解各种索引)
            • 5.1.3.4 QS:什么是覆盖索引?什么是覆盖索引失效?
            • 5.1.3.5 主键索引与辅助索引
            • 5.1.3.6 聚集索引与非聚集索引
            • 5.1.3.7 其他索引概念
          • 5.1.4 Mysql版本变迁
          • 5.1.5 mysql事务
            • 5.1.5.1 事务特性
            • 5.1.5.2 事务隔离级别
          • 5.1.6 QS
            • 5.1.6.1 innodb主键索引一定比其他索引快吗
            • 5.1.6.2 mysql中主键为什么一般推荐使用自增的整数
            • 5.1.6.3 innodb 一定要声明主键吗?
            • 5.1.6.4 了解间隙锁吗?
            • 5.1.6.5 怎么了解密集索引,稀疏索引?
            • 5.1.6.6 主键如何选择
            • 5.1.6.7 外键的好处和劣势
            • 5.1.6.8 ORM有什么优势,弊端?
          • 5.1.7 MVVC :多版本并发控制
          • 5.1.8 死锁
          • 5.1.9 乐观锁与悲观锁
          • 5.1.10 mysql分表,分区,分库
          • 5.1.11 binlog
          • 5.1.12 mysql主从复制
            • 5.1.12.1 QS:主从不一致怎么解决
          • 5.1.13 QS:sql注入是如何产生的,怎么预防
          • 5.1.14 sql优化(建议直接背把)
        • 5.2 DB-mongoDB
          • 5.2.1 存储引擎
          • 5.2.2 版本变迁及特性
          • 5.2.3 mongodb扩展
          • 5.2.4 mongodb索引结构
        • 5.3 DB-redis
          • 5.3.1 数据类型
          • 5.3.2 使用场景
          • 5.3.3 存储机制(了解)
          • 5.3.4 Bloom Filter算法
          • 5.3.5 QS:了解redis缓存穿透,缓存击穿,缓存雪崩?
          • 5.3.6 redis如何做持久化
          • 5.3.7 QS:redis内存不够怎么办
        • 5.4 QS:MySQL和MongoDB区别
        • 5.5 行式数据库与列式数据库
        • 5.6 数据库范式
      • 6 网络编程
        • 6.1 Websocket
          • 6.1.1 ajax轮询
          • 6.1.2 long poll
          • 6.1.3 websocket
            • 6.1.3.1 谈谈你对回调函数的理解?
          • 6.1.4 网络相关面试题
        • 6.2 IO多路复用
          • 6.2.1 select(1983)
            • 6.2.1.1 什么是线程安全,非线程安全?
          • 6.2.2 poll(1997)
          • 6.2.3 epoll(2002)
          • 6.2.4 QS:什么是IO多路复用,/epoll和select,poll有什么区别?
        • 6.3 Web框架
          • 6.3.1 同步/异步 阻塞/非阻塞
          • 6.3.2 Django
          • 6.3.3 Flask
          • 6.3.4 Tornado
          • 6.3.5 Sanic
            • 6.3.5.1 QS:sanic是什么?uvloop为什么那么快?(拓展,了解)
          • 6.3.6 QS:Python Web 框架认识和对比
        • 6.4 消息队列
          • 6.4.1 需求
          • 6.4.2 应用场景
          • 6.4.3 两种模式
          • 6.4.4 RabbitMQ
          • 6.4.5 Kafka
            • 6.4.5.1 QS: Kafka为什么那么快?(拓展,后续深入了解)
            • 6.4.6 本地搭建ELK -日志分析系统
        • 6.4 DeveOps
        • 6.5 uwsgi(作用,进程数设置)
        • 6.6 Nginx
      • 7 数据结构
        • 7.1 数据结构种类
          • 7.1.1 数组
          • 7.1.2 栈
          • 7.1.3 队列
            • 7.1.3.1 双端队列
          • 7.1.4 链表
            • 7.1.4 链表反转
          • 7.1.5 树(倒挂树)
            • 7.1.5.1 二叉树
          • 7.1.6 散列表|哈希表
          • 7.1.7 堆
          • 7.1.8 图(初级不做要求)
        • 7.2 算法
          • 7.2.1 时间复杂度
          • 7.2.2 空间复杂度
        • 7.3 排序算法(至少熟练掌握三种)
          • 7.3.1 冒泡排序(***必会)
          • 7.3.2 选择排序(会选择题即可)
          • 7.3.3 插入排序(会选择题即可)
          • 7.3.4 希尔排序(不要求)
          • 7.3.5 快速排序(***必会)
          • 7.3.6 归并排序(***必会)
          • 7.3.7 堆排序 (***必会)
        • 7.4 数据结构与算法面试题
          • 7.4.1 QS: 单链表反转: --- 必须会写
          • 7.4.2 QS:判断两个单向链表是否相交 --- 要会思路,不要求现场代码
          • 7.4.3 QS:求满足指定和的子集
          • 7.4.4 QS: 纸币组合(星商,较难)
          • 7.4.5 QS:洗牌算法(较为简单,游戏必问)
          • 7.4.6 QS:从1亿个整数中找出其中出现次数最多的数
          • 7.4.7 QS:两个大文件A,B,每一行都为一个字符串,找出在两个文件中都出现的字符串
          • 7.4.8 QS: 某整数开方如何实现,保留指定位数 --二分逼近
          • 7.4.9 QS:九宫格 --排列组合问题 --通过进制(难)
          • 7.4.10 QS:给你几张扑克牌,扑克牌的牌值通过运算得出24,用代码求出可能的组合,用到的符号: +, -, /, 括号(难)
          • 7.4.11 旋转数组
          • 7.4.12 从100亿个数中找出最大/小的1000个数
          • 7.4.13 有1亿个整数,其中只有1个整数只出现了1次,怎么快速找出来
          • 7.4.14 python中sort 是如何实现的
      • 8 开发杂谈
        • 8.1 优化
          • 8.1.1 规范化
            • 8.1.1.1 编码风格:
            • 8.1.1.2 流程规范
          • 8.1.2 性能
          • 8.1.3 体系
        • 8.2 解决线上未知问题
        • 8.3 编码规范(不限于PEP8)
      • 9 linux
      • 9.1 docker的好处和缺陷
      • 9.2 CPU负载怎么理解,比如四核机器负载多少算超载
        • 9. 3 kubernetes+docker
        • 9.4 命令相关
      • 10 git
        • 10.1 git与svn区别
        • 10.2 git常见命令
        • 10.3 提交代码发生冲突,如何产生,你是如何解决?
        • 10.4 git fetch, git pull, git merge之间的区别与联系?git merge 和 git rebase 区别?
        • 10.5 github 与 gitlab 的区别?
        • 10.6 .gitignore 的作用?
      • 11 django详细(待完善)
        • 11.1 django基础
          • 11.1.1 cookie&session
          • 11.1.2 csrf_token
            • 11.1.2 MTV与MVC
        • 11.2 django进阶
          • 11.2.1 orm
        • 11.3 drf
      • 12 vue(待完善)

0. 开发者(程序猿)tip(杂)

以下跟面试无直接相关,但是是日常编程和学习中非常有用的知识点或者可以带来提升的小建议(因为是突然想到或者不知道放哪,就统一放这里了,可跳过)。

tip01: 多浏览技术新闻,博客,论文,论坛等,想想当前或者以后工作中哪些能用到?

tip02: 熟透部分数字的量级;做到立即反应;举例:40亿bit = 512Mbytes等;

tip03: 空间局部性原理:如果一条数据在某一时刻被访问了,则大概率预测其旁边的数据也即将被访问。

tip04: 存储器速率:寄存器(1ns) > 高速缓存(L1(k ns),L2, L3) > 内存(100ns) > 硬盘(50ms) > 外部存储;
CPU高速缓存是位于CPU和内存之间的临时数据交换器,他的容量比内存小得多但是交换速度却比内存要快得多。
CPU缓存一般直接跟CPU芯片集成或位于主板总线互连的独立芯片上。

tip05: 多核和多CPU区别:多个cpu,cpu通过总线进行通信,效率比较低;
多核cpu,不同的核通过L2 cache进行通信,存储和外设通过总线与cpu通信


​ 多cpu

​ 多核CPU

tip06: 进程和线程在多核CPU,多CPU中的运行关系

	多CPU的运行,对应进程的运行状态;多核CPU的运行,对应线程的运行状态。
	操作系统会拆分CPU为一段段时间的运行片,轮流分配给不同的程序。对于多CPU,多个进程利用并行在多个CPU中计算,当然也存在进程切换;对于单CPU,多个进程则是并发运行,根据时间片读取上下文->读取上下文->保存上下文。同一个进程同一时间段只能在一个CPU运行,如果进程数小于CPU数,那么未使用的CPU将会空闲。
	进程有自己独立的地址空间,每启动一个进程,系统就会为他分配一个地址空间,建立数据表来维护代码段,堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据,使用相同的地址空间,因此CPU切换一个线程的开销花费远比进程小的多,同时创建一个线程的开销也比进程小的多。
	对于多核CPU,进程中的多线程并行执行,执行过程中存在线程切换,线程切换开销较小。对于单核CPU,多线程在单CPU中并发执行,根据时间片切换。同一个线程同一时间只能在一个CPU内核中运行,如果线程数小于CPU内核数,那么将有多余的内核空间。
	总结:
	1. 单CPU中进程只是并发,多CPU中计算机中进程可以并行。
	2. 单CPU单核中线程只能并发,单CPU多核中线程可以并行。
	3. 无论是并发还是并行,使用者来看,看到的是多进程,多线程。

tip07: mysql5.7 单表 建议数据 不要超过1000万

​ mongodb4.0+ 固态硬盘 单collection 1-2亿条数据

tip08: 数据规模:数据库里记录数据最多的一张表的记录量级;

tip09: Python中有序的结构: 列表, 元组, 有序集合, 有序字典
无序的结构: 普通集合, 普通字典

tip10: 敲sql命令时: 修改操作时, 建议先把条件敲上, delete from user where id=10;防止手误条件没加上删多了数据!!!

tip11: unspecified:最差的结果,有可能报错,有可能不报错(不代表没有错误)

tip12: protobuf协议:

tip13: 初创公司(高内聚,高耦合)–>中型公司->大型公司(耦合性越来越低,高耦合,低内聚,运维成本越来越高)

tip14: 编码封装函数的时候,尽量不要修改传入的参数(容器对象)

tip15:QPS:每秒处理的请求数

tip16:微服务:微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,
服务之间相互协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,
服务和服务之间采用轻量级的通信机制相互沟通(通常是基于HTTP的Restful API).
每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。
另外,应尽量避免统一的、集中的服务管理机制,对具体的一个服务而言,
应根据业务上下文,选择合适的语言、工具对其进行构"---- Martin Fowler

1.游戏后端架构设计

1.1 设计方案

​ a. 性能卓越 和 高可用性

​ 分布式架构

​ 高并发

​ 游戏推送 – 周期性批量推送

​ 故障自动切换

​ 热更新

​ 安全因素: DDos防护,防重放,sql注入 …

​ b. 监控运维,测试

​ 用户行为模拟

​ 通知

​ 监控系统

​ linux上线操作脚本自动化

​ ELK 体系

1.2 web服务架构

​ web接口层: 收到路由过来的请求,只对消息参数做基本的验证;

​ 业务逻辑层: 该请求的业务逻辑在此处理;

​ 数据接口层: 封装了所有的数据存取操作; – 不让调用者关注数据存取底层是如何存储的;

​ 数据存取层: 直接操纵数据的存取,操作 mysql, redis, mongodb, mssql, memcache;

1.3 运维监控

  1. 服务器层面|主机层面监控: — 云厂商
    云主机状态: 存活, 内存, 硬盘, 网络, cpu…
    是否可用, 是否超过阈值
    是否反常(某个时间段的状态是否明显和其他时候不一样)
  2. 服务层面|业务层面: — 有部分三方厂商可以监控一部分
    mysql 响应时间, 登录服务响应时间… — google sre
    99.9% 99.99% : 服务可用时间
  3. 业务逻辑层面 — 公司内部自己做
    检查货币情况是否有误差

2 面试控场

面试课知识点较多: 先背再理解

技术能力: 符合岗位要求;

综合素质: (价值观,协作,沟通,逻辑思维)优良;

控场: 你的亮点(优势);常见问题与技术可以不会,但是不能不知道;

2.1 技术面

2.1.1 专业能力

–知识面广度(对常识性知识点必须了解)

–知识点深度(关键所在)

–逻辑思维能力:场景设计和算法逻辑(高级岗位必需)

2.1.2 综合素质

比重较小,面试过程尊重面试官

2.2 leader面

–基础知识点不多

开放型问题较多

–重点在综合素质

tip: 回答的时候尽量挑某个熟悉或者无异议的点来回答,尽量闭坑或者避免有争议的东西。

原则:遵循客观事实,承认劣势;强调优势,举例证明;道明兴趣,展现自信;

2.3 HR面

人品,诚信,价值观,薪资

2.4 自我介绍

time: 30s-2min; 不要低头; 尽量不要背,眼神互动;切忌卡顿,尽量流畅;

2.5 职业规划

避免谈太长周期3-5年即可;避免规划管理方向,尽量往技术方向上说;

2.6 离职原因(丧命题)

不要非议上家公司和leader;

大公司:做的事情很窄,螺丝钉(劣势);最前沿的技术,知识点深度(优势);

小公司:接触很广,体验产品从0-1的过程(优势);技术深度不够(劣势)

2.7 公司认同

公司领域业务认同;

有没有什么要了解的(提问);

期望薪资(根据自己面试情况,招聘岗位薪资范围自我认知)

询问自己与岗位需求的差异或差距(询问面试结果情况)

3 python

3.1 语言特点

3.1.1 设计哲学

简洁,明确,优雅

3.1.2 追根溯源

1991 发布第一个版本; 2000 发布稳定的2.7版本;2008 Python3发布; 2020 Python2 将不再维护;

3.1.3 python2 与 python3 对比

思路:

  1. 核心类差异(编码方式&字符串类型的差异,import方式的区别,类的区别,缩进)
  2. 废弃类差异(答5条即可,如 long整数类型废弃,xrange函数废弃,print、exec、execfile语句,rawinput函数废弃)
  3. 修改类差异(for循环,比较操作符,round函数,异常处理,除法运算的区别)

参考blog

3.1.4 QS:Python综述
  1. 什么情况下使用Python语言开发比较好?
  2. 你对Python怎么看?
  3. 为什么要用Python?

回答思路: 时间线 + 空间线 + 引子(带入自己熟悉的领域)

时间线: 91,第一个稳定的Python版本;00,Python2.7稳定版发布;08,Python3发布;20,Python2不再维护;

空间线:Python设计哲学 + Python2,3特性 + 和其他编程语言(如c++对比)+ 语言选型考虑因素;

引子:Python2 与 Python3 的区别;

话术串联(历史->Python本质特性->如何选型(语言精华,语言对比)->埋引子)

# 参考案例1
	Python自1991年发布其第一个稳定版本以来,
	一致秉承着其“简单、明确、优雅”的设计哲学,
	这从根本上决定了Python这门语言在编程开发上的易用性和工作高效性。
	同样的业务需求实现,可能传统编程语言C++需要一周工期
	,而Python可能三天就能完成,并且可能隐藏的问题也会少很多。
	因为Python本身目前有着非常庞大的开源第三方库,
	同时封装了很多复杂的语言底层操作如内存分配与释放,指针的相关的操作等;
    在具体的项目实践过程中,开发语言的选型具体要考虑几个方面:
    业务的本身特点适合哪个语言、公司的历史技术栈和技术人员技术储备倾向于哪种语言、
    选型语言当前的生态体系和发展前景(如是否不维护);
    如果项目非计算密集型项目。此时Python语言就有一定的优势,
    碰到其中少部分算法性能瓶颈的地方可以用c++等语言重写,
    然后Python调用,这样就可以兼顾Python开发低成本和C++高性能两方面的优势;
    具体选用Python后,新项目推荐使用Python3,
    自08年发布Python3以来,Python3生态圈三方库已经非常完备和齐全,
    且Python2宣布自2020年以后不再做维护,
    另外Python3相比Python2性能上也有一定提升。
# 参考案例2
	Python语言和C++, Java等其他编译型语言不一样,它是一种动态解释型脚本语言,
	代码在执行时会一行一行的解释成CPU能理解的机器码。
	同时它又是跨平台的,可以允许在windows,linux,mac等系统上,
	同样的程序逻辑,可能C语言需要1000行代码,java有100行,
	而Python实现可能只需要20行,这极大程度上提升了企业的项目开发效率。

     记得之前看到一篇报道说,到2014年为止,
     哈佛、麻省理工、伯克利等顶尖高校都已经使用Python作为教学语言,
     而最近这一两年来Python也多次被各大机构评为年度最佳编程语言。
     对于企业项目后台开发而言,不是说一定需要使用Python开发,
     但至少应该是一个首选考虑项,
     当然Python本身从根本上来说性能是比不上C/C++这些编译型语言,
     但现在语言本身的性能差距在实际开发的过程中,相对业务功能合理性设计、
     代码编写架构质量等等,语言底层本身造成的性能损失越来越可以忽略不计;
     针对于一些特定的功能模块,Python当前也出现了pypi,
     JIT等大幅提高Python程序运行效率的相关技术,能够满足大多数功能需求业务场景。
     
     编程语言的本质是人与机器沟通的工具,将人类希望机器做的事翻译成机器本身能够理解的指令;
     语言发展进化的历史也已经表明,越符合人类自身思维逻辑及习惯的编程语言将越受到大众欢迎。
     目前从云端、客户端,到物联网终端,python应用无处不在,
     同时也是人工智能首先的编程语言(Python在在人工智能上的优势至今无人能够撼动)。
     Python当前也已经具备了非常完备丰富的开源三方生态圈(
     比如web框架tornado,sanic, 运维监控zabbix,游戏引擎firefly等等不胜枚举)。
     对于大多数企业新后台项目开发,个人倾向于推荐Python作为首选语言。

     具体选用Python后,新项目推荐使用python3,2008年Python3发布后,
     十几年来Python3生态圈三方库已经非常完备和齐全,而官方已宣布Python2在2020年将不再维护,
     另外Python3本身性能也相较Python2有一定的提升;
     


3.1.5 QS: 什么是解释型语言

思路:解释型,编译型语言特点,举例说明

# 回答案例
1.解释器语言是指,通过专门的解释器对程序逐行翻译成机器码,并立即执行。
特点:每次运行都需要将源代码解释成机器码,运行效率低;
逐行执行,开发效率高;平台提供解释器即可运行程序,
移植性高;python是解释型语言。
2.编译器语言是指,针对特定的平台使用专门的编译器一次性翻译成平台可以识别的机器码,
并可以包装成平台能识别的可执行性程序的格式。
特点:一次编译,永久执行,运行效率高;与平台有关,
移植性较差;需要全部翻译才能运行,
开发效率低;c,c++都属于编译型语言。

3.2 GIL

(Global Interpreter Lock) 全局解释器锁 — cpython解释器

原因:多线程安全问题

运作机制: 一个进程同一时间只有一个线程在真正运行

# 并行并发是针对操作系统执行来说的
并行:绝对同一时间在做多件事情;
并发:短时间段内做多件事情;表面上一个cpu同时做了多件事,本质上是交替进行;
# 同步异步关注的是消息通信机制
同步:程序在发出一个调用时,在没有得到结果之前,该调用就不返回;即调用者主动等待这个调用的结果;
异步:程序在调用发出后,这个调用就直接返回了,调用者不会立即得到结果。被调用者通过状态,通知或回调函数来告知调用者结果;

GIL释放时机: 线程IO操作时,每隔100 opcode 或者 15ms

Python的多线程只对IO密集型计算产生正面效果,而当其中有一个CPU密集型任务时,多线程效率会急剧下降

IO密集型:输入输出较多,占用内存资源不多;
CPU密集型:占用内存资源较多,输入输出较少;

( cython与cpython )

避免 GIL 带来的影响: 多进程 + 协程 ; 其他解释器(很多三方库用不了);

3.2.2 QS:谈谈你对GIL的认识?

答题思路:

概念、背景、特性和使用场景,缺陷,缺陷解决替代方案。

# 回答实例
	(历史背景,概念,特性)1991年Rossum设计Python的时候,当时世界上并无多核处理器的存在,而在Cpython解释器中内存管理不是线程安全的(同一进程中的不仅有代码执行的线程,也有解释器开启的垃圾回收进程),因此GIL应运而生。GIL用来保证同一进程只有一个线程能修改共享资源。因此python多线程大多时候并不能真正的并行,只有获取GIL的线程才能运行,难以发挥多核处理器的性能。
	(使用场景与缺陷)GIL锁的释放时机有两种情况:
	a.线程开始IO操作时,会先释放GIL
	b.计算任务时,解释器每隔100次opcode或15ms释放GIL
	因此python的多线程因为GIL的存在对IO密集型产生正面效果,而但凡有一个CPU密集型任务存在,多线程效率都会大大下降。
	(改善)改善GIL带来的影响:
	1.GIL是cpython的特性,而不是python特性。因为可以使用其他解释器来避免GIL的影响,但是同时也会损失使用很多c语言有用模块的机会。
	2.使用多进程+协程的方式代替多线程充分发挥多核处理器的优势,每个进程中的GIL都是独立的,不会相互影响。
3.2.3 QS:既然有GIL的存在,为什么我们在代码中我们自己经常还选择加锁(普通互斥锁)?

思路:1.理解锁存在的作用 2.理解GIL与普通锁的级别

# 回答实例
	锁是用来保护多线程共享数据的安全的。
	GIL和自己代码中的锁都是这个作用,
	但是不同的是,锁的级别不一样,保护的数据级别也不一样。
	GIL是解释器级别的锁,用来保护解释器级别的数据(如解释器要回收的数据),
	而自己加的普通互斥锁主要是保护用户自己程序级别的数据。

3.3 python对象与存储

  1. 万物皆对象

  2. python 对象存储:

    简言之,Python在内存中缓存了整数和短字符串,容器对象则是进行(赋值)引用;

    (1)同一文件(模块)中变量缓存机制:

    number类型:
    int:-5 ~ +∞
    float: 非负
    bool
    复数:纯虚
    以上值相同时,地址相同;
    
    容器类型:
    只有字符串和空元组相同时,地址才相同,否则不相同;
    

    (2)不同文件(模块)里,部分数据驻留小数据池中(更多指的是这种情况)

    int: -5 ~ 256 ;
    str: 长度为0,1; 只含字母数字下划线; 字符串*1得到的字符串;
    指定驻留:sys模块导入的intern函数导入的数据;
    

    (3)赋值(这也一点也可不说)

    >>> values = [0,1,2]
    >>> values[1] = values
    >>> values
    [0, [...], 2]
    # 此案例解释了python中赋值的操作实际上是对象的引用,该对象的第二个元素就是对自己本身的引用,引用造成了无限循环,故该对象第二个元素的结果是无限的;
    

    Python中的对象的缓存机制以及赋值(实际上是引用)都能起到节省内存的操作;

3.4 python内存回收

3.4.1 引用计数(主)

一个对象会计算自己被引用的次数,每增加一个引用,引用次数+1,相反-1,如果对象作为参数传递给函数,则引用次数+2,因为函数调用时内部有两个属性引用;查看引用计数:sys.getrefcount;当对象间出现相互循环调用时,则不能通过引用计数器进行销毁。

3.4.2 标记清除(辅)

如果两个对象的引用计数都为 1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被 回收的,也就是说,它们的引用计数虽然表现为非 0,但实际上有效的引用计数为 0。所以先将循环引 用摘掉,就会得出这两个对象的有效计数

3.4.3 分代回收(辅)

Python里面将对象回收分为三代;

默认一个对象创建之后,处于0代,0代的检测频率最高,当经过一定的检测频率该对象还存活,则移入下一代;

1代的检测频率比0代低,回收一定次数若该对对象还存活,则迁移至下一代;

2代的检测频率最低;

get_threshold():
	(700, 10, 10)
	700代表 新增的对象数 - 消亡的对象数 > 700时,触发一次0代回收
	第一个10代表,每进行一次0代回收触发一次1代回收
	第二个10代表,每进行一次1代回收触发一次2代回收

手动回收 gc.collect:

弱引用 weakref.ref:

3.4.4 内存池机制

Python按256kb这个分界点分为大内存和小内存:

–操作系统相关操作(第 -1,-2层)

–大内存:malloc函数进行分配,free函数释放内存(第0层)

-小内存调用:PyObject_Malloc 分配内存,不会调用free函数释放,该内存块直接留在内存池中以便下次使用(第1,2层)

–用户对Python对象的操作(第3层)

3.4.5 QS:Python内存泄漏
  1. Python会不会存在内存泄漏
  2. 你在项目过程中有没有遇到性能问题

分析:以上两个问题都可以用内存泄漏来回答;

思路:Python内存管理方式->自动管理分配的弊端->引用计数(相互调用)造成内存泄漏->引子;

# 案例
	(答2:有出现过性能问题。之前的项目中出现过内存泄漏的情况。当时项目代码过程中出现了两个对象相互引用的情况。)
	内存泄漏并非指内存在物理上的消息,而是应用程序分配某段内存后,由于涉及错误,失去了对该段内存的控制,因而造成了内存的浪费
	(答1,2)编程语言的内存管理管理方式分为两种:手动管理和自动管理。手动管理机制的语言代表有C++,而Python是自动内存管理机制的编程语言。自动内存管理带来了开发效率的提升,但同时在某些场景下也存在隐患。在引用计数时,若两个对象相互引用,则会导致两个对象的技术一直≥1而导致两个对象一直不能被回收,从而导致内存泄漏的情况。
	如何避免:
	不使用一个对象时使用:del object来删除一个对象的引用计数就可以有效防止内存泄漏问题。
	通过Python扩展模块gc来查看不能回收的对象的详细信息。
	可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为0来判断是否内存泄漏。
3.4.6 内存管理调优手段
  1. 手动垃圾回收(gc.collect)
  2. 调高垃圾回收阈值
  3. 避免循环引用(手动解循环引用和使用弱引用)
    弱引用:
    (1)引用不会增加对象的引用次数
    (2)弱引用返回被引用的对象,如果没有则返回None
    (3)弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。

3.5 python异常处理

详细讲解参考blog

3.5.1 QS:列举常用异常
# 尽量写自己项目中常见的

IndexError  # 索引超出范围
keyError    # (字典)键不存在
IndentationError  # 缩进异常
AttributeError  # 尝试访问未知的对象属性
typeError  # 不同类型异常操作
NameError  # 尝试访问不存在的变量 

3.5.2 python异常处理方式
  1. 主动抛出异常
raise xxError
  1. 用异常解决程序处理
try ... except xxError  #python3 建议except 后面加异常类(PEP8)
try ... finally  # 无论是否报错,finally代码块都执行
try ... except ... else ... # 没有报错执行else代码块,报错不执行
for/while ... else ... # 如果遇到break就执行else代码块内容,否则else代码块一律不执行
 

3.7 python正则处理

3.7.1 常用正则(必须会!!!)
# 1. email地址
\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}

# 2.url地址
^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+

# 3.手机号(国内)
0?(13|14|15|17|18|19)[0-9]{9}

# 4.电话号码(国内)
[0-9-()()]{7,18}

# 5.腾讯qq号
[1-9]([0-9]{5,11})

# 6.格式日期
\d{4}(\-|\/|.)\d{1,2}\1\d{1,2}

# 7.身份证号
\d{17}[\d|x]|\d{15}

#8.整数 负整数 正整数
-?[1-9]\d*
-[1-9]\d*
[1-9]\d*

# 9.  浮点数 负浮点数 正浮点数
-?([1-9]\d*.\d*|0.\d*[1-9]\d*)
-([1-9]\d*.\d*|0.\d*[1-9]\d*)
[1-9]\d*.\d*|0.\d*[1-9]\d*


3.7.2 常用正则函数比较
findall  匹配字符串中相应内容,返回列表 [用法: findall("正则表达式","要匹配的字符串")]
search   通过正则匹配出第一个对象返回,通过group取出对象中的值
match    验证用户输入内容
split    切割
sub      替换 
subn     替换 
finditer 匹配字符串中相应内容,返回迭代器
compile  指定一个统一的匹配规则

3.8 python函数

3.8.1 python内置函数

尽量挑一些项目用的多的回答,注意不要写到三方模块的函数就行。
诸如:
len(), type(), chr(), dict(), eval(), exec(), filter(), id(), len(), map(), max(), min()等;

3.8.2 需要关注的函数

(1)返回迭代器的函数

### (1)enumerate
enumerate(iterable,[start=0])
功能:枚举 ; 将索引号和iterable中的值,一个一个拿出来配对组成元组放入迭代器中
参数:
    iterable: 可迭代性数据 (常用:迭代器,容器类型数据,可迭代对象range) 
    start:  可以选择开始的索引号(默认从0开始索引)
返回值:迭代器
### (2)zip
zip(iterable, ... ...)
    功能: 将多个iterable中的值,一个一个拿出来配对组成元组放入迭代器中
    iterable: 可迭代性数据 (常用:迭代器,容器类型数据,可迭代对象range) 
返回: 迭代器

(2)高级函数

高阶函数:能够把函数当成参数传递的就是高阶函数

# map
map(func,iterable)
功能:
	把iterable里面所有数据 一一的放进到func这个函数中进行操作 ,把结果扔进迭代器
参数:
	func  内置或自定义函数
	iterable 具有可迭代性的数据 ([迭代器],[容器类型的数据],[range对象])
返回值: 
	返回最后的迭代器


# reduce
reduce(func,iterable)
功能:   
    先把iterable里面的前2个数据拿到func函数当中进行运算,得到结果,
    在把计算的结果和iterable中的第三个数据拿到func里面进行运算,
    依次类推 ,直到iterable里面的所有数据都拿完为止,程序结束
参数:
	func     内置或自定义函数
	iterable 具有可迭代性的数据 ([迭代器],[容器类型的数据],[range对象])
返回值: 
	计算的最后结果


# sorted 
sorted(iterable,reverse=False,key=函数)
功能:  
	对数据进行排序
参数: 
    iterable  : 具有可迭代性的数据(迭代器,容器类型数据,可迭代对象)
    reverse   : 是否反转 默认为False 代表正序, 改成True 为倒序
    key       : 指定函数 内置或自定义函数
返回值:
	返回排序后的数据


# filter
filter(func,iterable)
功能:
    用来过滤的,如果func函数中返回True , 会将这个值保留到迭代器中
    如果func函数中返回False , 会将此值舍弃不保留
参数:
    func : 自定义函数
    iterable : 具有可迭代性的数据(迭代器,容器类型数据,可迭代对象)
返回值: 
	返回处理后的迭代器

QS: 一行代码写出 1+2+…+10*8 ?
答:reduce(lambda x,y:x+y,range(1,10**8))

3.8.3 迭代器&生成器
  1. 迭代器是系统内置的,不可重写;
    生成器本质上是迭代器,但是是可写的;

  2. 生成器可以用两种方式创建:
    (1)生成器表达式 (里面推导式,外面圆括号)
    (2)生成器函数 (用def定义,里面有yield)

QS1: yield 和 yield from 有什么区别?

# 示例回答
yiled from : 将一个可迭代对象变成一个迭代器返回。底层是一个转化成迭代器的操作。
yield :用在生成器定义中,每次调用生成器取值触发yield 语句,从生成器中返回值(类似于 函数中 return 但是又不完全一样)。

QS2: yield 和 return 有什么区别?

示例回答:
 yield用在生成器定义中,return用在函数定义中。
 两者的作用都是返回值。
 yiled 返回值会记录位置,下一次调用生成器会直接从上次记录的位置从下走;
 return 返回值直接终止函数,下次调用函数从头开始。

QS3: next和send区别

# 示例回答
 都是用在生成器外调用生成器取值,next只能取值,send不仅能取值还能发送值;
 send 取值 和 发送值是错开的,取值从第一个开始,发送值从 第二个 send开始;
send发送从发给第二个yiled开始,因此生成器的最后一个yield 接受不到 send 的值。(理解成 yiled 和 send 交换值,但是 第一个 send 没给,导致 最后一个 yiled 没收到。)

QS4:利用生成器改写斐波那契数列(***写斐波那契必须掌握)

def mygen(maxlength):
	a = 0
	b = 1
	i = 0
	while i < maxlength:
		yield a
		a, b = b, a+b
		i += 1
		
gen = mygen(30)

for i in range(10):
	print(next(gen))

递归实现斐波那契数列

class Solution():
    def Fibnacci(self,n):
        if n <= 0:
            return 0
        if n == 1:
            return 1
        return self.Fibnacci(n-1) + self.Fibnacci(n-2)
3.8.3 函数参数

QS: 说说 *args 和 **kwargs 的作用:

# 示例回答:
(1)函数定义处
*args : 普通收集参数,收集普通参数,放入元组;
**kwargs: 关键字收集参数,收集关键字参数,放入字典;
(2)函数调用处
*args : 解包实参,列表或者元组类型;
**kwargs: 解包实参,字典类型;

3.9 python模块

3.9.1 python内置模块

os, sys, time, datetime, random, re, math, pymysql, functools, socket, json, urllib, threading, logging, string

3.9.2 python第三方模块

注:需要通过pip安装;
django, flask, tornado, numpy, pandas, xadmin, pillow, requests, virtualenv, worldcloud

3.10 python面向对象

面向对象详细参考

3.10.1 装饰器

装饰器详细参考

  1. QS:@classmethod, staticmethod, @property的含义与用法
# 示例回答

三者都是用在类定义中。都是为类量身定做的。
@classmethod 类方法装饰器
作用:将一个方法绑定到类,该方法必须有类参数cls;可以通过类调用。

@staticmethod 静态方法修饰器
作用:静态方法,不需要任何参数,跟普通函数函数一样操作;对象和类都直接可以调用。

以上两者都可以通过不通过实例化对象便可访问到类属性和方法,是python对象节省空间的操作。(对象要调用以上两类方法,也能调用)。

@property 特殊属性修饰器
作用:把方法当属性一样操作,实现对属性的修改,设置,删除等操作,达到节省代码,安全检查的目的。
  1. 手写装饰器相关
    (1)实现一个简单的装饰器,实现运行函数过程中打印出函数执行时间。
import time

def metric(fn):	# 设计装饰器
   # 对程序进行封装
   def wrapper():
       # 在程序执行之前记录一次时间
       start_time = time.time()
       # 需要执行一次程序  
       fn()
       # 在程序执行之后记录一次时间
       end_time = time.time()
       # 两次时间相见,得到的差就是程序执行时间
       print('耗时:{:.8f}s'.format(end_time - start_time))    
   return wrapper

@metric
def ceshi_func():
   pass

ceshi_func()

3.10.2 设计模式
3.10.2.1 单例模式
  1. python的模块本身就是一个单例模式;
  2. 非线程安全的单例模式(***必会)
class Singleton(object):
	__instance = None

	def __new__(cls, name, age):
		# 如果类属性__instance的值为None,那么就创建一个对象
		if not cls.__instance:
			cls.__instance = object.__new__(cls)
		# 如果已经有实例存在,直接返回
		return cls.__instance

a = Singleton("Zhangsan", 18)
b = Singleton("lisi", 20)

print(id(a))
print(id(b))

  1. 线程安全的单例模式(***必会)
import threading

"""
线程安全的单利模式

紧跟with后面的语句被求值后,返回对象的 __enter__() 方法被调用,这个方法的返回值将被赋值给as后面的变量。
当with后面的代码块全部被执行完之后,将调用前面返回对象的 __exit__()方法
""" 
def synchronized(func):
    func.__lock__ = threading.Lock()

    def lock_func(*args, **kwargs):
        with func.__lock__:
            return func(*args, **kwargs)

    return lock_func

class Singleton(object):
    instance = None

    @synchronized
    def __new__(cls):
        # 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象
        if not cls.instance:
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance

3.10.3 面试题合集

QS1: 元类__metaclass__是什么?何时被创建?什么时候被使用?

# 示例回答:
(1)元类;python中的类其实本质上也是一种对象,可以称为类对象,而类的创建就是通过元类来完成的。可以这么说,元类就是类对象的类;
(2)创建与使用:
使用: 创建类的时候,会触发元类的__init__,来调用元类。

QS2: 实例属性和类属性?

# 示例回答:
1. 类属性:在类中直接定义的属性,归类所有;
2. 实例属性:在__init__函数中定义,实例化对象之后绑定给对象的属性,归各对象所有;
注意:类定义时,如果类属性与对象属性同名,对象优先找到自己的实例属性(覆盖类属性),但是通过类还是可以的访问到类属性。

3.11 数据类型与运算符顺序

  1. 类型强转规则

2.运算符优先级:算位比生成逻

4 系统编程

4.1 进程

进程:资源分配和调度的最小单位

多进程适用场景: CPU密集型任务

多进程缺陷:

  1. 创建和切换代价高
  2. 进程间通信效率相对低
  3. 资源消耗大

父子进程: fork时复制逻辑地址(物理地址 = 逻辑地址 + 基址)

4.1.1 QS:了解COW吗?
# 示例回答
-(copy on write): 写时复制

	一种推迟甚至避免数据拷贝的技术。刚开始内核并不会赋值整个地址空间,而是让父子进程共享地址空间,父子进程都有独立的地址空间,只有在写时才会复制地址空间;资源的复制只有在需要写入时才会发生,在这之前都是以读的方式和父进程共享资源。
	这样在页跟不会被写入的场景下,fork()立即执行exec(),无需对地址空间进行复制,fork()的实际开销就是复制父进程的一个页表和为子进程创建一个进程描述符。也就是说进程空间中各段的内存内容发生变化时,父进程才将其内容复制一份给子进程,大大提高了效率。

4.2 线程

线程:系统运算调度的最小单位

多线程

同一进程内多线程资源共享

适用场景:IO密集型任务

缺点:

  1. 因为GIL锁的存在,cpython中的多线程是伪多线程,难以实现并行
  2. 线程之间的频繁切换造较高的代价
4.2.1 QS:如何保障线程安全

思路:1.线程安全概念 2.保障线程安全措施

# 回答案例
	线程安全是指采用加锁机制对多线程共享数据进行保护,防止数据污染。
	措施:
	1. 使用线程安全的类;
	2. 使用lock锁;
	3.多线程并发情况下,线程共享的变量改为方法局部变量。

4.3 协程

协程:用户级别的轻量级线程,协程的调度完全由用户自己调度

一个线程同一时刻只有一个线程在运行,由这个线程来决定执行哪一段程序,cpu控制权完全由这段程序自己选择交出来,不由外界控制。

相比多线程优势:

1.子程序切换不是线程切换,而是程序自己控制;因此节省了线程切换造成的开销;和多线程比,线程数量越多,协程的性能优势越明显;

2.因为一个线程只有一个协程,不需要多线程的锁机制,因此相比多线程具有更高的执行效率。

适用场景:IO密集型任务

利用多核CPU的优势:多进程+协程

4.3.1 QS:协程的实现原理

4.4 QS:对多进程,多线程,协程的理解

思路:

(1)介绍进程概念,多进程使用场景,多进程缺点(引出多线程)

(2)介绍线程概念,多线程特点,多线程使用场景,多线程缺点(引出协程)

(3)介绍协程概念,协程的优点(相比多线程),协程利用多核CPU的性能优势;

#案例
	
	协程的概念最早提出于1963年,但由于其并不符合当时崇尚的的“自顶向下”的程序设计思想,未能成为当时主流编程语言的一部分;

      20世纪60年代,进程的概念被引入,进程作为操作系统资源分配和调度的基本单位,
      多进程的方式在很长时间内大大提高了系统运行的效率,
      虽然中间产生了Copy-On-Write等技术的出现,
      但进程的频繁创建和销毁代价较大、
      资源的大量复制和分配耗时仍然较高,
      于是80年代出现了能够独立运行的单位--线程。
      多线程之间可以直接共享资源,
      同时线程之间的通信效率又远高于进程间,
      将任务并发的性能再次向前推进了一大步。
      不过多线程也有其不足的地方,虽
      然线程之间切换代价相较多进程小了很多,
      但一些场景下线程CPU时间片的大量切换
      其实是做了很多不必要的无用功,
      特别是Python中因为GIL锁的存在,
      其多线程很多时候并不能提供程序运行效率,
      于是协程的概念又开始发挥了作用,
      是一个线程在执行,
      只有当该子程序内部发生中断或阻塞时,
      才会交出线程的执行权交给其他子程序,
      在适当的时候再返回来接着执行。
      这省去了线程间频繁切换的时间开销同时也解决了多线程加锁造成的相关问题;

      具体的生产环境中,Python项目经常会使用多进程+协程的方式,
      规避GIL锁的问题,充分利用多核的同时又充分发挥协程高效的特性。

4.4.1 QS:python中协程的实现原理(深度)

思路:

# 回答案例

5 DB

5.1 DB-mysql

5.1.1 QS:sql 与 nosql 对比

思路:1.sql和nosql基本概念 2.特性对比,优劣势分析 3.列举常用数据库

# 回答案例
	(1)sql即指关系型数据库,指采用了关系模型来组织数据的数据库。关系模型指的是二维表模型,且表之间存在联系。可以说关系型数据库是由二维表及其之前的联系组成的数据组织。数据库特性必须具备ACID特性:原子性(aotumatic),一致性(consistency),隔离性(isolation),持久性(durability)。常用的sql有:mysql,mariadb,oracle,sql server,sqlite.
	sql优势:
	1.易于维护:都是用表结构,格式一致;
	2.使用方便:sql语句通用,可用于复杂查询;
	3.复杂操作:支持SQL,可用于多表之间的复杂查询;
	sql劣势:
	1.面对高并发读写时,硬盘IO是一个很大的瓶颈;
	2.用于海量数据的读写时,效率较低;
	3.多表的关联查询导致性能较差;
	(2)nosql即指非关系型数据库,是一种数据结构化存储方法的集合,即非关系型的,分布式的,可以是键值对,文档等。且一般不用遵循ACID原则。常用的sql:redis(key-value),mongdb(文档)。
	nosql优势:
	1.格式灵活:存储数据的格式可以是key,value格式,文档形式,图片形式等;使用灵活,应用场景广泛。
	2.速度快:nosql可以使用硬盘或者随机存储作为载体,而关系型数据库有只能使用硬盘。
	3.高扩展性
	4.成本低:开源软件,部署简单
	nosql劣势:
	1.不提供sql,操作成本较高
	2.一般来说无事务处理
	3.数据结构复杂多样,不适合复杂查询
	(3)总体上来说,sql适合用于解决复杂的业务功能需求;nosql适合用于大数据|海量数据。
5.1.2 QS:mysql有哪些存储引擎以及区别?

思路:回答至少3个存储引擎(myisam,innodb必答)以及各自特性

# 回答案例
	存储引擎(table_type),即表结构,表类型。采用合适的存储引擎能有效提高数据库的性能和访问效率。mysql常用的存储引擎有myisam,innodb,memory.
	(1)myisam:
	1. Mysql5.5 版本之前的默认引擎
	2. 不支持事务,不支持外键,可以没有主键
	3. 表级锁,访问速度很快,适用于读写较多,更新删除很少的场景
	4. 数据和索引是分开的,myisam在磁盘上存储三个文件 .frm 存储表定义 .MYD 存储数据 .MYI 存储索引
	(2)innodb:
	1. 首选引擎,Mysql5.5 版本之后的默认引擎
	2. 提供提交,回滚,支持事务安全(ACID),支持外键,必须含有主键(没有指定还会自动生成)锁的粒度减小到行级锁
	3. 支持崩溃修复和并发控制,适用于事务完整性要求较高以及频繁更新,删除的场景
	(3) MEMORY:
	1.该存储引擎的存储介质是内存。
	2.数据读取较快,但是安全性不高,并且不支持变长的数据类型(如 varchar, text)
	3.受内存大小影响存储空间
	
5.1.3 索引

‘数据库的索引可以类比于书的目录’

5.1.3.1 索引的必要性(优势&作用)
  1. 非必要
  2. 数据具有一定规模
  3. 需要反复查询
  4. 索引不是越多越好,方便查询,但是删除或者创建的时候也需要维护索引(类比书的目录)
5.1.3.2 索引缺陷(劣势)
  1. 可能占用内存空间
  2. 读写性能可能下降
5.1.3.3 B+树与B树(拓展,方便理解各种索引)
(1)B树特性:
1. 关键字分布在整棵树的所有节点
2. 任何一个关键字出现且只出现在一个节点中
3. 搜索有可能在非叶子节点结束
4. 其搜索性能等价于在关键字全集内做一个二分查找。
mongoDB的索引使用B树的原因:
MongoDB不是传统的关系性数据库,而是以Json格式作为存储的nosql,目的就是高性能,高可用,易扩展。首先它摆脱了关系模型,所以范围查询和遍历查询的需求就没那么强烈了,其次Mysql由于使用B+树,数据都在叶节点上,每次查询都需要访问到叶节点,而MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问
优点:时间复杂度(O(1)-O(logN)) --海量数据查询性能优于B+树
(2)B+数特性:
1.所有关键字都出现在叶子节点的链表中,且链表中的关键字是有序的
2.搜索只在叶子节点命中
3.非叶子节点相当于是叶子节点的索引,叶子结点是存储关键字数据的数据层。
mysql的索引结构是B+树
B+树相对于B树做索引的优势:
1.B+树的磁盘读写代价更低。B+树的内部节点并没有指向关键字信息的具体指针,因此其内部节点相对B数更小,因此同样盘块能容纳的关键字数量更多,可一次性读入内存的需要查找的关键字更多,提升了IO读写的效率。
2.B+树的查询效率更加稳定。由于所有数据都存在于叶子结点。所有关键字查询的路径相同,每一个数据的查询效率相当。
3.B+树只需要遍历叶子节点就可以实现整棵树的遍历。(mysql使用B+数做索引的主要原因)


追加:关键字是惟一能标识一个记录的数据项。比如一般设置主键值为关键字。
5.1.3.4 QS:什么是覆盖索引?什么是覆盖索引失效?
# 回答案例
	(1)覆盖索引
	指从辅助索引中就能够获得到需要的记录,而不需要查找主键索引中的记录;使用覆盖索引的一个好处是因为辅助索引不包括一条记录的整行信息,数据量较聚集索引要少,可以减少大量IO操作。
	(2) 覆盖查询失效
	1.select选择的字段中含有不在索引中的字段,即索引没有覆盖全部的列
	2.where条件中不能含有对索引进行like的操作
5.1.3.5 主键索引与辅助索引

主键索引:根据主键建立索引,不允许重复,不允许空值。

辅助索引:非主键索引(或者说非聚集索引)

由于mysql存储引擎的不同,会有区别,下面分存储引擎来说。

(1)innodb

主键索引:

主键索引中,叶子节点既存储了主键值,又存储了行数据。

辅助索引:

叶子页中保存主键值,通过这个主键值来回表查询完整记录。

进行了二次查询,不如主键索引效率高。(性能差别较大)

(2)myisam

主键索引:

mysiam使用B+树作为索引结果,叶节点的data域存放的是数据记录的地址。

mysiam索引文件和数据文件是分离的。叶子节点保存的是对应行的物理位置,通过该值,回表查询,得到一行完整的记录。同时每个叶子也保存了指向下一个叶子页的指针。方便叶子节点的范围遍历。

辅助索引:

myisam中,主键索引和辅助索引在结构上没有任何区别,只是主键索引要求Key是唯一的,而辅助索引的key可以重复。

5.1.3.6 聚集索引与非聚集索引
  1. 聚集索引:数据行的物理顺序与索引(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。

    innodb会选择主键作为聚集索引,如果没有选择不含null的唯一索引,如果再没有则会依靠内置自动生成。

    所以可以说聚集索引一般情况下就是innodb存储引擎下的主键索引。

    接下来就是说 innodb,myisam的主键索引,辅助索引;

  2. 该索引中索引的逻辑顺序与行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。

    mysql中除下innodb的主键索引都是非聚集索引。

5.1.3.7 其他索引概念
  1. 普通索引:

    用表中普通的列构建的索引,没有任何限制。

  2. 唯一索引:

    唯一索引列的值必须唯一,但允许空;如果为组合索引,列值组合必须唯一。

  3. 组合索引|联合索引

    用多个列值组合构建的索引,这多个列中的值不允许有空值。

5.1.4 Mysql版本变迁

mysql5.7(2013) 新变化特性:

  1. 灵活性上开始支持json串格式字段;
  2. generated column:数据列有其他数据列计算得到;
  3. 性能提升:自动检测只读事务,支持并行复制;

mysql8.0 新变化特性:

  1. 像mogodb般支持文档存储;
  2. 性能大幅度提升,速度比5.7快两倍;
  3. 默认的编码改为了utf8mb4;
5.1.5 mysql事务
5.1.5.1 事务特性

(ACID)

  1. 原子性(Atomicity):事务像原子一样不可分割,要不全部成功,要不全部失败,不能再分了;
  2. 一致性(Consistence):数据不会发生损失(类似能量守恒);
  3. 隔离性(Isolation): 事务之间互不干扰;
  4. 持久性(Durable):操作结果是永久的;
5.1.5.2 事务隔离级别
  1. RU(read uncommited): 未提交读;

    – 隔离效果最差

    –缺陷:脏读 B事务可以读到A事务未提交的数据

    –针对修改操作

  2. RC (read commited) :提交读

    –不可重复读:B事务本身未提交,但是可以读到A事务已经提交的事务

    –针对修改操作

    –一个事务里连续两次读取同一行数据,结果不一样

  3. RR(repeatable read): 可重复读 ;(默认隔离级别)

    – 幻读 | 虚读(执行同一个select语句读到的数据行发生了变化)

    – 针对删除、新增的操作

    – 用到了间隙锁解决

    –使用MVVC(多版本并发控制)解决

  4. SE(Serializable):可序列化

    – 相当于串行执行

    缺陷:性能差

5.1.6 QS
5.1.6.1 innodb主键索引一定比其他索引快吗
# 回答案例
参考innodb索引采用B+树结构,叶子节点中存放了整张表的数据,
而辅助索引叶子节点放置的是指向数据表位置的书签,回表二次查询,
一般来说同样大小的内存主键索引存储的索引数目要比辅助索引要少,

因此主键索引不一定比辅助索引快。
5.1.6.2 mysql中主键为什么一般推荐使用自增的整数
# 参考回答
1.相比字符串更适用于数据行的增加,数目自增即可,而字符串可能需要插入导致原来存在的行后移
2.整数相比字符串占用空间更少
5.1.6.3 innodb 一定要声明主键吗?
# 参考回答
innodb一定存在主键,但是不一定需要声明主键,如果不存在,mysql会内置生成一个默认主键。
5.1.6.4 了解间隙锁吗?
# 参考回答
间隙锁:封锁记录之间的间隔。将事务涉及到记录前后锁住,不允许插入数据,固定锁住的行数不变。主要用于RR级别,解决幻读,虚读问题。
5.1.6.5 怎么了解密集索引,稀疏索引?
# 参考回答
5.1.6.6 主键如何选择

思路:1.主键的概念与好处 2.innodb使用主键注意事项 3.不设置主键会发生什么

# 参考回答
	主键是相对于Innodb存储引擎来说的,MyiSAM可以没有主键。
	主键是数据表中唯一标志数据行的列。mysql中每张表只能有一个主键。主键的属性是不能为NUll且不能有相同值,通过主键可以强制表的实体完整性。主键主要用于其他表的外键关联,以及本记录的修改和删除。
	在选择主键上,我们一般选用自增ID。这是因为InnoDB的底层存储机构是把主键索引和数据存储在一起,如果不使用自增ID,删除数据时可能会发生大量的数据页迁移(因为主键索引在磁盘上和物理存储顺序一致,插入和删除顺序更快)。如果因为某些特定的原因不使用自增ID,则推荐使用整数优先于字符串,原因是节省内存占用和便于数据删除。
	InnoDB如果不声明主键,则会自动生成一个6byte空间的自增型主键。
5.1.6.7 外键的好处和劣势
# 参考回答
(1)优势
1.保证数据的完整性与关联性,避免数据冗余(主要)
2.增加数据库设计的可读性,结构更清晰
3.外键在一定程度上说明业务逻辑,使数据库设计周到
(2)劣势
1.外键删除,修改,更新波及范围较广,容易造成数据误删或数据无法删除
2.外键会降低数据操作如 update,delete,insert的性能,海量数据下读写性能严重下降
3.高并发情况下容易因为外键造成死锁
(3)总结
在生产环境中,有时允许一定的数据冗余和容错,较少使用外键。
5.1.6.8 ORM有什么优势,弊端?
# 示例答案
(1)优点
1.开发效率更高
2.数据访问抽象而轻便
3.支持面向对象封装
(2)缺点
1.消耗更多的内存,程序执行效率降低
2.思维固定化
3.部分情况orm会引起一起反作用优化
(3)总结
具体是否使用ORM还是sql是项目具体情况而定,因开发人员对orm看法而异;个人建议如果项目追求较高的数据库操作性能,且开发人员开发水平尚可,推荐使用原生sql。
5.1.7 MVVC :多版本并发控制

– 基本原理: 某个时间点快照

– 主要用于: RR RC 级别

  1. 每行数据都存在一个版本
  2. 修改时Copy出当前版本随意修改,各个事务之间无干扰
  3. 每次数据更新时都更新该版本
  4. 保存时比较版本号,如果成功,commit则覆盖原记录,如果失败则放弃copy(rollback)

–因为mysql中修改记录时写锁(写就上锁)的存在,MVVC 只能解决 读-写阻塞的问题,不能解决 写-写 阻塞的问题

5.1.8 死锁

由于竞争资源或者通信造成的进程间阻塞的现场成为死锁

死锁四大条件:互斥,不可剥夺,请求与保持,循环等待。

预防死锁四类方法:

  1. 预防:通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个条件,来防止死锁的产生。
  2. 避免:在资源动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生。
  3. 检测:设置检测机构及时检测死锁的发生,并采取适当措施加以清楚。
  4. 解除:采取适当方法将进程从死锁中解除出来。
5.1.9 乐观锁与悲观锁

​ 乐观锁和悲观锁都是并发控制主要采取的技术,目的都是为了保证数据库中的数据的完整性和一致性。

  1. 悲观锁:对数据被外界修改持悲观态度,在整个数据处理的过程中,将数据处于锁定状态。

    – 依靠数据库提供的锁机制来实现;

    – 对任意数据进行修改前,先尝试为该记录加上排他锁;

    – 加锁失败,说明该记录正在被修改,那么当前查询要等待或抛出异常;

    –加锁成功,那么可以修改数据,事务完成后解锁;

    – 优势在于保护数据成本要比低于事务回滚;

    –劣势:给数据库中增加额外开销,增加死锁机会;在只读型事务中,没必要加锁,加锁会增加系统负载,且降低并行性能;

    1. 乐观锁:对数据修改乐观,认为一般情况不会数据冲突,只有在数据提交更新才对数据冲突是否进行检测。

    –不依靠数据库的锁机制

    –读取数据一同读出版本标识,数据更新,版本标识更新;

    –提交更新,判断数据库表对应记录的当前版本与第一次取出来的版本进行比较;

    –版本相同,则赋予更新;否则 默认为过期数据;

    –mvvc是一种乐观锁

5.1.10 mysql分表,分区,分库

(1) 分表

  1. 由程序控制:InnoDB

    比如user表,10亿用户,每张表的数据上线为1000万,则可以分成200张表;

    user_01,user_02,user_03…user_200;

    程序里面封装一层,由程序控制操作哪个表;

  2. 由储存引擎控制:myisam

    myisam 集合 merge 存储引擎

    user_01,user_02,user_03…user_200;

    代码使用时不需要关注操作哪个表,由merge自动判定;

(2)分区

表存储时分块存储,记录每块的地址,查询时,根据分块的策略,快速定位到哪一块。

(3)分库

a.在同一个节点mysql服务中:存在多个同类型的数据库处理同一种业务(db名字不一样),如:baidu,aliyun,huaweiyun

b.在不同节点mysql服务中,db名字可以一样:集群

5.1.11 binlog
  1. 概念:binlog是一个二进制格式的文件,用于记录用户对数据库更新的SQL语句,并将这些SQL语句记录到binlog中,但是不会记录数据库查询操作。

  2. 应用场景

    • 主从复制
    • 数据的增量恢复
    • 增量获取数据库数据
    • 实现数据的订阅,消费
  3. 总结:

    出于安全考虑,我们一般需要打开binlog日志;但是在生产环境中一些频繁操作容易导致binlog日志过多而导致空间不足,因此需要注意定期清理。

5.1.12 mysql主从复制
  1. 原理

    主从复制7步曲:

    1. 主数据库写入数据之后, 会有data changes(数据变化)记录
    2. 有变化记录之后,将增删改的一些sql语句记录到本地的Binary log(二进制日志)中
    3. 从库会一直开启着一个线程(IO线程)
    4. 通过线程去读取这个二进制日志的内容
    5. 从库会将数据写入到自己的Relay log(中继日志)中
    6. 从库会将中继日志中的操作转化为SQL thread(SQL语句)
    7. 通过转化的SQL语句写入到自己的数据库, 两边的数据就一致了

    – 主开启一个io线程,从开启一个io线程,一个sql线程

  1. 作用

    • 读写分离,主库进行数据修改,从库只负责数据读取。
    • 数据的实时热备。
5.1.12.1 QS:主从不一致怎么解决

思路:主从不一致原因;解决方案

# 示例回答
(1) 主从不一致原因
a.网络延迟;
mysql主从复制是基于binlog的异步复制,通过网络传送binlog,网络延迟有极大几率造成主从不一致
b.主从机器负载不一样;
主从复制需要主数据库上面开启一个IO线程,而从数据库上开启一个IO线程,一个sql线程,但出现负载很高时,造成任何一个线程出现资源不足,会出现主从不一致的情况。
c.max_allowed_packet(最大包)设置的数据量主从不一致,可能导致一个大的sql能在主库上执行,而不能在从库上执行。
d.binlog.reflog文件损坏。
(2)解决方案
a.对数据同步性要求不严格且数据量差异不大,可忽略不一致继续主从同步。
b.重新做主从同步,适用于主从相差较大,或要求数据完全统一的情况。
c.使用第三方工具。如 pt-table-checksum检测,pt_table_sync修复注册不一致。
5.1.13 QS:sql注入是如何产生的,怎么预防

思路:1.sql注入概念 2.sql注入预防措施

# 参考回答
(1)sql注入就是把sql命令插入到web表单或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
注入语句:select * from news where id =1 or 1=1(“or 1=1”为非法信息,短路会造成where必为真)
(2)
1.使用预处理,让Pymysql去拼接字符串,而不是python自己去拼接字符串
sql = 'select * from news where username = %s and password = %s'
rows = cursor.executed(sql, (name, password))
2.输入验证:前后端加验证,检查用户输入的合法性
3.加密处理:将用户登录名称,密码等数据加密保存。
5.1.14 sql优化(建议直接背把)
  1. 对查询进行优化,应尽量避免全表扫描,首先应考虑在where和order by涉及到的列上建立索引;
  2. where语句:
    • 使用通配符,通配符不要放匹配的前面
    • 放弃where字句中使用or来连接条件,否则mysql将放弃索引来进行全表查询;
    • 放弃where字句中使用字段表达式,否则mysql将放弃索引来进行扫描;
    • 谨慎使用 in 和 not in,可能造成全盘扫描(between代替in);
    • 放弃where字句中对字段进行函数化操作;
    • 禁止在 where 语句中 = 左边进行函数,算术运算或其他表达式运算,容易导致系统无法使用索引;
  3. 使用索引字段做为条件,如果该索引是复合索引,则必须要用到该索引的第一个字段作为条件才能使用到该索引;
  4. 索引不是越多越好,索引一方面提高了select的效率,但是过多会降低 update 和 insert 的效率。一个表的索引最好不要超过6个;
  5. 尽量使用数字型字段,除非该字段必须设计成字符串字段;
  6. 字符串字段尽可能使用 char 代替 varchar;
  7. 使用连接(JOIN)来代替子查询(sub-Queries);
  8. 定长字段放前面,变长字段放后面(尽可能小的改变树状结构高度);
  9. 尽量使用组合索引代替单个索引;
  10. 重复的,字段值少的字段值不适合做索引,如性别;

5.2 DB-mongoDB

5.2.1 存储引擎
  1. MMAP

  2. WiredTiger

    3.2版本默认存储引擎改为了wiredtiger

    特性:

    a.文档级别锁,解决了锁粒度过大的问题

    b. 磁盘数据压缩

    c. 删除数据时,数据会立即删除

    d. mongoDB3.0在多线程,批量插入场景下相比2.6有4-7倍的增长

  3. RocksDB

    特性:

    顺序写入:LSM Tree结构,随机写入转换为顺序写入

    速度稳定:和WiredTiger相比,写速度稳定

引擎选用:

  1. 前期(数据规模不是特别大时):wiredtiger 在高并发读写时性能比RocksDB好
  2. 后期(数据规模很大时):推荐RocksDB
5.2.2 版本变迁及特性
  1. 3.0 15年

    1.文档级别

    2.可以真正删除

    3.性能大幅提升:4-7倍提升

    4.磁盘数据允许压缩:加入1条数据1k,1M条数据存储,实际占用的大小<1G

  2. 4.0 2018年6月

    –多文档事务支持

    –4.2版本开始支持分片集群分布式事务

5.2.3 mongodb扩展

1.分片(分区,分表结合) --集群中使用 --垂直扩展

–对外被调用时,程序员不需要关注到底如何分片,db会自动判断到底在哪里

–比如集群里面有10个节点,创建好分片后,每个节点都会自动创建相应的文档,会自动选择哪个节点

2.分库 分collection --水平扩展

5.2.4 mongodb索引结构

参考上面B树B+树的对比

5.3 DB-redis

5.3.1 数据类型

string, list, set, zset, hash

5.3.2 使用场景
  1. 缓存高频次访问的数据,降低数据库IO
  2. 分布式架构,做session共享
  3. 利用zset类型可以存储排行榜
  4. 利用set存储项目中不能重复的数据,如投票过滤候选人
  5. 利用List做简易MQ,秒杀,医院的挂号
  6. 利用哈希保存项目中的字典数据,如商城的购物车
5.3.3 存储机制(了解)

list键:双向链表

hash键:字典dict

zset键:跳跃表zskiplist

5.3.4 Bloom Filter算法
原理:对于有n个元素的集合S={x1, x2,……,xn},我们用k个哈希函数(h1,h2,……,hn),分别将S中的每一个元素映射到一个m位的位数组(bm-1bm-2……b1b0)中。该位数组在初始化时所有置为0,每当用哈希函数映射到该位时则将该位置为1。对于已经置为1的位则不在反复置1。

特点:
可以判断某个数一定没出现过,不能判断一个数一定出现过  --哈希冲突
使用的内存越小,则哈希冲突的概率越大

优势:查找性能较好
线性结构: O(n)  二叉树: O(logN)  Bloom: O(1)
5.3.5 QS:了解redis缓存穿透,缓存击穿,缓存雪崩?
# 参考实例
缓存穿透:指访问一个不存在的key,缓存起不到作用,请求会穿透到DB,流量大时DB会挂掉。
比如恶意攻击每次去查询一个不存在的数据,会给数据库造成极大的压力。
缓存雪崩:大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
比如:双十一过后的热门商品。
缓存击穿:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增,就像在一个屏障上凿开了一个洞。
比如在电商项目中,超级火爆的爆款商品便有可能出现这种情况。

 解决方案:
    缓存穿透:
       1. 采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;
       2. 访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。

    缓存雪崩:
         可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。

    缓存击穿:
         1. 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。
         2. 可以考虑针对于特殊的容易造成击穿的key,采用缓存不过期的策略。

5.3.6 redis如何做持久化

redis提供两种方式做持久化,一种是RDB持久化,灵一直用是AOF(append on file)

  1. RDB持久化

    原理:将redis内存中的数据定时dump到磁盘上;实际操作过程是fork一个子进程,现将数据集写入临时文件,写入成功后,替换之前的文件,用二进制压缩文件。

  2. AOF持久化

    原理:以日志的形式记录数据库的每一个写,删操作,不会记录查询操作;以文本的形式记录下来,可以打开文件查看详细的记录。

  3. 对比:

    RDB性能较好,但是故障易造成较大数据损失(指定dump间隔内的数据)。

    AOF更可靠,不易丢失数据,但是会影响redis的性能。

    总之,允许一定容错来追求性能,选用RDB,但要求数据的可靠性和一致性,选用AOF。

5.3.7 QS:redis内存不够怎么办

思路:redis和内存的关系;解决方法

# 参考回答
当redis内存不够的时候,可以参考以下几种解决思路
1.看能否从代码逻辑上变化来减少redis需要存储的数据;
2.使用redis集群,如redi-cluster;
3.引入bloom过滤器,需要注意业务是否运用bloom的场景;
4.数据过期机制,思考业务中是否有些数据可以设置过期机制,过期清除;
5.考虑切换其他数据库,如最新mongodb8.0,基于ssd硬盘存储

5.4 QS:MySQL和MongoDB区别

回答思路:

–时间线:

  1. 2015年,MySQL5.7发布,开始支持Json;
  2. 2018年,MySQL8.0发布,支持文档存储;
  3. 2018年,MongDB4.0发布,支持多文档事务;

–空间线

MySQL存储引擎及底层存储B+树

Mongdb存储引擎及底层存储B树

DB选型考虑因素

–思路串联MySQL、MongDB存储引擎及结构介绍->介绍MySQL和MongDB区别(夹杂时间线)->DB选型要素->埋引子(事务相关)

–回答案例

# 案例
	MySQL是关系型数据库的一种,其存储引擎有MyISam,InnoDB,Merge等,
	目前在业界大多使用的是支持事务的InnoDB存储引擎,从15年MySQL5.7开始,
	MySQL开始支持json格式(开始向nosql数据库靠近);
	而MongoDB去年8月份发布的4.0版本开始支持多文档事务,
	当前其默认的存储引擎wiredtiger性能非常卓越;
	mysql和MongDB数据发展越来越类似,都在取其精华,
	而在具体的数据库选型时,两种DB底层实现机制可能是一大考虑因素。
	Mysql索引底层是用B+树实现的,而MongDB则是采取的B树,
	B树的结构也就决定了MongDB在海量读写的情况下性能比mysql卓越(时间复杂度是 O(1)-O(logN)),
	而MySQL B+树的特性也决定了MySQL更适合多区间范围查询的业务需求。
	当然在具体的项目DB选择中,我们还需要考虑到成本,团队成员的意向,技术栈等情况。
	总体上相对来说,如果是海量数据高并发读写,从技术的角度推荐使用MongDB,
	如果数据结构相对统一,同时对事务有较高要求,个人倾向于MySQL。

5.5 行式数据库与列式数据库

行式数据库和列式数据库主要是存储机制的不同

列式数据库是按列存储数据

优点:

  1. 极高的装载速度(最高等于所有硬盘的IO总和)
  2. 适用于大数据而不是小数据
  3. 实时加载数据仅限于增加
  4. 高效的压缩率,便于节省存储空间,计算内存,CPU
  5. 适合做聚合操作

缺点:

  1. 不适合小量数据
  2. 不适合随时更新
  3. 批量更新各异,试具体数据库而异
  4. 不适合实时更新和删除

5.6 数据库范式

# 实例答案
1. 第一范式:数据表中的每一列都是不可再细分的原子列
比如一个字段为学生,数据内‘李华1010’,实际上包含了姓名和编号,则不满足第一范式
2. 第二范式:确保数据库的每一列都与主键相关,如果是联合主键,则不能只是与主键的一部分相关。简言之,一张表只能保存一种数据。
如一张订单信息的数据表,将订单编号和商品编号作为联合主键,而商品名称,商品单价等这些信息不与主键相关(联合主键不能只关联部分),则满足第二范式。
3.第三范式:确保数据表中的每一列数据都与主键直接相关,而不是间接相关。
比如订单数据表可以将客户编号作为外键,但是不能直接在订单表中添加客户信息。

以上范式在实际开发过程中并不是说一定要满足的。随着当前社会发展,数据量越来越大,第二范式与第三范式的严格性会限制数据库的查询性能。为了追求更高的查询性能,大多时候我们满足第一范式即可。

6 网络编程

6.1 Websocket

6.1.1 ajax轮询

浏览器每隔几秒就发送一次请求,询问服务器是否有新信息。

缺陷:1.服务端被动式;2.ajax要求服务端很快的处理速度

6.1.2 long poll
  • 轮询
  • 阻塞:一直打电话,没收到就不挂电话

缺陷:1.服务端被动式;2.要求服务端高并发

6.1.3 websocket
  1. 服务端可以主动推送消息到客户端
  2. 持久性:一次请求,持续消息传递(回调)
6.1.3.1 谈谈你对回调函数的理解?
6.1.4 网络相关面试题
  1. OSI七层模型是什么?,http,tcp,ip,udp…协议在哪一层

    # 参考答案
    OSI七层模型:应表会传网链物
    应用层,表示层,会话层,传输层,网络层,链路层,物理层
    应用层:http, ftp, smtp
    传输层:tcp udp 
    网络层:ip
    
  2. 简述TCP三次握手,四次挥手,为什么是握手三次,不是两次,四次,为什么挥手是四次,不是三次?



补充疑问:若建立连接后,客户端突然故障怎么办?

答:TCP设置一个存活计时器,每次收到客户端请求就会将计时器清零;如果客户端故障导致服务器长期没收到请求,超过计时器设定的计时时间(正常2小时),服务器就会向客户端发送探测报文。每75s发送一次,若连续十次都没有响应,服务端默认客户端故障,接着关闭连接。

  1. 浏览器中输入www.baidu后执行的全过程

    # 参考答案
    1.DNS域名服务器解析www.baidu得到一个ip地址
    2.客户端对解析得到的ip地址服务器发起TCP三次握手建立连接
    3.建立TCP连接后,发起http请求
    4.服务器对收到http请求进行响应,返回相应html代码
    5.客户端(浏览器)对收到的html代码进行解析,请求html中的资源(js css)
    6.浏览器整合资源渲染页面展示给用户
    
  2. TCP和UDP的区别

    # 参考答案
    1.tcp提供面向连接的,可靠的数据流传输;(类比打电话)
    优势:不限制数据包的大小,稳定传输不丢包
    劣势:接收时数据无边界,有可能黏合几条数据成一条数据,造成黏包现象。
    2.udp提供的是非连接的,不可靠的数据流传输;(类比发qq)
    优点:接收时,数据有边界,传输速度快,不黏包
    缺点:限制数据包的大小,传输不稳定,容易丢包
    
    
  3. DNS域名系统,简单描述其工作原理

    # 参考答案
    作用:用来将域名解析为ip。
    原理:查询时,客户机发送的每条查询信息包含三个信息:dns域名,指定的查询类型,DNS域名的指定类别;该系统基于UDP服务,端口53,一般不直接为用户服务,而是为http,smtp等需要完成主机名到IP地址转换的服务。
    
  4. TCP黏包是什么情况?怎么解决?

    # 参考答案
    	TCP是一个基于字节流的传输服务。意味着TCP传输数据是没有边界的。这不同于UDP传输消息是基于消息的,是有边界的。TCP的发送方无法保证接收方每次接收的都是一个完整的数据包。	
    	黏包的本质是 接收方不知道消息的边界在哪
    	解决黏包的办法:
    	1.发送定长包。
    	2.包围加上 \r\n 标记。(需要避免正文也含有\r\n)
    	3.包头加上包长的信息。
    
  5. http与https区别

    # 参考答案
    http:超文本传输协议;明文传输协议,较为不安全
    https = http + ssl:在http的基础上增加了数据加密。相比http更安全。ssl对传输的数据进行加密。
    
  6. 写出socket网络通信的demo
    服务端:建立sk连接对象;建立连接(IP,端口);监听;获得连接信息;关闭sk;

# 服务端,本机实现
import socket
# 创建socket对象,ipv4,,基于http
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定IP和端口
sk.bind(("127.0.0.1", 9001))

# 监听
sock.listen()

while True:
    conn, addr = sk.accept()  # 等待连接

    data = conn.recv(1024)  # 接收数据
    print("请求信息====>  %s" % data)

    # 发送数据
    conn.send("HTTP/1.1 200 OK\r\nContent-Type: text/html;charset=utf-8\r\n\r\n".encode('utf-8'))
    conn.close()

客户端:建立sk对象;建立sk连接;发送信息;接收信息;关闭sk;

# 客户端,本机实现
import socket

sk = socket.socket(socket.AF_INEF, socket.SOCK_STREAM )

sk.connect('127.0.0.1', 9001)

sk.send("good!")

res = sk.recv(1024)

print(res.decode(utf-8))

sk.close()


6.2 IO多路复用

概念:很多个网络IO复用一个或少量的线程来处理这些连接。

IO:网络IO

多路:多个TCP连接

复用:复用一个或少量线程

6.2.1 select(1983)
  • 轮询
  • 1024个连接上限
  • 会修改传入的参数数组
  • 非线程安全
6.2.1.1 什么是线程安全,非线程安全?
6.2.2 poll(1997)
  • 轮询
  • 去掉了1024个连接上限
  • 不再修改传入的参数
  • 非线程安全
6.2.3 epoll(2002)
  • 事件触发:水平触发,边缘触发
  • 线程安全
  • linux中独有,BSD中为kqueue
6.2.4 QS:什么是IO多路复用,/epoll和select,poll有什么区别?

解答:

–时间线:

1983年,select出现

1997年,poll出现

2002年,epoll出现

–空间线:

多路复用概念->多路复用发展历史及使用场景(select->poll->epoll)->留下一个引子(协程)

# 参考回答
	I/O多路复用,I/O就是指我们网络IO,多路指多个TCP连接,复用是指复用一个或少量线程
	第一次实现IO多路复用是在1983年select机制的出现;很长时间内select都满足IO多路复用的需求,但随着技术发展,select 1024的连接上线逐渐不够用了。
	于是1997年poll机制应运而生,poll机制去掉了select很多问题,但是仍然还存在像线程不安全问题,且轮询的方式在很多场景下会造成性能和资源的浪费。
	2002年epoll出现。epoll采用的是事件触发的机制,放弃了select和poll轮询的实现方式,同时又是线程安全的;但是epoll也有不足的地方,比如智能应用于linux系统,且部分情况下下如很多TCP就绪的状态下是比较适合用轮询的方式的。
	总体而言,目前多路复用技术中epoll相对使用较为广泛,比如在Python中tornado中的很多协程很多适合就是通过epoll机制来进行切换调度的。

6.3 Web框架

6.3.1 同步/异步 阻塞/非阻塞

同步/异步:针对应用程序来说,关注程序间的协作关系,是否需要等待结果

​ 同步:发起一个请求,直到请求返回结果之后,才进行下一步。

​ 异步:执行一个请求,不需要等待结果就可以执行下一步。

​ 区别:执行下一步是否需要等待请求结果

阻塞/非阻塞:关注单个进程/线程的进行状态,数据未就绪是否会妨碍当前进程/线程的后续操作;

​ 阻塞:调用结果返回之前,当前线程会被挂起;

​ 非阻塞:调用结果没有返回之前,不会阻塞当前线程;

异步都是跟非祖塞一起的,异步阻塞没有意义;

# 举例,from 知乎 老张煮开水
老张:线程  任务1:烧水 任务2:看电视 CPU:此处默认只能烧开水,放电视的系统
1.老张把水壶放到火上,立等水开
	同步阻塞;(执行下步需要等待结果,妨碍后续操作)
2.老张把水壶放到火上,去客厅看电视,放广告去看水开没有。
	同步非阻塞(执行下步需要等待结果,不妨碍后续操作)
3.老张买了个响水壶,立等水开
	异步阻塞(执行下步不需要等待结果,妨碍后续操作)
4.老张买了个响水壶,去客厅看电视,水壶响之前不去看,响了再去拿壶
	异步(执行下步不需要等待结果,不妨碍后续操作)
	
	一般来说,同步非阻塞,异步阻塞没有意义。
6.3.2 Django

大而全,全自动化的管理后台,高耦合(ORM和其他模块,非常臃肿)

性能较差,不能在线上裸跑(搭配 nginx + uwsgi)

6.3.3 Flask

小而精;核心简单,extension增加其他功能;

6.3.4 Tornado

少而精,高性能,异步非阻塞,更为原始,插件少

6.3.5 Sanic

Python3.5+;异步;速度快;类Flask;

6.3.5.1 QS:sanic是什么?uvloop为什么那么快?(拓展,了解)

sanic 超快的python异步框架。

asyncio 是遵循Python标准库的一个异步 I/O 框架。

uvloop:完整替代asyncio 事件循环。uvloop是用 cpython 写的,基于 libuv。

uvloop 使得 asyncio 更快。实际上,比 nodejs,gevent, 以及其他任何 Python 异步框架至少快两倍。uvloop asyncio 基于性能的测试接近于 Go 程序。

利用 uvloop 可以写出在单CPU内核下每秒钟能够发出上万个请求的Python网络代码。在多内核系统下,可以使用进程池来进一步系统性能。

6.3.6 QS:Python Web 框架认识和对比

–提问:

  1. 了解过tornado或sanic吗
  2. 有没有接触其他web框架
  3. 谈谈你对Python Web 框架的认识

–回答:

从空间线上来进行组织回答;

  1. django,flask,tornado,sanic特性
  2. 项目框架选型依据
  3. 同步/异步,阻塞/非阻塞
  4. 埋引子(从异步引出协程的概念)

–回答案例

# 案例
	有了解过tornado和sanic框架,但是在之前公司的项目中更多的是用的django,少部分用的flask。django的涉及哲学是简明,方便,大而全,包含了丰富的插件以及自动化的管理后台,而flask相对于django而言更精小一些。django和flask都是同步框架,而sanic和tornado是异步框架,都拥有非常好的性能。其中tornado是facebook开源的一个项目,目前应用还是比较多的,而sanic是基于Python3.5的近些年兴起的框架,使用到了很多Python3的新特性。
	在框架选型的过程中,如果希望开发过程中有丰富的插件,推荐使用django和flask,然后在部署的时候使用 nginx+uwsgi 提高并发;如果是纯粹的后端项目,更加追求性能,可以考虑使用tornado或者sanic等异步框架,像tornado,sanic这些支持协程的框架更适合大多数的高并发网络IO处理。

6.4 消息队列

6.4.1 需求

不需要立即获得结果,但是并发量又需要进行控制的时候,差不多就是需要消息队列的时候。

优点:解耦,削峰,异步扩展性等等。

缺点:对消息中间件的维护,包括:重复消费、消息丢失、消息的顺序消费等;

6.4.2 应用场景
  1. 应用耦合

    多应用间通过消息队列对同一消息进行处理,避免调用接口失败

  2. 异步处理

    应用间并发处理消息,相比串行处理,减少处理时间

  3. 限流削峰

    广泛用于秒杀或抢购活动中,避免流量过大而导致系统崩掉

  4. 消息队列驱动系统

6.4.3 两种模式

(1)点对点模式

单个接收者

无依赖

接收者需应答

模式本身类似于打电话

(2)发布/订阅模式

单个发布者/消息队列可以有多个订阅者

时间依赖:创建一个订阅者之后,才能消费发布者的消息

提前订阅该角色主题,并保持在线运行

6.4.4 RabbitMQ

(RabbitMQ和ActiveMQ因为吞吐量的原因基本在大it公司绝迹)

以后学习生活中主要了解 kafka 和 rocketMQ(阿里开源的)

(1)优点:

  1. 性能好,高并发,单机QPS在万数量级
  2. 可靠性高,有消息确认机制和持久化机制
  3. 可定制路由
  4. 社区活跃,管理界面丰富

(2)缺陷

  1. 不利于二次开发和维护(基于erlang语言开发)
  2. 接口和协议较复杂,学习成本较高

(3)注意事项

  1. RabbitMQ的消息应当尽可能的小,并且只能用来处理实时且要求高可靠性的消息。
  2. 消费者和生产者的能力尽可能对等,否则消息堆积会严重影响RabbitMQ的性能。
  3. 集群部署,使用热备,保证消息的可靠性。
6.4.5 Kafka

架构体系:若干个Broker + Zookeeper集群

Zookeeper集群主要用来进行集群元数据的管理以及控制器的选举操作。

6.4.5.1 QS: Kafka为什么那么快?(拓展,后续深入了解)
  1. 使用了底层操作系统提供的PageCache功能
  2. 使用 Sendfile/零拷贝 技术 – 快发送
  3. 采用segment的方式切割分片存储数据 – 数据在磁盘里面快速根据offset找到
  4. 批量发送和批量消费

–优点:

  1. 性能非常好,单机QPS能达到百万级别
  2. 分布式架构,高可用性,高可靠性,理论消息存储无上限
  3. 消费者采用pull方式获取消息,能够保证消息获取有序且消费有且仅有一次
  4. 客户端语言丰富
  5. 日志领域较为成熟
  6. 支持批量操作
  7. 优秀的第三方管理界面 kafka-manager

–缺点:

  1. 只支持pull模式(轮询),不支持push(推送)模式
  2. 宕机后,消息可能会发生乱序 --对于一致性要求高的场景不适合
  3. 目前支持的功能没有RabbitMQ那么丰富

– 注意事项

  1. 对消息顺序不依赖,且不是那么实时的系统
  2. 对消息丢失并不那么敏感的系统
  3. 需要一个非常好的运维监控系统,不单监控kafka本身,还要监控zookeeper
6.4.6 本地搭建ELK -日志分析系统

ELK = elasticsearch(搜索引擎–当成一个数据库,全文索引,相关性搜索) + logstash (日志搜集,比较占内存,java)|filebeat + kibana(web控制台)

6.4 DeveOps

​ (1)软件开发最初出现的是瀑布式开发模式,把开发模式严格分割为各个阶段:需求,设计,编码,测试等;这种模式严格遵循阶段工作达到要求输出之后才可以进入下一阶段;灵活性极低;慢慢地出现了敏捷开发,强调快速迭代,能够满足客户需求,灵活性可扩展性很强。但是随着上线频繁,给运维和QA(品质保证)的工作越来越复杂。又是便出现了新的概念-devops(developments&opration)

​ (2)Devops是一组过程、方法、与系统的统称,用于促进了企业开发,技术运营和质量保障(QA)之间的沟通、协作与整合。

​ 他的出现使行业更加清晰地认识到:为了按时交付产品和服务,开发与运营必须密切合作。

​ 常用的Devops技术有:

​ 敏捷管理工具:trello, teambition, worktile, tower

​ 代码仓库管理: git, github, gitlab

​ 虚拟化:docker, vmvare, virtuabox

​ 持续集成及部署:jetkins, saltstack

​ 自动化运维:ansible, jiacrontab

6.5 uwsgi(作用,进程数设置)

  1. uwsgi作用:django是一个Web框架,框架的作用在于处理request和response,其他的不是框架所关心的。比如怎么部署不是django所关心的。django所提供的开发服务器是python自带的 simple httpserver,没有经过安全测试,在安全和性能上都是不行的。

    而uwsgi是一个全功能的HTTP服务器,他要做的就是把Http协议转换成语言支持的协议,比如把http协议转换成WSGI协议,从而让python可以直接使用。uwsgi主要有以下特点:

    • 超快的性能
    • 低内存占用
    • 多app管理
    • 详尽的日志功能
    • 高度可定制(内存大小限制,服务一定次数后重启)
  2. uwsgi进程设置:进程数的设置和具体的服务器配置,进程承担的工作紧密相关。如果是cpu密集型的工作任务,进程数一般小于等于服务器核数,而如果是IO密集型,进程数相对来说可以多些,但也不能太多,太多的进程切换会影响服务器的资源,造成服务器性能的下降;具体工作一般进程设置上限不超过服务器核心数*3。

6.6 Nginx

  1. Nginx负载均衡策略

    1.weight: 权重(样本越小越不均衡)

    2.轮询:默认方式

    3.ip_hash: 依据哈希分配(相对随机,同一用户多次请求一般会走一个程序)

    4.least_connection: 最少连接(热点数据可能集中于某台服务器)

    5.fair(需要第三方插件):按照服务器的响应时间来分配请求,响应时间段的优先分配。(同一服务器的请求可能走向不同服务器)

    6.Url_hash:按访问的url的hash结果来分配请求,使每个url定向到同一个后端服务器,要配合缓存命中来使用。(方便后期维护;同类型的服务器要避免单点问题)

7 数据结构

考研四大课程:操作系统,数据结构,组成原理,计算机网络

数据:程序设计语言中的基本类型,抽象概念;

结构:数据不是独立的,存在着特定的关系,这些关系便是结构;

内存分配:

堆:动态数组,数据定义的时候长度传的是变量,new

栈:普通数组,固定的数值,速度快

队列,树,堆,数组,栈,链表,图,散列表

7.1 数据结构种类

7.1.1 数组

存储方式:在内存中是连续存储(多个元素)

特性:

  1. 可以通过下标访问元素,下标从0开始
  2. 不适合随机插入和删除

优点:

  1. 按照索引查询元素速度快
  2. 按照索引遍历数组方便

缺点:

  1. 数组的大小固定后就无法扩容了
  2. 数组只能存储一种类型的数据
  3. 添加,删除的操作慢,因为需要移动其他元素

vector:支持扩容(了解)

​ 原来 a = vector(10)

​ 第11个元素会出现:重新申请一块2倍大小的新内存,将原来的数据拷贝到新内存中

7.1.2 栈
  • 类似于上开口的树立储物柜
  • 借助于列表实现(python中)
  • 先进后出
  • 一种特殊的线性表,仅能在线性表的一段(栈顶)操作
  • 常应用于实现递归方面的场景,例如斐波那契数列
# python栈实现(必须要会!!!)
class Stack:
	def __init__(self):
		self.items = []
    
    def isEmpty(self):
    	return self.items == [] # 判断为空
    	
    def push(self, item):
    	self.items.append(item) #添加至列表末尾(栈顶)
        
    def pop(self):
        self.items.pop()
        
    def peek(self):
        return self.items.pop() #删除列表末尾元素(栈顶)
    
    def size(self):  # 返回栈长
        return len(self.items)
7.1.3 队列
  • 类似于排队,先进先出
  • 同栈一样也是一种线性表
  • 一端放入元素:入队 一端取出元素:出队
  • 使用场景:多线程阻塞队列管理中非常适用
#python队列实现(必须要会!!!)
class Queue():
	def __init__(self):
		self.items = []
		
	def isEmpty(self):
		return self.items == [] # 判断为空
		
	def enqueue(self, item):
		self.items.insert(0, item) #从列表开头添加
		
	def dequeue(self):
		self.items.pop() #列表末尾删除
        
    def size(self):
        return len(self.items)
7.1.3.1 双端队列

双端队列和普通队列区别在于:对头和队尾都可以插入和删除元素

# 实现代码 (必须要会!!!)
class Queue():
	def __init__(self):
		self.items = []
		
	def isEmpty(self):
		return self.items == []
    
    def addFront(self, item):
        self.items.append(item)
		
	def addRear(self, item):
		self.items.insert(0, item) 
		
	def removeFront(self):
		self.items.pop() 
        
    def removeRear(self):
        return self.items.pop(0)
        
    def size(self):
        return len(self.items)
7.1.4 链表

存储方式:

  • 在内存中是间断不连续存储
  • 每个节点有两个成员变量,一个是存储的数值,一个是另一个节点的地址

特点:

无法通过下标访问,不适合随机访问,只能从头遍历查找

循环链表: 将单量表的最后一个节点指针指向表头

双向链表:每个节点有三个成员变量,一个存储数据,另外两个分别存储上一个、下一个节点的指针

7.1.4 链表反转
'''
时间复杂度:O(n)  空间复杂度:O(1)
'''
def reverse_ListNode(head):
	if head == None or head.next = None:
		return head
	
	cur = head
	temp = None 
	newhead = None
	while cur:
		'''
		1+4: 取除头节点后面的链表赋值给自己
		1+2:取下头节点(逆序拼接,新链表指针引到原头节点,作为新链表的后面)
		1+3:换新head(其实是新链表的后面)
		'''
		temp = cur.next
		cur.next = newhead
		newhead = cur
		cur = temp
	return new_head
7.1.5 树(倒挂树)
7.1.5.1 二叉树
  1. 每个节点最多只有两个节点
  2. 左右子树是有顺序的
  3. 即使一个子树也要区分左右子树
# 实现代码(必须掌握)
class BinaryTree():
    def __init__(self, root):
        self.key = root
        self.left_child = None
        self.right_child = None
        
	def insert_left(self, new_node):
        if self.left_child == None:
            self.left_child = BinaryTree(new_node)
        else:
            t = BinaryTree(new_node)
            t.left_child = self.left_child
            self.left_child = t
	def insert_right(self, new_node):
        if self.right_child == None:
            self.right_child = BinaryTree(new_node)
        else:
            t = BinaryTree(new_node)
            t.right_child = self.right_child
            self.right_child = t
    def get_right_child(self):
        return self.right_child
    def get_left_child(self):
        return self.left_child
    def set_root_val(self,obj):
        self.key = obj
    def get_root_val(self):
        return self.key

7.1.6 散列表|哈希表

散列表,也叫哈希表,是根据关键码和值(key 和 value)直接进行访问的数据结构。

哈希冲突解决方法-- 1.拉链法 2.往后排 3.重新哈希

取模

不可逆性(单向的,从输出无法直接倒退出输入) — 加密

确定性(输入确定,输出也就确定,同样的输入输出一定一样)–判断文件是否一致

冲突(多个不同的输入可能输出的结果一致)

7.1.7 堆

堆是一个比较特殊的结构,可以被看作一棵树的数组对象,具有以下的性质:

  • 堆中某个节点的值总是不大于或不小于期父节点的值;
  • 堆总是一颗完全二叉树
7.1.8 图(初级不做要求)
  • 图是由结点的有穷集合V和边的集合E集合
  • 为了与树形结构加以区别,在图结构中常常将节点称为顶点,边是顶点的有序偶数
  • 若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系

7.2 算法

思想;解决问题的办法;指令的有限序列;

确定性;有穷性;输入输出;可行性;

7.2.1 时间复杂度

时间复杂度:和每行代码的执行次数有关,只算大头

(!!O执行的次数,n是问题的规模)时间复杂度从小到大:O(1) < O(logN)<O(n)<O(nlogN)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)

7.2.2 空间复杂度

7.3 排序算法(至少熟练掌握三种)

详细讲解参考此blog

排序算法稳定性:排序后不会改变相等键值的先后次序

7.3.1 冒泡排序(***必会)
  1. 简单,稳定 2.效率低(时间:最优O(n^2) 最差O(n) 空间复杂度:O(1))

  2. 代码实现

    # 冒泡排序实现(必会!!)
    def bubble_sort(alist):
    	# 外层循环控制每次比较几轮
        n = len(alist)
        for j in rang(n-1)
        	# 定义计数器计算每次交换轮数
            count = 0
            # 内存循环控制交换
            for i in range(n-1-j):
                # 若前一个比后一个大,则换
                if alist[i] > alist[i+1]:
                    alist[i], alist[i+1] = alist[i+1], alist[i]
                    # 计数器
                    count += 1
            if count == 0:
                return
    
7.3.2 选择排序(会选择题即可)

原理:每轮选择出最小(大)的元素防止末尾(开头),直到排序完毕;

特点:1.不稳定效率低 2.优点是移动次数少 3.时间复杂度:固定 O(n^2)

7.3.3 插入排序(会选择题即可)

原理:构建有序序列,每轮插入一个元素进去(到合适的位置)

特点:1.稳定较快 2.比较次数不确定,数据量越大,越渣 3.时间复杂度:最优O(n) 最差O(n^2)

7.3.4 希尔排序(不要求)

原理:基于插入排序,增量分组,分组后使用插入排序,增量依次减少

特点:1.不稳定 2.时间短,移动少 3.时间复杂度(因步长而异):最坏O(n2),最优O(n1.3)

# 希尔排序实现(不要求掌握)
def Shell_sort(L):
	step = len(L/2)
	while step > 0:
		for i in range(step, len(L)):
			while(i >= step and L[i] < L[i-step]):
				L[i], L[i-step] = L[i-step], L[i]
                  i -= step
		step /= 2
        print(L)
7.3.5 快速排序(***必会)

时间复杂度:最优O(nlogN) 最差O(n^2)

– logN 二分的策略

优点:效率高,数据量越大,数据移动越少,优势越明显

缺点:不稳定

7.3.6 归并排序(***必会)

原理:先打碎,再合并

时间复杂度:固定O(nlogN) 稳定性:稳定

优点:数据量越大越稳定 缺点:需要额外空间

7.3.7 堆排序 (***必会)

原理:

# 定义堆自调整函数
def heapify(arr, size, root): 
	# 父节点
    largest = root  
    l = 2 * root + 1     # left = 2*root + 1 
    r = 2 * root + 2     # right = 2*root + 2 
  
    if l < size and arr[root] < arr[l]: 
        largest = l 
  
    if r < size and arr[largest] < arr[r]: 
        largest = r 
  
  	# 如果父节点不是最大,则调换为最大
    if largest != root: 
        arr[root],arr[largest] = arr[largest],arr[root]  # 交换
  		
  		# 递归对子树做调整
        heapify(arr, size, largest) 
 
 # 创建一个大顶堆,建堆就是不断做大根堆调整的过程 
def built_max_heap(arr): 
    size = len(arr) 
  
    # 建立最大堆,自底向下建堆,(size-2)/2 为size列表对应的完全二叉树父结点最大下标
    for i in range((size-2)//2, -1, -1): 
        heapify(arr, size, i) 
  
def heap_sort(arr):
	built_max_heap(arr)
    # 一个个交换元素
    for i in range(len(arr)-1, -1, -1): 
    	# 将堆顶和最后一个元素交换,最大的树依次相当于放在最后面,在递归调整剩余列表,因此大顶堆输出的是依次增加的序列。
        arr[i], arr[0] = arr[0], arr[i]   
        heapify(arr, i, 0) 
  
arr = [ 12, 11, 13, 5, 6, 7] 
heapSort(arr) 
n = len(arr) 
print ("排序后") 
for i in range(n): 
    print ("%d" %arr[i])

7.4 数据结构与算法面试题

详细代码实现参考此blog

7.4.1 QS: 单链表反转: — 必须会写

见上;

7.4.2 QS:判断两个单向链表是否相交 — 要会思路,不要求现场代码

思路:遍历第一个链表到他的尾部,然后将尾部的next指针指向第二个链表,即判断新链表是否有环(如何判断是否有环呢?);记得返回结果解除链表环,还原链表;

7.4.3 QS:求满足指定和的子集

题目:

给定一个整数数组和指定的数字和,求数组中相加等于指定和的所有子集,如:

输入: array = [2, 3, 7, 4, 10, 8, 6], sum = 10
输出: [2, 8], [3, 7], [4, 6], [10]

解题思路->递归:不停把需要考虑的范围一步步缩小 难点:数据格式调通

@functools.lru_cache (节省递归操作时占用空间的工具,需要 import functools)

7.4.4 QS: 纸币组合(星商,较难)

题目:手上有x张十元纸币,y张五元纸币,z张两元纸币,购物后需要支付n元(x, y, z, n为整数),要求编写一个复杂度为O(1)的函数 findsolution(x, y, z, n),功能是计算出能否用现在手上拥有的纸币是否能够并刚好凑齐n元,而不需要找零。

如果可以输出一个方案并结束;

如果不可以输出“不能凑齐”;

解题思路:

方法1: 分奇数/偶数进行讨论;

思路大概是这样:

1.为偶,则5快的可要可不要(要的话一定是偶数张凑整十);直接看10块的够不够用
	1.1 10块的够用(n//10 < x),不需要五块的(five_num=0);十块的数量可求(因为只要一种凑齐即可,10块按尽可能多给,简单处理:ten_num=n//10);接下来剩下来的个位数交给2块来凑(temp=n-10*ten_num),判断 temp//2的结果与z比较,若z为小,则2块不够,输出false;若z为大或者等,则two_num=temp//2,由此可得该分支下可能凑得齐的结果。
	1.2 10块的不够用,先拿五全部来凑;
	1.2.1 够凑的话拿偶数五凑满整十部分。个位数交给2来凑,同1.1判断,2够个位数就能输出,不够就false
	1.2.2 不够凑的话,拿五块的最多的偶数张凑,剩下的2来凑。2凑完整十接着凑个位数,判断剩下的2是否可凑,不够就false
	(易错点在于补5的时候,一定要控制补偶数张,因此要判断)
2.为奇,则五块必须要,且必须设置为奇数张,因此总数减1*5必为偶数,可以重新调用偶数函数
	先排除特殊情况:
	总数为1,3凑不出;5块为0凑不出;剩下进入正常讨论:
	将num-5得到一个偶数,在次调用为偶的判断函数
	res = func_double(x, y-1, z,num-5)
	if res:
		a, b, c = res
		return a,b+1,c (补上5去奇变偶的那一张)
    else:
    	return false

方法2:最小公倍数+约数(通用方法);(没搞懂)

(目前还没搞懂)

7.4.5 QS:洗牌算法(较为简单,游戏必问)

题目:给你一副54张的有序的新扑克牌,设计一个洗牌算法,打乱牌顺序。

思路:在数组中按索引轮着从头至尾与一个随机数(控制随机范围为当前索引至牌数最大索引)产生的索引,交换位置。

7.4.6 QS:从1亿个整数中找出其中出现次数最多的数

思路:同下

方法1:全局哈希统计次数 时间复杂度:O(n) 空间复杂度:O(n)

方法2:局部哈希统计次数 时间复杂度:O(n^2) 空间复杂度:O(1)

方法3:先排序再遍历(假设有序来简化思路) 时间复杂度:O(nlogN) 空间复杂度:O(1)

方法4:哈希+归并 时间复杂度:O(n) 空间复杂度:O(n)

7.4.7 QS:两个大文件A,B,每一行都为一个字符串,找出在两个文件中都出现的字符串

思路:同上

7.4.8 QS: 某整数开方如何实现,保留指定位数 --二分逼近

思路:穷举,每一个位小到大去试,位从整数到小数一位位从左向右去确认,如果该位上的数字与前面确定的数字拼接的数字,产生的平方值等于指定整数,该位最终数字即为该数字;如果一旦,大于指定指定整数,则该位最终数字为该数字减1;

python实现部分:组装我们去试的这个数,怎么去表示指定位数的

小数是关键

第一步先算整数部分,整数部分跟设定的保留

7.4.9 QS:九宫格 --排列组合问题 --通过进制(难)

思路:调整为每位进制不一样的组合

输入 257 总共有 len(abc) * len(jkl)*len(pqrs) = 36种组合

for i in range(36):

​ 将 i 的值转换成257对应的进制数: 3进制 3进制 4进制

​ i =0 , --> 转换 000 – > print(ajp)
​ i =1 , --> 转换 001 --> print(ajq)
​ …
​ i = 4, --> 转换 010 —> print(akp)
​ i= 13, --> 转换 101 —> print(bjq)
​ i=35 …

7.4.10 QS:给你几张扑克牌,扑克牌的牌值通过运算得出24,用代码求出可能的组合,用到的符号: +, -, /, 括号(难)

思路:

step1 先求出2,4,6,8 的排列情况

step2 再求出+ - * / 的可重复的排列情况

step3(延伸) 考虑加括号的情况:先从1个括号组合情况再到最多数量括号情况

7.4.11 旋转数组

题目:写一个函数,传递两个参数(旋转角度, 数组)求出旋转后数组的结果

思路:方法1:通过行列互换+行/列逆序来达到目的,两部操作皆可简单实现

方法2:一个个元素取出来组装的新数组,注意1.第一个元素的位置 2.按行取还是按列取 3.正序还是逆序(这个方法理解起来简单一些)

7.4.12 从100亿个数中找出最大/小的1000个数

思路1. (好像有问题,找老贺确认)

采用位操作(此题不推荐,过于占内存 42亿bit = 512 Mbytes)

申请512M空间,每一个bit的位置代表一个整数,遍历数字,数字值为value,边将内存中第value个bit置为1。

从内存高位开始读,该位为1,则记录该位的数值,依次记录1000个。

时间复杂度O(n), 空间复杂度O(1)

思路2.堆排

从100亿中取出1000个数,形成一个最小堆;每次取出1个数与堆顶比较,如果该数小于堆顶,继续取下一个数,如果该树大于堆顶,进行替换,并进行堆调整,然后继续取下一个数。

堆排O(mlogm n) 辅助空间O(1)

7.4.13 有1亿个整数,其中只有1个整数只出现了1次,怎么快速找出来

#todo

7.4.14 python中sort 是如何实现的

#todo

8 开发杂谈

微服务框架(请查询拓展):

阿里:Dubba(java)

腾讯:tars(腾讯内部叫 taf)

google: grpc, protobuf

facebook:thift

QS: 什么是微服务?

8.1 优化

8.1.1 规范化
8.1.1.1 编码风格:
  1. 代码是否符合PEP8规范 参考blog
  2. 多人开发时编程风格是否统一,比如同意含义变量名称不同,mysql等操作方式是否一致
8.1.1.2 流程规范
  1. 测试是否充分,有没有规范化的流程和记录?
  2. 代码继承和服务上线是否有相应流程并严格执行?研发人员是否随意上线?
  3. 上线是否由操作流程记录和相应的结果记录?
8.1.2 性能
  1. 当前CPU是否存在潜在的性能瓶颈?如GPU是否经常处于超负载状态?内存是否接近占满?产生性能的瓶颈在哪?
  2. 数据库架构是否合理?扩展性强不强?能适应接下来一年业务需求的增长和变化吗?
  3. 是否有负载均衡?有没有必要引入分库分表分片等措施的空间?
  4. 当前服务器资源利用是否充分?存不存在服务器某部分资源处于大量空闲状态?可否合并和迁移部分服务资源?
8.1.3 体系
  1. 是否由监控系统和预警系统?有没有保障监控和预警系统自身的可靠性?
  2. 监控的延迟是多少?能否做到实时响应,出现问题时第一时间通知相关负责人?
  3. 系统中是否存在单点故障隐患?
  4. 权限管理是否是松散和严谨?有多少人可以登录正式服和测试服,他们又分别具备什么样的权限?
  5. 各关键数据备份机制是否齐全?有没有实时备份和定期冷备?
  6. 故障恢复的流程文档是否存在?各部分出现问题后到恢复正常的时间周期多长?

8.2 解决线上未知问题

七月留存:第一天注册的用户在第七天还在登录的用户/第一天注册用户总数

日活|月活:同理月活为30日还留存的用户

埋点:对关键的行为或者数据做一个记录(日志,向指定的服务发送一个请求,存储DB)

预发布环境:自己去测试,经常借由正式服的数据库,但是普通用户不知道预发布环境的访问地址

公测环境:普通用户都可以进去,用户知道可能会有很多bug

  1. 判断是否能够快速找到问题原因(5分钟内)
  2. 若不能则先启动备用服务或者回滚至安全代码
  3. 确保线上服务正常后,再回过头继续跟踪问题原因
  4. 找到该问题解决办法并修复bug
  5. 尽可能预防同类问题再次出现,完善业务代码
  6. 如果下次再出现类似问题,如何快速跟踪定位(监控,预警,预先日志埋点,定开发,流程开发,自动化等)
  7. 完善后的新代码上线
  8. 若该问题对用户或客户造成了损失或可能被感知,通知用户或客户
  9. 问题总结,记录相关相应文档,团队内部分享

crontab: 定时任务

隐患:

  1. 编辑不方便,容易出错
  2. 必须要登录到具体的服务器上才能修改

jiacrontab:

  1. 先不要直接登录到具体的机器上去操作
  2. 网站操作界面
  3. 需要做的事情:在每个机器上部署一个agent

8.3 编码规范(不限于PEP8)

  1. 算法测试用例要全一些,考虑特殊情况,要有满足的,不满足的都要考虑;
  2. if或者for ,while 嵌套层数不要超过3层;
  3. #code=utf-8
  4. 函数的返回值不要用字符串,特别是中文
  5. 测试用例要让读者能够快速验证结果是正确的
  6. 如果函数的代码过长(超过一屏),一定要在每个模块的地方加上注释
  7. 算法库里不要print
  8. 测试用例不要使用input进行输入
  9. elif 过多换用字典
  10. 算法函数一般需要有返回值
  11. 注释不要每行都写,到模块即可
  12. 测试用例要放到if name 去
  13. 变量的作用域遵循最小原则
  14. 要确保每个分支最终都需要有返回值
  15. 类名采用驼峰法,一般不用下划线
  16. 局部变量不要下划线开头
  17. 一般普通算法不建议封装成类,尽量使用一个函数即可

9 linux

9.1 docker的好处和缺陷

(1)好处

  1. 简化配置
  2. 代码流水线管理
  3. 提升开发效率
  4. 隔离应用
  5. 整合服务器
  6. 调试能力
  7. 快速部署

(2)缺点

  1. 必须在64位机器运行
  2. linux内核必须是3.8或者更高
  3. 内核必须支持cgoups和命名空间

9.2 CPU负载怎么理解,比如四核机器负载多少算超载

系统负荷:单核情况下,如果CPU每分钟处理100个进程,那么系统负荷0.2,意味着在这1分钟里只处理20个进程

对于多核CPU,平均到每核:

  1. 系统负荷大于 0.7 ,需要注意调查,看是否有问题
  2. 大于1.0 必须解决问题,降值
  3. 达到5.0,系统有问题,长时间没响应,或者接近死机

9. 3 kubernetes+docker

9.4 命令相关

  1. 查看端口,进程 : netstat
  2. 查看当前系统负载命令: top
  3. 查看进程命令:grep

10 git

10.1 git与svn区别

  1. Git:git是分布式的版本控制软件,没有中央管理器,每个节点地位平等。

  2. svn:集中式的代码管理软件。部署一台中服务器,大家都把代码提交到中央服务器。(必须联网工作,只有最新版本)

10.2 git常见命令

(1) 本地操作命令:
git init  # 本地初始化仓库
git add # 添加代码到暂存区
git commit -m "版本描述"  #从暂存区提交到版本库
git status # 查看仓库状态
git rm #删除(暂存区)
git checkout #撤销提交到暂存区的操作
git log | git reflog #查看历史版本
git reset HEAD^ #回退版本(撤销提交到版本库的操作)
git branch 分支名 #创建本地分支
git checkout 分支名 #切换本地分支

(2)远程仓库同步操作命令:
git remote add origin 远程仓库地址 # 添加远程仓库地址并命名
git pull  # 拉取以及合并远程仓库到本地仓库(dev_local)
git clone 仓库地址 # 克隆仓库地址
git push # 推送到远程仓库
git fetch # 更新远程仓库到本地 pull = fetch + merge
git merge # 将内容合并到当前分支

10.3 提交代码发生冲突,如何产生,你是如何解决?

可能产生的情况:和别人同时修改了一个公共类的公共方法

解决办法:即保存修改-消除冲突-合并修改

1.git stash 将工作区 的修改提交到栈区

2.git pull 拉取远程合并到线上分支,消除冲突

3.git stash pop 把栈区的修改部分合并到最新工作空间(即保存修改-消除冲突-合并修改)

必要时要跟同事沟通。

git stash 是专门解决提交冲突的命令。

10.4 git fetch, git pull, git merge之间的区别与联系?git merge 和 git rebase 区别?

(1) git fetch 把远程分支拉取到本地;git merge 将远程分支与本地分支合并; git pull = git fetch + merge

(2) 两者都是合并分支;merge会产生一个新的commit,记录每个分支的详情(不够简洁,易造成分支杂乱);rebase 会合并之前的commit 历史(不易定位出错代码)。

10.5 github 与 gitlab 的区别?

  1. 相同点:都是基于web的git仓库

  2. 区别:

    github 面向开元及私有软件项目;私有仓库需要付费;

    gitlab 面向企业;允许免费仓库设置权限(免费),允许对project设置权限(安全),设置获取到团队的改进进度(便捷)

10.6 .gitignore 的作用?

配置git忽略的文件或文件夹,不会提交到版本库

11 django详细(待完善)

11.1 django基础

11.1.1 cookie&session
11.1.2 csrf_token

cookie原码分析;

11.1.2 MTV与MVC

11.2 django进阶

11.2.1 orm

11.3 drf

12 vue(待完善)

更多推荐

python初中级开发面经(持续更新2020-3-7)