第一次面试给了OPPO
效果不是很满意,感觉自己开始上来状态就不行,紧张的自我介绍都结结巴巴,太需要克服这点了,然后后面面试官问的题偏数据库较多一点,但恰巧这边我只是会增删改查,毫无理论可言,虽说面试官问的知识点我都看过之前,有些没答上来,有些答的时候还是答的不够全面,条理也不够清晰,另外自己总是带一些,好像是。。。。,可能是这个。。。的字眼,自我感觉非常的不好,哎,不开心。
Mysql常用的存储引擎
MyISAM是MySQL存储引擎之一,不支持数据库事务、行级锁、和外键。因此在INSERT
或UPDATE
数据即写操作时需要锁定整个表,效率会很低
InnoDB为MySQL提供了事务支持、回滚、崩溃修复能力、多版本并发控制、事务安全的操作。
InnoDB有什么特性
InnoDB引擎特点
1.支持事务,支持4个事务隔离级别,支持多版本读。
2.行级锁定(更新时一般是锁定当前行),通过索引实现,全表扫描仍然会是表锁,注意间隙锁的影响。
3.读写阻塞与事务隔离级别相关。
4.具有非常高效的缓存特性:能缓存索引,也能缓存数据。
5.整个表和主键以Cluster方式存储,组成一个平衡树。
6.所有Secondary Index都会保存主键信息。
7.支持分区,表空间,类似oracle数据库。

8.支持外键约束,5.5之前不支持全文索引,5.5之后支持外键索引。
小结:supports transactions,row-level locking。and foreign keys
9.和Myisam引擎比,Innodb对硬件资源要求比较高。
MySQL引擎之innodb引擎应用场景及调优
Innodb引擎适用的生产场景
1、需要事务支持的业务(具有较好的事务特性)
2、行级锁定对高并发有很好的适应能力,但需要确保查询时通过索引完成。
3、数据读写及更新都较为频繁的场景,如:bbs,sns,微博,微信等。
4、数据一致性要求较高的业务,例如:充值转账,银行卡转账。
5、硬件设备内存较大,可以利用Innodb较好的缓存能力来提高内存利用率,尽可能减少磁盘IO。
B+树索引和hash索引使用的场景
B+树是一个平衡的多叉树。B+树从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动。
哈希索引采用一定的哈希算法,把键值换成新的哈希值,检索时不需要类似B+树那样从根节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置。
创建索引有什么技巧吗 (没答上来)
- 经常检索排序大表中30% 或非排序表7%的行,建议建索引;
- 为了改善多表关联,索引列用于联结;
- 列中的值比较惟一;
- 列中有许多空值,不适合建立索引;
- 经常一起使用多个字段检索记录,组合索引比单索引更有效;
- 不要索引大型字段;
- 不要索引常用的小型表,尤其假如它们有频繁的插入和删除操作;
- 不能真正使用到索引的情形:在索引列上使用函数查询,使用模式匹配LIKE,使用IN子查询;
事务的隔离级别
读未提交 读已提交 可重复读 序列化
读已提交解决脏读问题
可重复读解决不可重复读问题
序列化解决幻读问题。
MySQL
的默认隔离级别是可重复读
Oracle
的默认隔离级别是读已提交
SQL Server
的默认隔离级别是读已提交
redis有哪些基本的数据类型
Redis
支持5种数据类型:String(字符串)
、hash(哈希)
、list(列表)
、set(集合)
、zset(有序集合)
-
String
一个
key
对应一个value
String
类型是二进制安全的,因此redis
的string
可以包含任何数据,比如jpg
图片或者序列化对象String
类型的值最大能存储512MB常用命令:
get
、set
、decr
、incr
、mget
等 -
hash
hash
是一个键值对集合:是一个String
类型的field
和value
的映射表hash
特别适合用于存储对象每个
hash
可存储2^(32-1)
键值对常用命令:
hget
、hset
、hgetall
等 -
list
list
是一个简单的字符串列表、按照插入顺序排序,你可以添加一个元素到列表的头部或者尾部list
类型经常会被用于消息队列的服务、以完成多程序之间的消息交换列表最多可存储
2^(32-1)
个元素常用命令:
lpush
、rpush
、lpop
、rpop
、lrange
等 -
set
set
也是一个字符串列表,和列表不同的是,在插入和删除时会判断是否存在了该元素。集合的最大的优势在于可以进行交集并集差集操作。集合是通过hash
表实现的,因此,添加,删除,查找的复杂度都是o(1)
应用场景:
- 利用交集求共同好友。
- 利用唯一性,可统计访问网站的所有独立
IP
。 - 好友推荐的时候根据
tag
求交集,大于某个thresold
就可以推荐
集合最多可存储
2^(32-1)
个元素常用命令:
sadd
、spop
、smembers
、sunion
等 -
zset
和
set
一样是String
类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double
类型的分数。redis
正是通过分数来为集合中的成员进行从小到大的排序。zset
的成员是唯一的,但分数却可以重复。应用场景:
- 例如存储全班同学的成绩,其集合
value
可以是同学的学号、而score
就可以是成绩 - 排行榜应用,根据得分列出
topN
的用户等。
常用命令:
zadd
、zrange
、zrem
、zcard
等 - 例如存储全班同学的成绩,其集合
Redis的持久化策略
Redis
的持久化策略有两种:
-
RDB
:快照形式是直接把内存中的数据保存到一个dump
文件中,定时保存。 -
AOF
:把所有的对Redis
的服务器进行修改的命令都存在一个文件里,命令的集合 -
RDB的优缺点:
-
优点:
- 对性能的影响最小,
Redis
在保存RDB
快照时会fork
出子进程进行,几乎不影响Redis
处理客服端请求的效率。 - 每次快照都会生成一个完整的数据快照文件,所以**可以辅助其他手段保存多个时间点的快照(**例如把每天0点的快照备份至其他存储媒介中)
- 作为非常可靠的灾难恢复手段。使用
RDB
文件进行数据恢复要比使用AOF
要快很多。
- 对性能的影响最小,
-
缺点:
- 快照是定期生成的,所以在
Redis crash
时或多或少会丢失一部分数据 - 如果数据集非常大且
cpu
不够强(比如单核cpu
),Redis
在fork
子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的性能。
- 快照是定期生成的,所以在
-
AOF的原理
AOF提供了三种
fsync
配置,always/everysec/no
,通过配置项[appendfsync]
指定:appendfsync no
:不进行fsync
,将flush
文件的时机交给OS
决定,速度最快appendfsync always
:每写入一条日志就进行一次fsync
操作,数据安全性最高,但速度最慢appendfsync everysec
:折中的做法,交由后台线程每秒fsync
一次随着
AOF
不断地记录写操作日志,因为所有的操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF
文件过大,也会让数据恢复的时间过长。不过Redis
提供了AOF rewrite
功能,可以重写AOF
文件,只保留能够把数据恢复到最新状态的最小写操作集。AOF rewrite
可以通过BGREWRITEAOF
命令触发,也可以配置Redis
定期自动进行:auto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb
上面两行配置的含义是,
Redis
在每次AOF rewrite
时,会记录完成rewrite
后的AOF
日志大小,当AOF
日志大小在该基础上增长了100%
后,自动进行AOF rewrite
。同时如果增长的大小没有达到64mb
,则不会进行 -
AOF的优缺点
-
优点:
- 最安全,在启用
appendfsync always
时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec
也至多只会丢失1秒的数据。AOF
文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof
工具轻松修复。AOF
文件易读,可修改,在进行了某些错误的数据清除操作后,只要AOF
文件没有rewrite
,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
- 最安全,在启用
-
缺点:
-
AOF
文件通常比RDB
文件更大性能消耗比RDB
高数据恢复速度比RDB慢
Redis
的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:AOF + fsync always
的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync
,会对Redis
的性能有比较明显的影响AOF + fsync every second
是比较好的折中方案不过大多数应用场景下,建议至少开启
RDB
方式的数据持久化。Redis
对于数据备份是非常友好的, 因为你可以在服务器运行的时候对RDB
文件进行复制:RDB
文件一旦被创建, 就不会进行任何修改。 当服务器要创建一个新的RDB
文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才使用rename(2)
原子地用临时文件替换原来的RDB
文件。
-
redis的过期策略
1.设置过期时间
- expire key time—这是最常用的方式
- setex(String key,int seconds,String value) —字符串独有的方式
注意:
- 除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置时间
- 如果没有设置时间,那缓存就是永不过期
- 如果设置了过期时间,之后又想让缓存永不过期,使用persist key
2.三种过期策略
-
定时删除
- 含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
- 优点:保证内存被尽快释放
- 缺点:
- 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
- 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
- 没人用
-
惰性删除
- 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
- 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
- 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
-
定期删除
- 含义:每隔一段时间执行一次删除过期key操作
- 优点:
- 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理"定时删除"的缺点
- 定期删除过期key–处理"惰性删除"的缺点
- 缺点
- 在内存友好方面,不如"定时删除"
- 在CPU时间友好方面,不如"惰性删除"
- 难点
- 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)
3.Redis采用的过期策略
惰性删除+定期删除
- 惰性删除流程
- 在进行get或setnx(当key不存在时设置value)等操作时,先检查key是否过期
- 若过期,删除key,然后执行相应操作
- 若没过期,则直接执行相应操作
- 定期删除流程(简而言之,就是对指定个数的数据库的每一个库随机删除小于等于指定个数的过期key)
- 遍历每个数据库(redis.conf中配置的
database
数量,默认为16)- 检查当前库中的指定个数个key(默认每个库检查20个key,相当于循环下面的操作20次)
- 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
- 随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
- 判断定期删除操作是否已经到达时长,若已经到达,直接退出定期删除。
- 检查当前库中的指定个数个key(默认每个库检查20个key,相当于循环下面的操作20次)
- 遍历每个数据库(redis.conf中配置的
- 注意:
- 对于定期删除,在程序中有一个全局变量current_db来记录下一个将要遍历的库,假设有16个库,我们这一次定期删除遍历了10个,那此时的current_db就是11,下一次定期删除就从第11个库开始遍历,假设current_db等于15了,那么之后遍历就再从0号库开始(此时current_db==0)
4.RDB对过期key的处理
过期key对RDB没有任何影响
- 从内存数据库持久化数据到RDB文件
- 持久化key之前,会检查是否过期,过期的key不进入RDB文件
- 从RDB文件恢复数据到内存数据库
- 数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)
5.AOF对过期key的处理
过期key对AOF没有任何影响
- 从内存数据库持久化数据到AOF文件:
- 当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令)
- 当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉)
- AOF重写
- 重写时,会先判断key是否过期,已过期的key不会重写到aof文件
StringBuilder和StringBuffer
主要有线程安全
、缓冲区
、性能
三方面的区别
-
线程安全方面
StringBuffer
:线程安全的。StringBuilder
:线程不安全。因为StringBuffer
的所有公开方法都是synchronized
修饰的,而StringBuilder
并没有sychronized
修饰。
-
缓冲区方面
StringBuffer
每次获取toString
都会直接使用缓存区的toStringCache
值来构造一个字符串。StringBuilder
则每次都需要复制一次字符数组,再构造一次字符串所以、
StringBuffer
对缓冲区进行了优化。 -
性能方面
由于
StringBuilder
没有加锁,所以性能要优于StringBuffer
-
总结:
StringBuffer
适用于多线程操作同一个StringBuffer
的场景,StringBuilder
在单线程场景下更为合适
LinkedList和ArrayList的区别
ArrayList底层是基于数组实现的,查询效率较高,增删改查的效率较低。
LinkedList底层是基于链表实现的,查询效率较低,增删速度较快
两者都不是线程安全的。
concurrentHashMap
因为在多线程环境下,使用HashMap进行put操作可能会引起死循环,导致cpu利用率接近100%,所以在并发情况下不能使用HashMap
因此针对这一问题:出现了Hashtable 和concurrentHashMap
-
hashtable
Hashtable使用synchronized来保证线程安全,但在线程竞争激烈的情况下,hashtable的效率非常低下。因为在同一时刻只能有一个线程占有资源,其他线程都处于等待状态。
-
concurrentHashMap
concurrentHashMap采用分段锁的思想,将数据分成一段一段存储,然后给每一段数据配上一把锁,当一个线程占用锁访问其中一段数据时,其他线程也可以访问别的段数据。
-
总结
Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。
ConcurrentHashMap 是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分数据。
应该根据具体的应用场景选择合适的HashMap。
sychronized和lock有什么区别
区别:
-
底层实现:sychronized是Java中的关键字,是由JVM来维护的,是JVM层面的锁 lock是一个类,是java代码层面的锁底层实现的
-
使用方式不同:sychronized在使用时候,获取锁和释放锁,都是由系统维护的。而使用lock的需要手动获取锁,手动释放锁。
-
异常的处理方式:sync在线程发生异常时会自动释放锁,不会发生异常死锁,Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。
-
等待是否可中断:sychronized是不可中断的,除非抛出异常或者正常运行完成,Lock是可以中断的,中断方式有:
- 调用设置超时方法
tryLock(long timeout,timeUnit unit)
- 调用
lockInterruptibly()
放到代码块中,然后调用interrupt()
方法可以中断
- 调用设置超时方法
-
加锁的时候是否可以公平:sync是非公平锁,lock既可以公平也可以不公平
-
锁可以绑定多个条件:
sync:要么随机唤醒一个线程,要么是唤醒所有等待的线程
lock:用来实现分组唤醒所需要唤醒的线程,可以精确的唤醒线程。
-
从锁的实现机制来看:
sync采用的是独占锁,也就是悲观锁的机制
lock采用的是乐观锁,所谓乐观锁就是每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
volatile关键字
volatile,用来将变量的更新操作通知到其他线程,当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将改变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。
而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
当一个变量定义为volatile之后,将具备两种特性:
1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,
2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
volatile 性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
线程池处理任务的流程
线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源,在调用execute()添加一个任务时,线程池会按照以下流程执行任务:
(1): 如果正在运行的线程数量小于corePoolSize
(核心线程数量),线程池就会立刻创建线程并执行该线程任务
(2):如果正在运行的线程数量大于等于corePoolSize
,该任务就将被放入阻塞队列中。
(3):在阻塞队列已满且正在运行的线程数量少于maximumPoolSize
时,线程池会创建非核心线程立刻执行该线程任务
(4):在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize
时,线程池将拒绝执行该线程任务并抛出RejectExecutionException
异常
(5):在线程执行任务完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。
(6):在线程处于空闲状态的时间超过keepAliveTime
时间时,正在运行的线程数量超过corePoolSize
,该线程将会被认定为空闲线程并停止。因此在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize
大小。
TCP三次握手的流程
第一次握手:主机A
发送位码SYN=1
;随机产生seq number=1234567
的数据包到服务器,主机B
有SYN=1
知道,A
要求建立联机;
第二次握手:主机B
收到请求后要确认联机信息,向A发送ack number=(主机Aseq+1)
,SYN=1
,ACK=1
,随机产生seq=7654321
第三次握手:主机握手后检查ack number是否正确,即第一次发送的seq number+1,以及位码ACK是否为1,若正确,主机A会再发送ack number=(主机B的seq+1)
ACK=1
;主机B收到后的确认seq
值与ACK=1
,则连接建立成功。
OSI七层模型
OSI七层模型:
- 物理层:
IEEE802.1A
IEEE802.2到IEEE 802.11
- 数据链路层:
FDDI
、Ethernet
Arpanet
PDN
SLIP
PPP
- 网络层:
IP
、ICMP
、ARP
、RARP
、AKP
、UUCP
- 传输层:
TCP
,UDP
- 会话层:
SMTP
、DNS
- 表示层:
SNMP
,Telnet
、Rlogin
、Gopher
- 应用层:
HTTP
、FTP
、SMTP
、WAIS