java、c++、android开发面经纪要
部分参考资料为https://www.nowcoder.com/tutorial/94/
持续更新中
数据库
SQL
- SQL注入
SQL注入是通过在涉及到数据库操作的接口函数的参数中传入一些sql命令,在具体的操作时被直接带入到sql命令中进行运行,导致数据库出现意外结果等。解决方法一般是格式校验以及sql预编译。
索引
是一种单独的数据库结构,可以在创建时或者是创建后来创建数据库表的索引,索引可以提高相关的查询速度。
索引可以分为唯一索引和普通索引、单一索引和组合索引、全文索引和空间索引。
组合索引的生效方式按照最左前缀方式生效。
大量的表索引不仅会占用磁盘空间,还会影响insert、delete与update等语句的性能。
不适合创建索引的情况:
- 频繁更新的字段不适合建立索引
- where条件中用不到的字段不适合建立索引
- 数据比较少的表不需要建立索引
- 数据重复且分布比较均匀的字段不适合创建索引,例如性别、真假值
- 参与列计算的列不适合建立索引
MyISAM和InnoDB两种数据库引擎都是使用的B+树格式当作数据库索引结构,但是具体实现方式不一样。 (MEMORY和HEAP引擎可以使用HASH或者BTREE索引)
InnoDB是使用主键作为主索引,整个数据文件就是按照B+树的格式来进行组织的。
- B+树可以分为聚簇索引和辅助索引,聚簇索引是根据主键创建的一颗B+树,其叶子节点就存放了对应的所有记录;而辅助索引是根据被索引值来创建的一颗B+树,其叶子节点放的是索引键值以及该值对应的主键,所以如果通过辅助索引来进行查找,之后可能还需要通过主键查找得到完整的数据,但同时因为辅助索引中没有记录完整数据,因此同样大小限制下每页可以存放更多的键值,导致其树深度会小于主键对应的树深度。
MyISAM可以没有主键,数据文件和索引文件是分离的。
回表
数据库查询语句中找到对应行号后再到原数据表中取出对应数据的过程。
事务
- 四特性:ACID
- 原子性
- 一致性
- 隔离性
- 隔离级别 4种,
脏读 不可重复读 幻读 READ UNCOMMITTED 读未提交 可能 可能 可能 READ COMMITTED 读提交 不可能 可能 可能 REPEATABLE READ 可重复读 不可能 不可能 可能 SERIALIZABLE 串行化 不可能 不可能 不可能 - 隔离级别分别解决脏读、不可重复读、幻读等问题。
- 脏读:当前事务可以读到其他事务中未提交的数据(脏数据)
- 不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。
- 幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。
- 隔离性一般通过加锁来实现,行锁,表锁等。
- 隔离级别 4种,
- 持久性
- 类型:
- 扁平事务
- 带保存点的扁平事务
- 链事务
- 嵌套事务
- 分布式事务
InnoDB引擎支持除嵌套事务外的其他几种。
锁
行级锁
- 共享锁(S) 允许事务读一行数据
- 排他锁(X) 允许事务删除或更新一行数据
- 共享锁之间兼容,共享锁和排他锁不兼容
- 行锁算法
- Record Lock: 单个记录上的锁
- Gap Lock:间隙锁,锁定一个范围,但是不包含记录本身。
- Next-Key Lock:Gap Lock+Record Lock,锁定一个范围并且将记录也锁定。
表级锁
- 意向共享锁(IS)
- 意向排他锁(IX)
死锁:事务互相等待无法推进,解决办法是超时机制以及等待图(wait-for graph)的死锁检测。
优化
查询优化
- 使用索引 但是有些查询情况索引没法优化
- 对子查询进行优化 使用表连接查询来替代子查询 (join)
插入优化
禁用索引
禁用唯一性检查
使用批量插入
LOAD DATA INFILE语句
对于InnoDB引擎的数据库表
- 禁用唯一性检查
- 禁用外键检查
- 禁用自动提交
大规模数据表的优化
- 优化SQL和索引
- 增加缓存,将常用但是不常修改的数据加载到内存中,直接从内存查询
- 读写分离
- 使用分区表, 中间表
- 垂直拆分
- 水平拆分
慢查询优化
- 开启慢查询日志 记录查询时间较慢的操作到日志中
- 分析慢查询日志 根据具体的慢查询语句具体分析和优化
- 修改索引没起作用的语句
- 优化数据库结构
- 分解关联查询
- 优化limit分页
explain和describe语句用来分析查询语句的执行情况
数据库基础
- 范式 共有六种范式,一般只需满足3NF即可。
- 1NF 无重复的域,每个属性不可再分,例如商品有名称和数量是两个属性,则需要分解为商品名称和商品数量两个属性才行。
- 2NF 非主属性完全依赖于候选码 (主键)
- 3NF 消除冗余信息(消除传递依赖),表中若含有其他表的主键,则不应该含有该表中的其他字段信息。
- mysql引擎
- InnoDB
- 支持事务四特性,支持行锁定和外键,为mysql的默认存储引擎。
- 为处理巨大数据量的最大性能设计,cpu效率高于其他基于磁盘的关系型数据库。
- MyISAM
- 拥有较高的插入、查询速度,但不支持事务
- 等。。。
- Memory、Merge、Archive、Federated、CSV、BLACKHOLE等。
- InnoDB
- 三种日志文件
- binlog 二进制日志文件,记录所有有关修改数据库的操作
- redo log 用来实现事务持久性的日志
- undo log 用来完成事务回滚的日志
- MVCC
Multi-Version Concurrency Control ,多版本并发控制协议,InnoDB使用该协议来实现RR的隔离级别,读不加锁,可以实现很好的并发性能。 - MySql的主从同步
mysql数据库如何实现高可用系统中主从服务器的数据库同步?- 主服务器将数据更改记录到binlog中
- 从服务器复制binlog到自己的中继日志(relay log)中
- 从服务器重做中继日志中记录的操作,实现主从服务器的数据一致
计算机网络
网络体系结构
- 七层网络结构
- 物理层
- 透明地传输比特流
- 硬件:集线器、中继器
- 数据链路层
- 将网络层的IP数据报组装成数据帧
- 功能
- 链路链接的建立等
- 帧定界和帧同步
- 差错检测
- 硬件:交换机、网桥
- 网络层
- 任务
- 将传输层传下来的报文分组
- 选择合适的路由
- 功能
- 组包、拆包
- 路由选择
- 拥塞控制
- 硬件:路由器
- 协议:ICMP、ARP、IP等
- 任务
- 传输层
- 负责两个进程间的通信
- 功能
- 提供端到端可靠的服务
- 为端到端连接提供流量控制、差错控制、服务质量等管理服务
- 协议:TCP、UDP
- 会话层
- 不同主机各进程间的对话
- 管理主机间的会话进程、包括建立、管理以及终止,是端到端的服务。
- 表示层
- 负责在两个不同信息格式的通信系统之间交换信息
- 数据加密解密以及数据压缩和解压等功能
- 应用层
- 提供系统于用户的接口
- 功能
- 文件传输
- 访问和管理
- 电子邮件服务
- 协议:FTP、SMTP、POP3、HTTP、DNS、TELnet等
- 物理层
- 五层网络结构
- 应用层
- 传输层
- 网络层
- 数据链路层
- 物理层
协议
TCP协议
- 特性:面向连接的、提供全双工的服务,数据流可以双向传输,也可以是点对点的。
- 流量控制
如果发送方发送数据超过接收方应用程序读取数据的速度较多,会使接收方的接受缓存溢出,发送方维护一个“接受窗口”变量,控制接收方当前可用的缓存空间,从而进行发送速度和接受速度的匹配。 - 如何做到可靠?
- 校验和
- ACK确认字段以及超时重发机制
- 滑动窗口机制增加发送效率
- 拥塞控制避免网络拥堵
- 三次握手、四次挥手
- 断点续传如何实现?
- 一般在上层协议中进行实现,例如http、ftp等
HTTP协议
- 特点:支持客户/服务器模式;简单快速;灵活;无连接;无状态
- 客户端一般指web浏览器或者是一些应用;服务器则是指web服务器,也包括一些html文件、图片之类的web对象
- 请求方法
- GET
- POST
- HEAD 只请求头部信息
- PUT
- DELETE
- 状态信息
- 301 Permanently Moved 被请求的资源已永久移动到新位置,新的URL在Location头中给出,浏览器应该自动地访问新的URL。
- 302 Found 请求的资源现在临时从不同的URL响应请求。301是永久重定向,而302是临时重定向。
- 200 OK 表示从客户端发来的请求在服务器端被正确处理
- 304 Not Modified 304状态码是告诉浏览器可以从缓存中获取所请求的资源。
- 400 bad request 请求报文存在语法错误
- 403 forbidden 表示对请求资源的访问被服务器拒绝
- 404 not found 表示在服务器上没有找到请求的资源
- 500 internal sever error 表示服务器端在执行请求时发生了错误。
- 503 service unavailable 表明服务器暂时处于超负载或正在停机维护,无法处理请求。
- 文件格式
- text/html: HTML格式
- text/plain: 纯文本格式
- image/jpeg: 图片格式
- application/json: json格式
- application/x-www-form-urlencoded: form表单编码成key-value格式
- multipart/form-data:表单中文件上传时使用
https协议
- 客户端发起连接请求
- 服务器回复ssl/tls证书信息,包括公钥
- 客户端验证证书的合法性
- 如果验证合法,则生成随机值作为对称加密的密钥,用服务器的公钥加密后发送给服务器
- 服务器用私钥解密,拿到对称加密的密钥
- 后续客户端和服务器之间的信息都使用对称加密密钥来加密和解密,可以保证效率和安全性
FTP协议
- 使用两个并行的TCP协议来传输文件
- 控制连接(持久): 传输控制信息,包括用户口令、shell控制命令等等。该连接在FTP会话期间一直保留。
- 数据连接(非持久):用来传输实际的文件,该连接在文件传输开始前建立,文件传输结束后马上解除连接。
- 是一个有状态的协议
- 使用两个并行的TCP协议来传输文件
DNS协议
- DNS在进行区域传输的时候使用TCP协议,在其他时候使用UDP协议。 一个区中一般会有主DNS服务器和辅助DNS服务器,辅助DNS服务器会定期复制主DNS服务器中的数据信息作为备份,同时同步主DNS服务器的一些数据修改,这时由于数据量较大,考虑使用TCP协议,能够发送超出512字节的数据,并且TCP是稳定安全的。
- 而其他的情况下一般都是使用UDP协议,不用三次握手,可以减轻服务器的负担。
IP协议
- 报文字段
域名和IP
域名解析成ip的过程
- 检查浏览器中缓存中是否有对应ip
- 检查本机系统缓存中是否有对应ip
- 向本地域名解析服务器系统发起域名解析请求
- 本地域名解析服务器向根域名解析服务器发起域名解析请求
- 根域名服务器返回gTLD域名解析服务器地址
- 本地域名解析服务器向gTLD服务器发起解析请求
- gTLD服务器接收请求并返回Name Server服务器
- Name Server服务器返回IP地址给本地域名解析服务器
- 本地域名解析服务器缓存解析结果
- 本地域名解析服务器返回IP地址给本机
IP地址、子网掩码、网关
- IP地址 IPV4为32位地址 每个计算机的唯一标识,但是每个计算机可以有多个IP(当其同时处于多个子网中时)
- 子网掩码 子网掩码与IP地址做与运算,用来屏蔽宿主机id,运算得到该IP的子网网段,可以与当前进行比较,确定目标主机是在本地网络还是远程网络。
- 网关 不同子网间通信时,需要先将待发送数据包发送给同一子网中的网关,然后由该网关转发给目标子网中的网关,然后由该网关发送给目标主机。
IP寻址
- 本地网络 直接使用arp广播获取目标主机的mac地址,然后进行发送
- 非本地网络,将数据包发送给自己的网关,需要从缓存中查询或者是同样使用ARP协议广播获取网关的mac地址,然后依此方式继续传播到目的主机。
操作系统
linux操作系统常用命令
ps 查看想知道的进程信息
# 参数 -A #显示当前所有进程 -aux #查看进程状态
top 查看进程运行状态、内存使用情况
cat 查看文件内容
grep 全局正则表达式搜素。过滤关键词,与前面的命令结合管道使用可以查看到对应的关键信息。
# 参数 -A n --after-context #显示匹配字符后n行 -B n --before-context #显示匹配字符前n行 -C n --context #显示匹配字符前后n行 -c --count #计算符合样式的列数 -i #忽略大小写 -l #只列出文件内容符合指定的样式的文件名称 -f #从文件中读取关键词 -n #显示匹配内容的所在文件中行数 -R #递归查找文件夹 ...
hostname 修改主机名
开机自启动
- 使用cron命令,将要启动的应用写成脚本,使用cron进程进行管理,每次开机自启动都自动运行该脚本,脚本中需要有相关的环境变量
- 使用/etc/rc.d/rc.local 直接修改这个开机自启动的脚本文件,并授予相关权限
free 查看内存使用情况
-m #以MB为单位显示 -s<间隔秒数> #持续观察内存使用情况
tar
tar -zxvf #提取文件 tar -zcvf #压缩文件
netstat 查询连接数
ps -ef | grep 进程名 # 查看进程pid netstat -nap | grep 进程id # 查看进程对应的端口信息 netstat -nap | grep 端口号 # 查看该端口对应的进程信息
ping
ICMP协议kill 命令 根据PID删除进程。
进程
进程与线程
- 进程是系统资源调度的最小单位,线程是CPU调度的最小单位
- 进程在执行时拥有独立的内存单元,进程中的多个线程共享进程的内存,如代码段、数据段、扩展段;但每个线程拥有自己的栈段和寄存器组。
- 线程切换只需要切换硬件上下文和内核栈,进程切换还需要先刷新TLB获取新的地址空间。
- 每个线程都有自己的上下文,包括线程ID、栈、程序计数器、通用寄存器等。
进程间的通信机制
- 匿名管道 常用在父子进程之间
- 有名管道 可用在任两个进程之间
- 信号
- 硬件方式 无效内存访问? 除数为0等信号
- 软件方式
- 键盘输入
- 消息队列 消息队列存放如内核中,只有内核重启或者是显示地删除时才会被删除,可以有多个进程进行消息的写入
- 信号量
- 共享内存 多进程访问时结合信号量对内存访问进行控制
- socket通信
进程的状态
- 就绪
- 运行
- 阻塞
进程(线程)的非抢占式调用和抢占式调用
- 抢占式 会被CPU的调度机制进行切换
上下文切换消耗更大,但可以防止某一进程长时间占用资源 - 非抢占式 在运行中途不会被CPU调度机制切换,除非进程自身退出运行状态。也称合作式线程,需要自己手动或者被动的挂起(主动或者被动阻塞、运行结束等)来进行多线程间的合作,
- 抢占式 会被CPU的调度机制进行切换
僵尸进程和孤儿进程
- 父进程已经结束,但子进程还在运行,会变成孤儿进程,会被init进程收养。
- 父进程使用fork创建子线程,如果子线程退出,而父进程并不知道,这种子进程称为僵尸进程。
协程
协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。
- 线程和协程的区别
- 协程执行效率极高。协程直接操作栈没有内核切换的开销
- 不需要多线程锁机制,协程之间属于一个线程,不会写变量冲突
- 一个线程可以有多个协程
锁
悲观锁 只要有访问操作都上锁,无论是否发生了并发竞争
- 重量级锁
- 自旋锁
- 自适应自旋锁
乐观锁 访问时如果发生了数据修改再上锁
- CAS CompareAndSwap
- 轻量级锁
- 偏向锁
自旋锁 获取锁失败时会进行自旋操作,轮询获取锁状态,不会阻塞
公平锁 按照先来先到的原则竞争锁就是公平锁
非公平锁 不按照原则竞争资源
共享锁 一般用于读操作
死锁 两个进程互相等待从而都阻塞的一种情况,可以使用等待超时方式进行解决。
IO多路复用
select 无差别轮询所有流
poll 无差别轮询,但使用链表存储数据,无最大连接数限制
epoll event poll 事件触发,与文件描述符(FD)关联 ,数据结构为红黑树加双向链表,红黑树存放对应的fd索引结构,双向链表存放回调函数相关,两个数据结构分别对应,且可以快速的插入、删除和查找。
对文件描述符的操作有两种形式- LT模式 检测到事件发生时通知应用程序,应用程序如果不处理该事件,下次仍然会再次通知此事件。
- ET模式 检测到事件发生时通知应用程序,如果不立即处理该事件,下次就不会再通知此事件。
同时以及该多路复用方式的数据结构是什么样的,用什么形式来存储
原子操作
- 如何保证原子操作的原子性?
- 开关中断(单核CPU)
- 关中断,此时正在执行的指令集无法被打断,
- 开中断,执行完毕原子操作后开中断
- 总线锁(多核U)
- 向总线发送锁信号,其他核无法访问相应的共享内存
- 缓存锁(多核U)缓存一致性机制(MESI协议)
- 开关中断(单核CPU)
地址
- 物理地址
存储器里的真实物理地址,与地址总线相对应 - 逻辑地址
从应用程序角度看到的各种物理单元的地址,需要经过地址翻译器等转换成物理地址 - 虚拟地址(线性地址)
- 虚拟地址的构成
- 虚拟地址如何转换成物理地址(MMU)?
存储
存储介质
- 寄存器 ns级别响应,字节级别容量
- CPU缓存 cache 10ns级别响应,字节-MB级别容量
- 主存 100ns级别响应 GB级别容量
- 外部存储介质(外存) ms级别响应,TB级别容量
虚拟内存
抽象了应用程序物理内存的细节,将物理内存映射到虚拟内存页中。
由操作系统的页表来保存映射关系,层级最低的页表存放实际页面的物理地址,然后高层级的页表包含指向低层级页表的物理地址。指向顶级的页表的地址存在寄存器中,当发生地址索引时先依次索引,然后找到具体的物理地址。大页机制 由于内存访问速度较慢,如果逐级查询物理地址就会很慢,所以直接将虚拟地址和物理地址的对应关系存入TLB地址转换cache(快表)中,这样如果能直接从TLB中获取到对应的物理地址,就可以大大提高速度。而如果TLB的命中率降低会很大程度上影响大内存工作集的应用程序,因此往往在分页时增加页的大小,这样存放地址的页表就能变小,TLB能存放的内容就会变多,提高TLB的命中率。
- TLB的组织形式
- 全相联 暴力匹配 高命中,高延迟
- 直接匹配 hash计算 低延迟,低命中
- 组相连
- 全相联和直接匹配结合,hash计算得到对应组,组内暴力匹配相同项,可以保持较高的命中率和较低的延迟
- TLB的组织形式
虚拟内存模型
- 栈区 由编译器自动分配释放,存放局部变量,函数参数等
- 堆区 由程序员分配释放,存放对象等
- 全局区 存放全局变量和静态变量等,由系统释放
- 文字常量区 存放常量字符串等常量区域,由系统释放
- 程序代码区 存放函数体的二进制代码
页面置换算法
- 最佳置换算法(Optimal) 理想化的,将那些永不使用的或者最长时间内不再被访问的页面置换出去。
- 先进先出置换算法FIFO 将内存之驻留最久的页面予以淘汰
- 最近最久未使用置换算法LRU 最近最久未使用的页面予以淘汰
- Clock置换算法NRU 最近未用算法,向最近的一个未被使用过的页面淘汰
- 最少使用置换算法LFU 选择最近使用最少的页面作为淘汰页
下面这部分参考自https://blog.csdn.net/sunxianghuang/article/details/51883496
操作系统的功能
- 用户接口 命令接口、程序接口、图形接口
- 处理机管理 进程控制、进程同步、进程通信、进程调度
- 存储管理 内存分配、内存保护、地址映射、内存扩充
- 设备管理 缓冲管理、设备分配、设备处理、虚拟设备管理
- 文件管理 文件存储空间管理、目录管理、文件读写管理、文件保护、文件系统的安全性、文件接口
结构
内核
- 核心态
一般操作系统内核的程序模块运行时处于核心态,其他程序运行时处于用户态。核心态状态下处理机拥有较高的权限,可以执行一切指令。
- 核心态
外壳
- 用户态
具有较低的执行权限,只能执行规定的命令和访问指定的存储器,一般的用户程序在这一级别执行。
- 用户态
用户态切换到内核态:
- 系统调用 中断,软件中断,用户主动调用系统级服务
- 外设中断 例如外设完成了规定的输入任务,硬盘读取等
- 异常 例如缺页异常等
操作系统特征
- 并发 多个程序宏观上同时运行的特征
- 虚拟 虚拟内存、虚拟处理器等
- 共享 程序并发运行带来的资源共享
- 不确定性 无法确定每个作业的执行的顺序和执行时间
现代操作系统新特性
- 微内核
- 多线程
- 多处理器
- 分布式操作系统
- 面向对象技术
操作系统分类
- 多批道处理系统
- 分时系统
- 实时系统
- 网络操作系统
- 分布式操作系统
处理机调度
- 先来先服务 FCFS
- 短作业优先 SJF
- 时间片轮转调度算法 RR
- 高响应比优先 HRRN 按照高响应比((已等待时间+要求运行时间)/ 要求运行时间)优先的原则
- 优先权(Priority)调度算法
- 多级队列调度算法
设计模式
设计模式的部分另外还参考了http://c.biancheng.net/view/1317.html
设计模式的六大原则
- 单一职责原则
一个类应该专注于一种任务和职责,有利于保障对象的高内聚和细粒度。 - 里氏替换原则
父类可以被替换成子类,但是子类被替换成父类则不一定能行。即子类可以扩展父类的功能,但不能够改变父类原有的功能,尽量不要重写父类的方法(抽象方法除外)。 - 依赖倒置原则
高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象,要面向接口进行编程。
可以降低类间的耦合性,提高稳定性,同时减少并行开发引起的风险。
例如:猴子和马都是动物,它们应该依赖于动物类,使用动物类来描述动物间的共性,而不是声明成多个类。应该让猴子和马是子类,依赖于其底层的类。编程时则可以直接面对动物类提供的接口进行编程。 - 接口隔离原则
接口应该更加细粒度,每个类为其建立专用的接口,而不是将所有的功能结合成一个大的接口让所有的类调用。 - 迪米特原则
每个类只与它的直接对象进行交流,两个类之间不发生直接的通信交互,必要时可以由第三方进行转发,有利于降低类之间的耦合度,提高模块的独立性和重用能力。 - 开闭原则
在设计一个模块的时候应当让这个模块可以在不被修改前提下被扩展,在不修改该模块源代码的情况下改变模块的行为。
感觉和多态有点类似,在设计某个模块的功能时考虑到其可能发生的功能改变,将其主要功能抽象成父类,具体实现用子类来实现,如果行为需要发生改变则可以新建一个子类重新定义其功能。- 提高复用性
- 提高可维护性
- 提高灵活性
- 易于测试
设计模式
- 单例模式
- 一个类只会有一个实例对象
- 无法从外部直接实例化该对象(构造方法私有等)
- 开放一个唯一的访问接口供外界访问
- 分类
- 懒汉式单例 在获取实例时再创建静态实例,需要考虑线程安全情况
- 饿汉式单例 在类加载时就以及创建了静态实例,可以直接用于多线程
- 简单工厂模式
- 一个创建产品对象的工厂接口,函数的返回类型时产品对象的抽象接口,根据传入的参数中指示的产品信息,返回对应的产品对象,每个产品对象都继承于产品对象的抽象接口。如果需要有新的产品引入,那么可能需要修改原本生产产品的函数代码。
- 工厂方法模式
- 抽象工厂类负责创建具体工厂,具体工厂负责创建具体产品,具体的产品继承于抽象产品,实现了抽象产品的接口。
- 抽象工厂模式
- 在工厂方法的基础上增加了不同种类的产品,有多个不同产品的抽象接口。
- 策略模式
- 将算法的过程封装起来到一系列的策略类当中,同一个接口使用多种方法实现,用户根据实际情况选择调用不用的算法。类似于JAVA中的map的实现方式有hashmap treemap等等方式,但我们在使用的根据情况选择我们需要的就行了。
- 观察者模式
- 类似于发布-订阅者模式,当发布的信息改变时需要通知到每个订阅的观察者。
- 实现起来相当于有一个抽象的主题,这个主题的类中管理所以的观察者,当发生变化时调用方法通知列表中的每个观察者,观察者继承于一个抽象基类。
- 责任链模式
- 将责任处理者进行一个抽象的定义,该定义中包括一个处理函数和获取后一个责任处理者的信息,然后对抽象基类的具体实现来定义具体的每个责任处理者的处理思路。
- 通过基类的链式结构构成一个责任链,待处理事务会在这个链上一直传播,知道被解决或者是链结束为止。
- 装饰器模式
- 在不改变现有对象结构的情况下,动态地给对象增加一些职责(或者说增加一些额外的功能)的模式。
- 对象每有一类特殊的模式就得单独为其设置一个抽象的装饰器,然后对应该类的每一个具体的对应的装饰角色就得为其单独设置一个继承自抽象装饰器的具体装饰器。
- 抽象装饰器与原始类会继承同一个公共接口,彼此之间通过对象注入的方法进行调用。
- 适配器模式
- 为两个目标类之间的通信进行一个适配,将它们之间的接口格式进行转换。
- 将两个类进行解耦,提高代码的重用性。
- 具体实现时采用了类和接口的同意使用,以及对象注入等方法。
- 代理模式
- 一个类不适合或者是不能直接访问另外一个类时可以使用中间类作为代理访问。
- 模板方法模式
- 通过定义抽象类实现一些公共的模板操作,而一些具体细节通过虚函数的方式定义等待子类中进行实现
- 类似于在排序算法中的自定义排序等方式,实际上就是修改了模板方法中的一些默认的或者时抽象的方法,可以让用户对某些细节部分进行一些自定义的修改。
- 生产者-消费者模式
- 中间通过一个缓冲队列来进行解耦,生产者生产的产品存到缓冲队列中,若缓冲队列满,则生产者阻塞;消费者从缓冲队列中拿取产品进行消费,若缓冲队列空,则消费者阻塞
java
java基础
默认值
- 变量为引用数据类型,默认值为null
- 变量为基本数据类型,默认值都为0(char 为’\u0000’ ,boolean为false)
包装类
- 自动装箱
- 自动拆箱
- 对Integer和Double的类型判断,可以使用使用其基类Number的类型转换方法,将其转换为同一类型之后再比较。
java多线程
三大核心特性
- 原子性
- 一个操作中的所有步骤要么全部成功,要么全部失败
- 可见性
- 当一个线程对共享变量进行修改后,其他的线程立马能够获取到。因为cpu的获取顺序是一级缓存、二级缓存、三级缓存、内存,一级缓存是cpu内核私有,其他线程访问不到的,同时当更新时也会同步在多级缓存和内存中进行更新。
- 有序性
- JVM允许编译器和处理器对指令进行重排,以提高效率,虽然不会影响到单线程程序的执行,但是会影响到多线程并发执行的正确性。
- 原子性
volatile
- 作用原理
- 语法上它用于修饰变量类型。
- 在被修饰的变量被修改后,会保证将修改的值马上同步到主存。
- 可以保证两个特性,可见性和顺序性
- 不能保证原子性
因为如果两个线程同时进行i++操作,因为这并不是一个原子语句,所以会分成几步,一是读取i的值,二是计算结果,三是更新i的值。可能两个线程同时都能读取到最新的i的值,然后同时更新,但是结果只是得到了一次加法,而不是两次加法。
- 作用原理
synchornized
- 作用范围
- 语法上只能作用于代码块或者是方法的前面,只能锁住引用类型的对象。
- 但实际上是锁住的调用该方法的对象。
- 锁的类型
- 重量级锁
没获取到锁的会进入阻塞状态,等待被唤醒 - 轻量级锁
CAS方式 - 偏向锁(自适应自旋锁)
没获取到的会使用自适应自旋的方式重试
- 重量级锁
- 可以保证三特性
- 原子性
- 可见性
- 当一个线程对共享变量进行修改后,其他的线程立马能够获取到。因为cpu的获取顺序是一级缓存、二级缓存、三级缓存、内存,一级缓存是cpu内核私有,其他线程访问不到的,同时当更新时也会同步在多级缓存和内存中进行更新。
- synchornized可以让同步代码块在执行时直接从主内存中拉取数据,然后退出时将数据操作更新至主内存,保证其可见性。
- 有序性
- 作用范围
lock是一个类或者说一个接口
- 也可以保证原子性、有序性和可见性
- 需要自己手动的进行锁的获取和释放
实现方法 参考 https://blog.csdn.net/king_kgh/article/details/78213576
继承Thread类
- 要执行的函数定义在run函数内,但是在启动线程的时候通过start方法来启动
实现Runnable接口
- 自定义类实现Runnable接口中的run函数
- 新建线程将该类的对象作为参数传入
class Test implements Runnable{ @override void run(){}} Test test=new Test(); new Thread(test).start();
- lambda方式创建线程任务
Runnable task = () -> {while (true) {// 输出线程的名字printThreadInfo();} }; // 创建线程对象,并将线程任务类作为构造方法参数传入 new Thread(task).start();
使用内部类
- 直接定义内部类实现run方法
// 基于子类的方式 new Thread() {@Overridepublic void run() {while (true) {printThreadInfo();}} }.start();// 基于接口的实现 new Thread(new Runnable() {@Overridepublic void run() {while (true) {printThreadInfo();}} }).start()
- 使用lambda表达式简写(同上)
Runnable task = () -> {while (true) {// 输出线程的名字printThreadInfo();} }; // 创建线程对象,并将线程任务类作为构造方法参数传入 new Thread(task).start();
- 直接定义内部类实现run方法
使用定时器Timer
- Timer实际上是一个基于线程的工具类
通过Callable接口,可实现异常抛出和接受返回值
// 创建线程任务 这里的泛型是指该函数对应的返回值类型(此处为Integer) Callable<Integer> call = () -> {System.out.println("线程任务开始执行了....");Thread.sleep(2000);return 1; };// 将任务封装为FutureTask FutureTask<Integer> task = new FutureTask<>(call);// 开启线程,执行线程任务 new Thread(task).start();// 为所欲为完毕之后,拿到线程的执行结果 Integer result = task.get(); System.out.println("主线程中拿到异步任务执行的结果为:" + result);
基于线程池
- 使用ExecutorService接口
java虚拟机 JVM
JVM由四大部分组成
- ClassLoader 类加载器
- 负责加载字节码文件(class文件),只负责加载,能否运行由执行引擎决定
- Runtime Data Area 运行时数据区,内存分区
- 堆
- 存放对象实例,所有的对象实例和数组都应该在堆上进行分配
- 一般堆大小是可扩展的
- 方法区
- 存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
- 运行时常量池也是方法区的一部分
- 虚拟机栈
- 线程私有
- 每个方法执行的时候,虚拟机会同步创建一个栈帧,用于存储局部变量表、操作数栈、方法出口、动态链接等信息,然后每个方法被调用的时候这个栈帧就会被推进虚拟机栈,调用结束就会被推出虚拟机栈。
- 本地方法栈
- 存放本地方法调用的时候的栈帧情况,跟虚拟机栈类似
- 程序计数器
- 程序计数器是java程序的流程控制以及多线程切换的实现基础
- 堆
- Execution Engine 执行引擎
- class文件加载后,指令和数据信息会放入内存中,执行引擎负责将jvm指令集翻译为操作系统指令集。
- Native Interface 本地库接口 (JNI)
- 负责调用本地接口,可能是其他不同语言提供的接口
- ClassLoader 类加载器
jdk、jre
jdk是java开发工具包,包括了java开发编译调试环境和java运行环境
jre是java运行环境,只是负责运行,包括java虚拟机环境、java平台核心类、支持文件等jvm启动过程
- JVM的装入环境和配置
- 查找JRE环境,查找顺序依次是自己目录下的JRE、父目录下的JRE、注册路径中的JRE
- 装载JVM
- 初始化JVM,获得本地调用接口
- 运行Java程序
- JVM的装入环境和配置
java程序运行
java代码编译过程 略
类加载过程
- 加载
将class文件中的二进制字节流读入内存 - 链接
- 验证
验证字节流是否符合JVM规范,元数据验证、类检查、符号引用验证、权限检查(private public)等等。 - 准备
为类的类变量(静态变量)开辟空间并赋默认值- 如果为基本类型默认值为0
- 如果是引用类型则默认为null
- 如果有定义默认值,则为设定的值
- 解析
将class在常量池中的符号引用转变为直接引用
- 验证
- 初始化
- 声明变量时直接给变量赋值的情况
- 运行静态初始化块代码
- 使用
- 卸载
- 加载
类加载器
通过一个类的全限定名来获取描述该类的二进制字节流- 三层类加载器,双亲委派的类加载架构
- 启动类加载器
- 顶层类加载器,例如java.lang.Object的类加载器就是启动类加载器。如果此时自己定义一个java.lang.Object的类,但是这个类是使用的应用程序类加载器,在加载的时候由于双亲委派模型的存在,实际上在加载的时候还是是通过父类加载器加载的原始的java.lang.Object的,避免了混乱情况的出现。
- 扩展类加载器
- 应用程序类加载器
- 启动类加载器
- 双亲委派模型
- 当某个类进行加载时首先会让其父类加载器对这个类进行加载,如果加载失败,则调用自身的类加载器进行加载。
- 避免重复的一些字节码加载,例如多条System.out.println()语句中只会对System对象的字节码只会加载一次。
- 三层类加载器,双亲委派的类加载架构
JVM垃圾回收
垃圾回收关注的内存区域主要是堆区域和方法区内存,这块区域有较大的不确定性。
对垃圾的定位
- 引用计数法
- 在对象中添加引用计数器,每当有对象引用它就加一,引用失效就减一,为零则表示可回收。
- 无法解决对象之间循环相互引用的问题,需要很多额外处理。
- 可达性分析
- 以一系列称为“GC Roots”的根对象作为起始节点集,根据引用关系进行搜索遍历,构成“引用链”,未在引用链中的对象则可回收。
- 主流的JVM中一般都使用的是该方法
- 可作为GC Roots的对象
- 虚拟机栈中的引用对象(各个栈帧中的本地变量表)
- 方法区中引用型类静态变量
- 方法区中常量引用对象,例如常量池等
- 本地方法栈中引用到的对象,即JNI执行中用到的对象
- 虚拟机内部引用,基本数据类型的class对象,常驻的异常对象,系统类加载器等
- 被同步锁(synchornized)持有的对象
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
- 引用计数法
对方法区的回收
- 对常量的回收
- 对不再使用的类的信息的回收
需要满足三个条件- 该类的所有实例都被回收,java堆中不存该类及派生类的实例对象
- 该类的类加载器已被回收,这条通常很难达成
- 该类的java.lang.Class对象未被引用
垃圾回收方法
- 两个分代假说
将不同代的对象放在一块便于管理,弱代的放在一块可以只关注保留少量的存活对象,强代的放在一块可以以更低的频率对其进行管理。- 弱分代假说 朝生夕灭的对象
- 强分代假说 熬过垃圾回收次数越多,越难以消亡
- 回收算法
- 标记-清除算法
- 将所有需要回收的对象进行标记,然后清除
- 会有内存碎片问题,同时执行效率不稳定
- 标记-复制算法
- 把内存分为两份,其中一份为备份,将存活的对象复制的新的一个分区,同时将原来的一块内存整块清除
- 无内存空间碎片,存活对象复制时的开销可能很大,同时内存浪费较多
- 改进型:半区复制分代策略 一般用于新生代垃圾回收
- 内存分为:Eden:Survivor:Survivor=8:1:1
- 每次垃圾回收后将Eden区和一个Survivor区存活的对象一起复制到另一个Survivor区中,然后对这两区进行全部的清理,然后下次这个新的Survivor区就作为备份
- 总共会有10%的空间浪费,但充分利用了新生代朝生夕灭的特点
- 标记-整理算法 一般用于老年代垃圾回收
- 对各个对象进行标记,然后将存活的对象移动到内存的一端,保证存活的对象分布连续,然后将后面的区域进行整体清除。
- 标记-清除算法
- 跨代引用假说:跨代引用相对与同代引用只占极少数。
- 解决老年代对象引用新生代对象的情景
- 根据假说所说,将可能的跨代引用使用数据结构(“记忆集”)进行记录,当发生Minor GC时去这个集合中就能找引用链关系。
- 两个分代假说
完整GC流程
- GC分类
- Minor GC
- 一般发生在新生代区域的GC
- Major GC
- 一般发生在永久代(方法区)
- Full GC
- 一般发生在老年代的GC
- 进行全部的垃圾回收,包括年轻代、老年代、永久代
- 会暂停用户应用程序所有线程的执行
- Minor GC
- GC流程
- 新创建的对象会存放的新生代中,如果新建对象可以直接放在Eden区中,则不进行GC;如果新建对象无法放入Eden区中或者将Eden区完全挤满,这时候触发Minor GC。
- 触发Minor GC时,首先会进行新生代中对象大小与老年区中剩余空间大小进行比较,因为可能这些对象会被复制进入老年区。
- 如果新生代对象大小比老年区空闲空间大,则考虑是否有老年空间分配担保规则
- 如果有担保规则,则考虑空闲空间是否比历次Minor GC之后新生代剩余对象的大小大
- 如果大,则进行Minnor GC
- 如果小,则进行Full GC,之后再进行检查
- 如果没有担保规则,则直接Full GC,之后再进行检查
- 如果有担保规则,则考虑空闲空间是否比历次Minor GC之后新生代剩余对象的大小大
- 如果新生代对象大小比老年区空间空间小,则直接进行Minor GC
- 如果新生代对象大小比老年区空闲空间大,则考虑是否有老年空间分配担保规则
- 如果Minor GC后,新生代中存活对象可以放入备份的Survivor区中,则直接放入,GC结束
- 如果Minor GC后,新生代存活对象无法放入备份的Survivor区中,那就把存活对象放入老年代中
- 如果能放下,则结束
- 如果不能放下,就Full GC
- 如果Full GC之后还是无法成功,则报OOM
- GC分类
内存泄漏和内存溢出
内存泄漏:指程序运行过程中分配内存给临时变量,但是用完之后却没有被GC回收,始终占用着内存,既不能被使用也不能分配给其他程序使用,于是就发生了内存泄漏。
- 常发性内存泄漏
对应的代码多次被执行,经常发生 - 偶发性内存泄漏
对应的代码只有在特定条件下才会执行,偶尔发生 - 一次性内存泄漏
对应的代码只会执行一次,一定也只会发生一次 - 隐式内存泄漏
代码运行过程中没有及时地进行内存释放,在程序运行结束后才释放内存。但是由于服务器程序可能需要长时间地运行,未能及时地释放内存也可能会导致最终耗尽系统的所有内存。
- 常发性内存泄漏
可能的内存泄露场景
- java中长生命周期对象持有短生命周期对象的引用
- 在生命周期的对象失效之后,但是由于它被长生命周期的对象持有,所以无法被jvm回收,造成内存泄露。
- 在各种连接之类的场景中,例如数据库连接、socket连接、io连接等等。
- 这种相关的连接对象和结果对象需要自己显示关闭,而不会被GC回收。
- 在单例模式中持有其他类的引用
- 这种情况下由于单例对象的生命周期往往是整个程序的生命周期,所以被引用的对象永远不会被jvm回收,如果被引对象是Collection等对象,那么就可能引发严重的内存泄漏现象
- 改变hash值
- 当一个对象被存进集合中时,不能再改变该对象参与hash计算的值,否则其hash值会被改变,使得从集合中无法检索到该对象,但是又无法被垃圾回收,从而造成内存泄漏
- 数组模拟的栈中持有一些对象,但是退栈时只是移动了栈指针,并没用被推出的栈对象的引用置空,这样推出的对象就一直不会被回收,从而造成内存泄露
- java中长生命周期对象持有短生命周期对象的引用
内存泄漏解决方法
- 尽早释放无用对象
- 避免在循环中创建对象
- 使用字符串处理避免使用String,应该使用StringBuffer
- 尽量少使用静态变量,因为静态变量存放在永久代(方法区),基本不参与垃圾回收。
内存溢出:指程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存,于是就发生了内存溢出。
- 导致原因
- 内存中加载数据量过大
- 集合类中由对对象的应用,但是使用完之后没有清空,导致JVM不能回收
- 代码中存放死循环或者循环产生过多重复的对象实体
- 使用的第三方代码的BUG
- 启动参数内存设置过小
- 解决方案
- 修改JVM启动参数内存设置
- 检查是否在OOM错误前有其他错误导致
- 分析检查代码
- 使用内存查看工具动态查看内存使用情况
- 产生的位置
- Java堆
- 虚拟机栈和本地方法栈
- 方法区
- 本地直接内存
- 导致原因
java机制
反射
在运行过程中获取类的所有属性和方法,包括类类型委托
类似与函数指针,java中没有该概念,可以借用反射进行实现。代码块 {}
局部代码块
- 出现在方法内部
构造代码块
- 出现在类中方法外,在构造方法前被调用
同步代码块
- 用synchornized同步
静态代码块
- 使用static修饰,出现在类中方法外,在初始化时被调用。
执行顺序
父类静态块->子类静态块->父类代码块->父类构造器->子类代码块->子类构造器
java框架
springboot
基本概念
IOC(Inversion of Control) 控制反转
DI(Dependency Injection) 依赖注入
AOP(Aspect Oriented Programming) 面向切面编程
- 可能有时我们需要对一类对象进行同样的预操作和后处理,但是为了避免重复以及代码简单,可以使用AOP的概念
- 数据库事务的管控等场景
- 对Controller接口的用户过滤等场景
- 基本概念
- 连接点 实际被拦截的对象或者说被拦截的对象中的某个方法
- 切点 通过切点的正则式或者是指示器的规则定义去适配不同的连接点
- 通知 实际上就是约定下的流程的方法,包括前置通知、后置通知、环绕通知等等
- 目标对象 被代理或者说拦截的对象
- 引入 引入新的类和其方法,增强原有Bean的功能
- 织入 利用动态代理技术,将被连接点和通知织入约定流程
- 切面 可以定义切点、各类通知、引入的内容(感觉可以理解为包括切点、通知、引入)
- 注解@Aspect,见注解
- 可能有时我们需要对一类对象进行同样的预操作和后处理,但是为了避免重复以及代码简单,可以使用AOP的概念
Bean 需要进行管理的对象
- Bean的生命周期,大致可分为4大步骤
- Bean定义
- 通过注解对资源定位,将Bean的定义保存到BeanDefinition实例中,然后IOC容器装载该Bean的定义
- 创建Bean的实例化对象
- 依赖注入被@AutoWired修饰的变量中
- Bean初始化
- 接口BeanNameAware中的setBeanName方法
- 接口BeanFactoryAware中的setBeanFactory方法
- 接口ApplicationContextAware中的setApplicationContext方法(这个需要IOC容器实现ApplicationContext接口才会被调用)
- BeanPostProcessor的预初始化方法-postPrecessBeforeInitialization方法
- 使用@postConstruct标注的自定义初始化方法
- 接口InitializingBean中的afterPropertiesSet方法
- BeanPostProcessor的后初始化方法-postProcessAfterInitialization方法
- Bean生存期
- Bean销毁
- 使用@PreDestroy标注的自定义销毁方法
- 接口DisposableBean中的destroy方法
- 注意:
- 上述生命周期中提到的接口中,ApplicationContext接口是需要IOC容器来实现的
- BeanPostProcessor接口中的预初始化方法和后初始化方法应该由一个单独的component来继承实现,使用@component注解标注,这两种方法会对所有的Bean生效
- 其他提到的相关接口的方法以及由标注定义的自定义方法应该由被管理的Bean对象自身继承实现
- Bean定义
- Bean的生命周期,大致可分为4大步骤
IOC容器,一定要继承BeanFactory接口,其中定义了一些获取Bean对象的接口
- AnnotationConfigApplicationContext类是一种IOC容器,实现了ApplicationContent接口(该接口继承自BeanFactory)
注解
@Configuration 代表这是一个java配置文件,根据它来生成IOC容器并装配Bean
@Bean 将被注释的方法返回的对象装配到IOC容器中进行管理,可以用于数据库连接的建立等场景
@Component 通过扫描装配Bean,结合@Value来定义每个属性的初始值,而不需要去逐个使用@Bean来实现
@ComponentScan 标注扫描装配的策略,例如扫描的包的范围,哪些类需要不被装配,哪些类需要被装配,是否延迟初始化等
@AutoWired 需要注入的参数的注解
- 基本策略是通过类型类自动寻找Bean对象注入
- @Primary,在有多个符合条件的Bean对象时,有此注解的对象优先被注入
- @Qualified,直接通过Bean对象名(Bean在容器中的唯一标识)来指定需要注入的对象
@Value 可以修饰属性、也可以修饰方法
- 通过@value(“${database.driverName}”)之类的方式可以直接读取application.properties属性文件中的对应属性 注意:这里可以使用SpringEL表达式来进行简单计算
@ConfigurationProperties 可以直接把属性文件中的某个字段下属的所有内容都读取到类中同名属性中
@PropertySource 修饰主启动类,可以定义待加载的属性文件名
@Transactional 修饰方法,表明该方法需要事务运行,能自动的实现数据库资源的打开和关闭以及事务的回滚和提交(AOP)
- 可以修饰类和方法,修饰类的话则会对类中所有公有非静态方法失效
- 属性
- isolation 隔离级别
- Isolation.READ_COMMITED 读已提交
- Isolation
- timeout 超时信息 单位为s
- propagation 事务传播行为
- 一共有7中事务传播方式,都在Propagation枚举类中进行定义
- isolation 隔离级别
@Aspect 定义切面
- @Before
- @After
- @Around
- @AfterReturning
- @AfterThrowing
- @PointCut
- @DeclareParents 引入
- @Order 定义多个切面之间的执行顺序
@Entity 声明一个实例对象,用于后续的jpa映射
- @Table 定义表名
- @Id 定义主键
- @Column 定义列名
- @GeneratedValue 定义主键策略
- @Convert 定义转换器
@Alias Mybatis指定pojo别名
数据库操作(持久层框架)
jdbc
主要是通过JdbcTemplate对象的自动注入,然后自己编写sql语句以及对结果集的对应。JPA(Hibernate)
- 主要是通过Entity中的注解配置使得Entity和数据库表一一对应
- 然后通过继承JpaRepository<Entity_name, Entity_id>接口来实现定义接口
- 然后通过自动注入自己定义的jpaRepository对象来调用系列数据库方法来进行数据库操作,这部分方法是spring自动根据JPA规范来帮我们实现的,不用自己实现。
MyBatis
- 是一个从SQL映射到POJO的持久层框架,我们需要自己提供映射关系(使用XML或者注解表示)和POJO(普通的java对象),并且也提供了自动映射和驼峰映射等操作,同时也不屏蔽自定义sql语句。
- 是一个半自动的框架,保留了一定的自动化特性的同时又保留了许多用户自定义的部分,可以让用户做到轻松优化性能。
事务操作
- mybatis框架中springboot会自动生成DataSourceTransactionManager对象,作为事务管理器
- jpa框架中会自动生成JpaTransactionManager对象作为事务管理器
java中间件(开源项目)
Redis
- 键值型数据库,NoSql非关系型数据库
Redis基础
NoSql
- 非结构化
- 键值型 key-value
- 文档型 一条一条的json数据的存储,但是每条json数据中的数据项并不需要一致
- 图类型
- 无关联,无外键约束等等
- 非SQL,无法使用标准sql语言查询
- 事务四特性无法全部满足,为BASE级别
- 很多是内存型的数据库,性能更高
- 非结构化
Redis
- key-value
- 单线程,每个命令具备原子性
- 低延迟、速度快
- 基于内存
- IO多路复用
- 良好的编码 (C语言)
- 数据持久化
- 支持集群(主从集群、分片集群)
- 多语言API
基本命令
- 启动redis服务 redis-server
- 启动redis客户端 redis-cli [-h ip] [-p port] [-a password]
基本数据类型
- key:string
- value:
- String
- Hash
- List
- Set
- SortedSet
- GEO 地理坐标
- BitMap
- HyperLog
消息队列
MongoDB
其他
docker
docker与虚拟机的区别,优势
- 区别
- docker有更少的抽象层,不需要对硬件也进行虚拟化,而是直接使用物理机上的硬件资源
- docker利用的是宿主机的内核,而不是新的内核。而虚拟机在创建时都需要重新创建一个新的操作系统内核
- docker对宿主机的资源性能损耗较小
- 区别
docker利用namespace进行系统环境隔离,利用cgroup实现资源的隔离限制,利用容器镜像rootfs实现根目录文件的隔离
- cgroup
- namespace
- rootfs
C++
C++基础
基础数据类型及大小
类型 64位(byte) 32位(byte) char 1 1 char* 8 4 short int 2 2 int 4 4 long 8 4 long long 8 8 float 4 4 double 8 8 union 联合
- 其中的多个数据成员的地址是一样的,是互斥赋值的状态,同时只会有一个数据成员是具有有效值的。
模板 template
迭代器
面向对象
虚函数和纯虚函数 virtual关键字
虚函数可以在子类中不定义,而纯虚函数则必须在子类中进行定义多态
- 静态多态,多由函数的重载来实现,自动识别不同类型进行调用
- 动态多态,通过基类指针以及虚函数来实现
构造函数与析构函数
- 构造函数不能声明为虚函数
- 析构函数如果是位于父类中,则应该定义为虚函数,负责可能导致子类对象无法释放,引起内存泄漏。
常函数,在函数后使用const修饰,只能访问数据成员,而不能修改,常量对象也只能调用常函数。
面向对象三大基本特征
- 封装
- 继承
- 多态
内联函数、宏定义的定义,区别以及各自的优缺点和适用场景
C++内存
C++程序的内存分布
- 数据段
存放程序中已初始化的全局变量和静态变量 - 代码段
存储程序执行代码,只读,还包括一些只读的常数变量 - BSS段
存放程序中未初始化的全局变量和静态变量 - 堆区
动态申请内存,堆从低地址向高地址增长,存放对象 - 栈区
存储局部变量、函数参数值。从高地址向低地址增长,是一块连续的空间。 - 共享区
- 数据段
内存对齐
- 将每个数据存放在它的大小的倍数中的地址上,为了避免cpu可能需要两个周期才能取出完整的值的情况,增加读取效率。
- 用于struct/class/union三种数据类型中
- 对齐原则
C++新特性
auto关键字
auto a=10;自动类型推导 在变量声明时必须初始化decltype关键字
decltype(1+2.0) b=2.2; 根据1+2.0的表达式结果的值推导b的类型,在声明时可以不用初始化智能指针
- shared_ptr
- unique_ptr
- weak_ptr
- auto_ptr
Lambda函数
- 格式:
[外部变量访问方式说明符] (参数) mutable noexcept/throw() -> 返回值类型 {函数体;};
- 格式:
app客户端开发
组件
activity
生命周期
- onCreate() 在系统首次创建activity时触发,已创建状态
- onStart() 会马上调用start,进入“已开始”状态, onRestart()
使activity对用户可见
与activity相关的生命周期相关联的组件会收到“ON_START”事件 - onResume() 系统调用resume,activity会进入已恢复状态,
activity来到前台,进入应用与用户交互的状态,直到中断事件(例如其他activity的调用等)发生 - onPause() 进入“已暂停”状态
activity不再处于前台,尽管多窗口模式下该activity可能仍然可见 - onStop() 进入“已停止”状态
不再对用户可见,一些在后台运行的任务,应该在这里和onstart中进行启动和关闭配置- 从“已停止”状态恢复时,系统会先调用onRestart,再调用onStart,再调用onResume
- onDestroy() 进入“已销毁”状态
activity之间的导航
startActivity 不期望获取返回值
startActivityForResult 期望获取返回值
Intent对象 指定目标activity以及操作类型,并且可以携带少量数据
- 该对象同时还可以用于启动系统中其他应用的activity来协助完成工作,例如发送邮件等功能
- 需要通过设置想要执行的操作来实现
生命周期回调的顺序已有明确定义,特别是当两个 Activity 在同一个进程(应用)中,并且其中一个要启动另一个时。以下是 Activity A 启动 Activity B 时的操作发生顺序:
- Activity A 的 onPause() 方法执行。
- Activity B 的 onCreate()、onStart() 和 onResume() 方法依次执行(Activity B 现在具有用户焦点)。
- 然后,如果 Activity A 在屏幕上不再显示,其 onStop() 方法执行。
activity的调用关系
- 系统一般会以调用栈的方式来存储activity信息,如果用户按返回键,则会销毁栈顶的activity,让下一个activity出现在前台
- 如非必要,一般情况下不要打破这种默认的栈调用模式
Service
- 生命周期
- onCreate()
- onStartCommand() 只有该生命周期回调函数会在一个周期内被执行多次
- onBind() 与服务绑定,可以通过IBind接口实例获取服务的运行状态
- onUnbind()
- onDestroy()
fragment
viewModel
LiveData
优点
- 具有生命周期感知能力
- 遵循观察者模式
liveData中数据变化时会自动通知观察者完成界面的更新 - 不会发生内存泄露
观察者会绑定lifecycle对象,在生命周期结束后自我清理 - 不会因为Activity停止而导致崩溃
非活跃状态的观察者不会收到任何LiveData事件 - 不需要手动处理生命周期
- 数据始终保持最新状态
非活跃状态转为活跃状态时会自动接收LiveData中的最新数据并渲染显示 - 当配置修改时会自动完成部分工作
例如设备旋转导致的配置更改 - 资源共享
可以全局共享一个LiveData数据来共享某些资源
一般在activity/fragment中的onCreate方法中创建绑定
Redis
- NoSql键值、内存数据库
- 可持久化
基本数据结构
5种核心的数据类型,分别是字符串、哈希、列表(List)、集合(set,元素上限为2^32-1)、有序集合(zset)
Bitmap、HyperLogLog、Geo类型,这些类型都是基于上述核心数据类型实现
Streams数据类型,它是一个功能强大的、支持多播的、可持久化的消息队列
底层存储数据结构
- 全局Hash表+链表
- rehash机制优化、导致冲突情况减少
- 有序集合(zset)
- 使用跳表存储,有序链表用于存储数据,然后通过建立冗余的索引节点来提高查找速度(查找速度接近logn)
基本操作命令
watch
- 通过乐观锁的机制对一些key值进行监控,如果这些key更改过,则拒绝执行客户端提交的事务,并返回空值
List操作命令
- lpush/rpush
- lrange
- lindex
- lpop/rpop
- blpop/brpop 从左侧或者右侧弹出数据,若为空则阻塞
sexnx 加锁命令
- 一般现在使用sex…nx…替代
- 使用场景
set key value nx key seconds #设置锁key值为value,过期时间为second# 通过观测这个key值来知道此时有没有锁上,从而达到实现分布式锁的目的del key #删除锁
- 存在问题是,这个锁一般会过期,如果它在对应任务执行完之前就已经过期自动释放了,那么在它执行完时进行释放锁的时候,这个key可能已经是别的任务的锁了,可能就会将别的锁释放掉
- 解决方式是采用标识来判断是否是自己线程的锁,避免将别的线程的锁释放掉(使用原子性的lua脚本来实现)
- 存在问题是,这个锁一般会过期,如果它在对应任务执行完之前就已经过期自动释放了,那么在它执行完时进行释放锁的时候,这个key可能已经是别的任务的锁了,可能就会将别的锁释放掉
randomkey 随机返回一个未过期的key
- 这里如果次数库中未过期的key较多,那么当它先拿到一个key时然后判断是否过期,如果过期就继续随机拿到key,此时就可能耗时较长,会阻塞进程、影响性能
- 5.0版本后限制了在从节点上的随机次数,一定次数后退出,不造成死循环
Redis机制
过期key的删除
- 如果使用set命令未指定过期时间,则默认该key的过期时间为永不过期,如果是修改value值时也需要进行过期时间的写入
- 删除策略:
- 惰性删除:下次访问key的时候判断过期时间,然后删除(内存利用率低)
- 定时删除:固定每一段时间(100ms)删除部分过期的key,不会一次性将全部的删完(缓慢的删除)
Redis集群架构
主从架构
master
|_slave 从节点为主节点备份,主节点的数据修改会同步到从节点中,如果主节点宕机,则会做主从切换
|_slave哨兵架构
client
|
|
哨兵集群 ---------主从结构
|——哨兵节点
|——哨兵节点客户端通过哨兵集群去访问redis的主从架构,哨兵集群中监听着每台redis节点的状态,如果master节点故障,会由哨兵集群进行主从切换
集群架构
- 过大的缓存文件以及并发量可以分配到每个主从节点上存储
- 去中心化
- 分片(集群中的每个小的主从架构就是一个小的分片、或者说槽位,可以将不同的请求划分到不同的片上)
杂知识
缓存的穿透、击穿、雪崩、污染
- 穿透:缓存访问失效,然后去访问数据库,但是数据库中也没有该结果,从而导致缓存也不会缓存该结果,最终导致这类请求都直接访问了数据库,使得数据库压力增大
- 解决方式:
- 设置缓存中缓存空的结果
- 布隆过滤器
- 解决方式:
- 击穿:缓存中某个key刚好失效了,正在从数据库中获取新的值,但在数据库返回信息前这个key是已经失效状态,所以这段时间内如果恰好有大量请求访问,那么由于缓存失效会直接访问数据库,知道数据库中返回第一次请求的结果并在缓存中更新
- 解决方式:
- 分布式锁,当同一请求中第一个失效了访问数据库时暂时让其他请求阻塞
- 热点数据设置永不过期
- 解决方式:
- 雪崩:一般是在缓存中有大量的数据同时过期,导致大的数据量直接请求数据库,使数据库宕机
- 解决方式:
- key避免设置在同一时间过期,尽量附加随机值
- 热点数据设置永不过期
- 分布式缓存,避免同一机器负载过高
- 多级缓存,当第一级缓存击穿时从第二级缓存获取信息,同时去异步的更新所有缓存,(第二级缓存应设置为不过期或者是过期时间较长)
- 解决方式:
- 污染:许多实际不会被再次调用的数据依旧保留在缓存中,导师缓存满之后的访问效率降低
- 解决方式;内存淘汰算法
- volatile-ttl:使用过期时间,但能应对的场景较少
- LRU:记录最近访问的时间,每次取时间最早的淘汰
- 一次性批量访问大量数据的数据可能有时淘汰较慢
- LFU:记录最近访问的次数以及时间,先选次数最少的淘汰,次数相同则选时间最早的淘汰
- 这里涉及到一个记录次数的非线性算法,使得记录次数的标记不会太快超过上限(255,8bit)
- LRU和LFU都分为volatile和allkeys两种,
- volatile是指只针对设置了过期时间的key进行处理
- allkeys是指对所有的key都进行处理,这种情况就可能会删除掉一些未设置过期时间的key
- 解决方式;内存淘汰算法
单线程、多线程
6.0之前,所有的网络IO和键值读写都是一个线程
6.0之后,键值读写是一个线程、但是网络IO这块引入了多线程,但关键的键值读写还是单线程顺序执行,所以依旧是并发线程安全的
单线程却快的原因
- 基于内存操作
- IO多路复用机制提高IO效率
- 高效的数据结构
- 无线程切换开销
持久化
RDB持久化
- 快照式持久化,将redis库中信息直接保存到二进制文件中
- 使用save格式在配置文件中配置持久化策略,当满足任一策略时开始进行写入磁盘文件
- 持久化时如果原数据发生修改,会复制一份,会将这个复制的数据写入磁盘,而不会将新修改后的数据写入
- 命令,主动执行
- save 同步
- bgsave 异步 ,不阻塞、可能会有额外内存(一般是这种方式)
- 问题:
未满足save策略时写入的数据可能会丢失
AOF持久化
- 即时性持久化,将redis的修改命令保存到文件中,备份时再逐一执行命令恢复
- 配置策略
- always:每次有修改命令都写入文件
- everyseconds:每s一次,每次最多只会丢失1s数据
- no:不主动执行,由操作系统调用,可能在负载不高的时候来写入磁盘,更快,安全性不可控
- AOF文件重写
- 定期根据内存最新数据来重写aof文件,去掉无用的命令,降低文件大小,提高恢复速度
- 配置策略
- min-size:控制重写最小大小
- percentage:控制文件增加多少再次重写
- bgrewriteaof命令:手动重写
- 恢复的时候动作叫做aof文件日志重放
RDB-AOF混合持久化
- 每次AOF重写之前,先将此时的内存数据保存为rdb二进制数据以及增量的AOF命令一起写入AOF文件中,平时按照AOF持久化的方式将文件命令写入aof文件中
- 恢复时先写入rdb二进制数据,再对aof日志进行重放
- 可以做到最多只丢失1s内的数据(如果AOF持久化使用的是everysec的策略)
缓存与数据库的双写一致性
- 一般的最好策略是先更新数据库、然后更新或者删除缓存
其他功能
- 布隆过滤器
- 使用多个hash函数来计算hash值,来综合判断该key是否已存在缓存中
- 可以降低一个hash导致的偶发性问题
- 多个hash的值对应的标识都证明这个key值存在,则大概率存在
- 多个hash的值对应的标识中有一个标识这个key值不存在,则一定不存在
- 使用多个hash函数来计算hash值,来综合判断该key是否已存在缓存中
消息队列
基础知识
- 幂等性
- 一条消息被消费端执行多次仍然只会产生一条记录或者是结果仍然相同,可以解决消息被多次重复消费的问题
Kafka
RabbitMQ
RocketMQ
java、c++、android开发面经纪要相关推荐
- 腾讯海外游戏直播Android开发面经
腾讯海外游戏直播Android开发面经 1.自我介绍,balabala 2.编程题:合并K个链表(顺序合并.分治.优先队列) 3.JVM内存模型,GC过程 4.Android内存回收有哪几种方式 5. ...
- android java设计模式,Android开发之Java设计模式基础篇
今日我们就Android开发中的一些设计模式做一些 根底性的 主宰,本次就Android项 目标架构设计 有关内容做 综合: 1. 静态工厂 步骤 静态工厂 步骤 可以算是工厂 步骤加单例模式的整合在 ...
- 71道Android开发面试题
注:本文来自"安卓巴士" Android面试题 1. 下列哪些语句关于内存回收的说明是正确的? (b ) A. 程序员必须创建一个线程来释放内存 B. 内存回收程 ...
- android开发面试题!微信小程序趋势及前景,社招面试心得
没有稳定的工作,只有稳定的能力. 又到了万物复苏的季节,在程序猿这个行当里,作为 Android 开发出生的,在经历了八年的脱发生涯后,有了越来越多的想法和感触 趋势 随着各类移动跨平台的兴起,在 R ...
- Android开发面试题 71道经典题目
注:本文来自"安卓巴士" Android面试题 1. 下列哪些语句关于内存回收的说明是正确的? (b ) A. 程序员必须创建一个线程来释放内存 B. 内存回收程 ...
- 百度Android开发面试题
此文转载,希望朋友有好的面试题 发来研究一下-- 1. Android dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念 DVM指dalivk的虚拟机.每一个Android应用程序都在 ...
- 优秀Android程序员必知必会的网络基础,Android开发面试题及答案
前言 大家应该看过不少人分享的面试成功的面经,是不是觉得自己"说不定也可以"呢? 这里重提一个理论:幸存者偏差.当取得资讯的渠道,仅来自于幸存者时(因为死人不会说话),此资讯可能会 ...
- android开发面试题!360°深入了解Flutter,面试总结
何为成长?成长是指自我提升,一方面是本身的个人能力,另一方面是社会对你的认可度.最终,程序员的职位和薪水都能在成长中得以体现. 很多人对成长有误解,在他们眼中,随着工作年限的提高,成长是理所当然的事情 ...
- Andorid Studio 制作欢乐写数字(Timer启动+帧动画,Android开发面经分享
什么是Kotlin? Kotlin,如前面所说,它是JetBrains开发的基于JVM的语言.JetBrains因为创造了一个强大的Java开发IDE被大家所熟知.Android Studio,官方的 ...
最新文章
- YOLO系列:YOLO v1深度解析
- Linux--DHCP
- 如何修改maven默认jdk配置
- POJ 3254 状态压缩DP
- 电子信息工程班徽设计_蜻蜓AI说专业:与5G时代息息相关的电子信息工程专业怎么样?...
- dotnet core 应用是如何跑起来的 通过自己写一个 dotnet host 理解运行过程
- python写标准api_用python写api
- Android学习笔记(四):android画图之paint之setXfermode
- QQ空间迁移_【山特C3KS_连接ESXI虚拟机】
- AI 考古比胡八一更高效
- 《word2vec Parameter Learning Explained》论文学习笔记
- WebStorm、Idea使用git账户密码重置
- python导入可用软件包
- Shell脚本之多重循环
- java没有舞台_不会偷懒的程序员不是好程序员!
- 来自作业本的写给90后
- 解决webpack中报错的问题
- latex安装教程(texlive+texstudio)并添加IEEE模板
- 看《越狱》体会项目管理-知识管理-性格分析
- java 微信小程序 语音识别成文字 音频格式转换 silk pcm wav