系列文章目录
常规后端面试准备
设计/架构
设计模式(常用的5个,S.O.L.I.D.)
- Single Responsibility(单一职责)
- Open-Close(开放封闭)— 拓展性开放,更改性封闭(如:vector, 泛型)
- Liskov Substitution(替换原则)— 子类应该可以替换父类出现在父类可以出现的地方
- Interface Segregation(接口分离)— 比起一个大的interface,应该分离成多个小的
- Dependency Inversion(依赖原则)— 上层模块不应该依赖于下层模块的具体实现,而应该依赖于他的抽象,如manager class包含SDE,SDM等多个成员就属于依赖具体实现,而应该含有一个
List<Employee>
,抽象下层对象
面向对象有哪些特征(3大特性)
- 封装 — 封装成类
- 继承 — 不用重新编码的前提下对现有类进行功能拓展
- 多态 — 允许将子类类型的指针赋值给父类类型的指针,虚函数(子类覆盖父类同名函数)
负载均衡算法
- 轮询法
- 随机法
- 源地址哈希法(同一IP总会被映射到同一个主机)
- 加权轮询法(根据不同服务器的性能进行加权)
- 加权随机法
- 最小连接数法
微服务
由“单体式”结构演变升级而来的一种新的结构,升级系统架构目的(基本所有架构问题都可以套这个):满足系统可靠性、并发量以及快速开发的需求。
- 可靠性
- 单体式所有服务严重耦合在一起,一旦有一个服务出错,整个系统崩溃,而且单体式很难增加冗余;
- 微服务,各个服务组件相互独立,可以方便的增加冗余,并且单个组件出错不影响整个系统;同时我们还可以对每个组件配置独立的防火墙等安全措施
- 并发量
- 单体式需要将整个系统部署到服务器上面,可能会为了满足某个瓶颈服务的并发量要求,造成过多盈余;
- 微服务可以以组件为单位进行部署,可以方便的进行扩容
- 快速开发
- 单体式所有开发人员一起开发同一款软件,分工/界线不清,很容易造成无人维护;同时可能会在不同模块有过多代码重复(如:移动端和网页端代码,都包含了支付模块)
- 微服务,每个开发人员明确开发目标,负责从头到尾的开发,部署,维护一系列工作
微服务缺点:
- 组件化,测试困难,可以单独测试每一个组件,但是组件间integration test困难;
- 难以定位出错点,传统单体式整个系统在一起,可以通过查看日志等方式快速定位bug,但是微服务+分布式会使得定位错位难度增加
平衡二叉树 (AVL vs Red-Black)
- Balanced BST
- AVL — 严格平衡(两个子节点间高度相差不超过1)
- 常规的递归插入/删除操作,插入/删除完后,更新每个节点的height,一旦发现unbalance,利用旋转restore
- Red-black(四个不变量)— 非严格平衡,相比于AVL,旋转较少,插入删除效率相对较高
- 1)每个节点要么是红色要么是黑色
- 2)根节点一定是黑色的
- 3)如果一个节点是红色的,那么子节点一定是黑色的(没有连续两个红色节点)
- 4)从根节点到NULL的每一天路径上拥有相同数量的黑色结点
- 新插入的一定是红色节点,如果一个节点两个孩子均为红色,则将他变为红色,孩子变为黑色,如果违反了规则3则旋转
- AVL — 严格平衡(两个子节点间高度相差不超过1)
Java相关内容
我也不知道为啥,但反正就面试中经常会问Java…
HashMap,HashTable,ConcurrentHashMap,1.7-1.8的变化
- HashMap(1.7)
- 底层:数组+链表(一个数组的Entry,Entry是一个链表节点结构)
- 插入:头插法(找到对应的Entry list,然后新的Entry指向原来的链表)
- threashold = capacity * loadFactor
- 位置 = hash % length(hash值取余数组长度),取余可以用&来替换
- resize之后会调用transfer方法拷贝数据,注意此时会重新计算每一个元素的位置(扩容的目的就是为了尽可能平均元素的分配),所以此时多线程可能会造成循环链表
- HashMap(1.8)
- HashMap 1.7 —> 1.8的变化
- 默认初始容量(16 — 0)
- 引入了红黑树(长度超过8时,转换为红黑树)
- 插入方法(头插法 — 尾插法)
- 优化hash算法
- resize计算索引位置算法优化
- 先插入后扩容
- Entry改为Node(内容还是一样的)
- HashMap 和 HashTable的区别
- HashTable是线程安全的(每个函数都有synchronized修饰),HashMap不是
- 性能HashMap > HashTable(因为要加锁)
- 初始容量:HashMap(16),HashTable(11)
- 扩容机制:HashMap翻倍,HashTable +1
- ConcurrentHashMap(1.7)
- 将整个HashMap分区,每个segment单独上锁
- 每个segment类似于一个小的HashMap,独自处理扩容等
- ConcurrentHashMap(1.8)
- 减小锁的粒度,不再有segment的概念,直接锁每个桶的头结点(first Node),并使用CAS来更新
ConcurrentHashMap/HashMap的扩容机制
LinkedHashMap 底层数据结构?使用场景?
-
除了有一个HashMap一模一样的散列链表之外,还单独维护了一个双向链表
-
利用双向链表,保证了迭代输出时候是有序的
-
LinkedHashMap的Entry继承自HashMap的Entry,并增加了before和after两个节点(原来还有一个next节点)
-
private static class Entry<K,V> extends HashMap.Entry<K,V> { // These fields comprise the doubly linked list used for iteration. Entry<K,V> before, after; Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { super(hash, key, value, next); } }
-
使用场景:LRU(重写removeEldestEntry函数)
LinkedHashMap和TreeMap
- TreeMap实现的是sortMap接口,实现了根据键值的排序;
- LinkedHashMap本质上还是一个HashMap,只是增加了两个指针,可以根据插入/访问顺序排序
其他
hash冲突怎么办
- chaining — 链表
- open addressing — 开放寻址,地址+1,+2…
静态编译,动态编译
- 区别在于动态链接库有没有被编译到目标可执行文件里面
- 静态 — 有,不依赖于环境里的动态库(因为全编译到了可执行文件内)
- 动态 — 无,编译更快,可执行文件体积更小,但是需要运行环境里面有对应的动态库
const int等
int*
- pointer to intint const *
- pointer to const intint * const
- const pointer to intint const * const
- const pointer to const int
一个小技巧,从左往右看(运算顺序)+const T
和T const
是等价的
int const *
—>(int const) *
—>(const int) *
,指向常量int的指针int * const
—>(int *) const
—>const (int *)
,一个常量指针,指向intint const * const
—>((int const) *) const
—>const (const int) *)
,一个常量指针指向一个常量int