面试八股文
java基础
JDK
我们常使用的是originJDK,更加稳定,比openjdk差不多,但是多了一些错误修复
字节码
jvm针对于所有操作系统、平台都进行了定制,无论在哪个平台上都可以生成固定的class文件供jvm使用,他是java跨平台的基础。
由于字节码并不针对于某种机器或平台,所以他不需要重新编译。java通过字节码方式在一定程度称解决了传统解释型语言效率低的问题,同时又保留了解释型语言的可移植性(java是一次编译多次解释)
基本类型 | 位数 | 字节 | 默认值 |
---|---|---|---|
int | 32 | 4 | 0 |
short | 16 | 2 | 0 |
long | 64 | 8 | 0L |
byte | 8 | 1 | 0 |
char | 16 | 2 | ‘u0000’ |
float | 32 | 4 | 0f |
double | 64 | 8 | 0d |
boolean | 1 | false |
关键字
- final修饰变量:不可修改,必须初始化,一般被称为常量
- final修饰方法:不可被重写,只能被使用
- final修饰类:无法被继承。
static
- static环境中不可访问非static变量,因为这些变量很有可能没有被初始化
集合
-
list继承于collection,有序可重复
- ArrayList,动态数组,尾部插入快,指定位置插入和删除慢,实现了RamdomAccess,支持随机访问,动态扩容扩0.5,容量浪费在预留容量上
- LinkedList,双向链表,插入删除不受位置影响,不支持随机访问,每个元素都包含上一个元素节点和下一个元素节点
- Vector,ArrayList的线程安全版,动态数组,使用synchroized关键字修饰,动态扩容扩1倍
- copyonwriteArrayList,写时复制,当进行写操作时,会复制出一个新的数组,然后在新的数组上进行写操作。这样做的好处是对于读操作不收任何影响,有着高并发度,低并发写的特点,
-
Set继承于Collection
- HashSet,由HashMap实现,key唯一,value为null,无序不可重复
- LinkedHashSet,LinkedHashMap实现。有序不可重复。
- TreeSet,由TreeMap来实现的。
-
Map
- HashMap,基于哈希表实现,key唯一,value可重复
- HashTable,HashMap的线程安全版,但是多了一个Key不能为null
- LinkedHashMap,HashMap的子类,HashMap的Entry有指引下一个Entry,使用哈希表提供快速查找和删除,双向链表保证插入有序
- TreeMap,实现了SortedMap,有序,默认是key升序,是根据key的comparator来比较的
-
其他
-
Stack,继承自Vector,是一个先进先出的线程安全的列表
-
ArrayDeque,继承自Queue接口,Queue继承自Collection,非线程安全的先进先出列表
-
ConcurrentHashMap,线程安全的哈希表,它使用了分段锁和CAS操作来实现线程安全,分段锁有点类似于热点分布,将一个大的列表分为多个小的哈希表,每个小的哈希表维护自己的锁,降低了锁粒度,提高了并发性能。动态扩容,初始16,每次扩一倍,触发扩容的条件是超过了负载因子(默认0.75)*当前容量。
-
HashMap的实现:
java8中采用了数组和链表或红黑树,(引入红黑树是为了解决在hash冲突严重的时候查询效率的问题)它在内部维护了一个Node数组用来储存桶,每个桶就是一个Node链表或红黑树,存有hash值,key,value,下一个node。每个node中的链表或红黑树存储的都是hash值相同的key,但不重复,重复会被覆盖,判重的方式是通过key的equals。当链表长度超过8,且桶的长度达到64时,就会将链表转为红黑树。发生Hash冲突时,会采用链表的方式解决冲突,具体的解决方式为1.为空添加到第一个。2.不为空则遍历链表到最后一个节点,将新节点添加到尾部。
泛型
泛型就是将类型参数化,在编译时才确定具体的类型。他可以提高我们代码的重用性,类型安全等。
IO
按照流的方向:输入流和输出流
按照处理数据的单位:字节流,以字节为单位进行输入和输出,如图像,音频,视频等,InputStream,outStream;字符流,文本处理,reader,writer。
反射
运行时动态的获取类的信息以及对象的能力。我们可以再运行时获取类的属性和方法,并且创建对象,调用方法等,修改值等等
面向对象:
面向对象是一种编程思想,当解决一个问题的时候,先把事物抽成一个对象,然后给对象附一些属性和方法,让每个对象执行自己的方法,从而解决问题
对象
现实世界中,随处可见的一种事物就是对象。对象是事物存在的实体,如人类,书桌,自行车等。对象通常有两部分组成,即静态部分和动态部分,静态部分就是属性,任何对象都具备属性,就像人有高矮胖瘦年龄,性别等等。这些人所具有的一些行为,行走,微笑,说话等也就是动态属性
封装
封装是面向对象的核心思想:将类的属性和行为进行封装,其载体就是类,类通常对客户隐藏其实现细节
继承
继承主要是利用特定对象之间的共有属性,如平行四边形和四边形都有四条边,它们的共有属性就是四条边,可以把平行四边形看做四边形的延伸,复用了四边形的属性和行为,又有自己的特性。我们就可以将平行四边形看做是从四边形继承的
多态
多种形态,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的值或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义
值传递和引用传递
值传递:指的是方法调用的时候,传递的参数是按照值的拷贝传递,传递的是值得拷贝,也就是说传递后就不相关了
引用传递:指方法调用时,传递的参数是按引用传递,其传递的是引用地址,传递前和传递后的参数都指向同一个引用。
基本类型在传递的时候肯定是值传递。
引用类型在传递的时候也是值传递。在传递参数时,传递的是引用地址的拷贝,两个相同的引用地址指向同一个对象。
==和equals
重写toString,用常量比较变量,Objects.equals()两个都为空时也会为true。不能比较基本类型,在比较包装类时,要注意数据类型是否一致
限流算法
- 令牌桶:基于一个令牌桶,以固定的速度生成令牌,有最大令牌数限制,每次请求必须持有令牌才能通过。
序列化
序列化是指把java对象转化为有序字节流,便于网络中传输或者本地存储。主要的作用是为了保存对象状态的保存和重建。java对象是保存在jvm堆中的,一旦堆不存在,对象也就不存在。
序列化提供了一种解决方案,让你在jvm停机的情况下也把java对象保存下来。把java对象序列化为可存储或可传输的形式,二进制流,保存在文件中,在需要的时候在文件中读取二进制流,反序列化出对象。
什么是spring
spring是一个轻量级开发框架,针对bean生命周期进行管理的容器。它有两大特性
ioc:控制反转,spring的核心,容器创建对象,配置并管理对象的生命周期,容器通过依赖注入来组成程序的组件
aop:面向切面,不通过修改源代码的方式,在主干功能里面添加新功能
springboot
设计的目的就是为了简化新的spring应用的搭建和开发过程的。他简化了spring等众多框架所需要的大量且繁琐的配置,所以它是一个服务于框架的框架,服务范围是简化配置文件
- 内嵌tomcat
- 可直接打jar包运行
- 自动配置
- 自动依赖
- 监控
bean生命周期
- 实例化(Instantiation):容器根据Bean定义创建Bean实例。这可以通过构造函数实例化对象或者通过工厂方法创建对象。
- 属性赋值(Population):容器将Bean的属性值或依赖注入到Bean实例中。这包括通过构造函数、setter方法或注解等方式来完成属性的赋值。
- 初始化(Initialization):容器在Bean实例创建完成并赋值后,调用Bean的初始化方法。可以通过实现
InitializingBean
接口或使用注解@PostConstruct
来定义初始化方法。 - 使用(In Use):Bean实例可以被应用程序使用。在此阶段,Bean会执行它的业务逻辑。
- 销毁(Destruction):当Bean不再被使用时,容器会调用Bean的销毁方法进行清理工作。可以通过实现
DisposableBean
接口或使用注解@PreDestroy
来定义销毁方法。
线程的五种状态
1,新建状态:线程被创建,即进入新建状态
2、就绪状态:调用start方法,线程进入就绪状态。等待cup分配时间片。
3、运行状态:被cpu分配了时间片,此时线程得以真正的运行
4、阻塞状态:主动阻塞,同步阻塞,遇到资源被锁
5、死亡状态:任务执行完毕或者因异常退出了run方法,该线程结束生命周期
Mysql
innerDB:支持事物,支持外键,支持表锁行锁。
Myisam:不支持事务,不支持外键,支持表锁,支持全文索引
索引:是一种数据结构,用于加快数据库的查询速度,减少数据的扫描和排序。同时创建和维护索引需要消耗时间,当数据库进行增删是,索引也需要进行相关的维护,降低执行效率。并且占用一定的磁盘空间。
explain
id:代表每个查询的顺序,id相同,由上到下,id不同,有小到大
select_type:查询的类型,如简单查询,子查询,联合查询,临时表等
table:对应的表名
type:查询的访问方式:all>index>range>ref>eq_ref等
possible_key:可能使用到的索引
key:实际使用到的索引
rows :估算sql要查找到结果集的需要扫描的行数
extra:额外的关键信息。如Using index,使用了索引来满足查询要求。Using index condition使用了部分索引。Using temporary使用临时表。Using filesort使用文件排序,Using where表示获取到的数据需要进一步的过滤
创建索引需要注意什么
- 选择合适的列。应考虑查询效率,数据分布广泛,数据量大的列。
- 符合最佳左前缀原则
- 尽量使用联合索引来避免索引数量过多
- 索引列字段越小越好,为过长的字段添加索引不仅会导致索引存储空间的使用,同时会降低查询效率
索引失效
- 使用!=
- 使用错误的数据类型‘
- 使用运算
- 使用函数
- 使用or
- 使用like时在前面加%
事物
事物是一个不可分割的操作序列,也是数据库控制并发的基本单元,要么全都执行,要么都不执行。
原子性:最小不可分割的工作单元,要么全部成功,要么全部失败
一致性:事物在开始前和结束后,数据库的完整性约束没有被破坏。事物的执行从一个一致性到另一个一致性
隔离性:并发执行的事物之间相互隔离,相互不影响
持久性:一旦事务提交成功,对数据库的影响是永久性的,即使系统故障也能保持。
隔离级别
读未提交:可以读取到别的事物未提交的数据,也称脏读,最少使用
读已提交:只能读取到别的事物提交的数据,不会产生脏读,但是会产生幻读和不可重读
可重复读:mysql的默认隔离级别,它确保了一个事物多次读取时,会得到相同的结果。
串行:单线程执行事物
脏读:读到别的事物未提交的数据,然后别的事物回滚,读取到的数据就是脏数据。
不可重读:事物A多次读取同一数据,事物B在A读取期间对数据进行了修改并提交,导致多次读取数据不一致。
幻读:事物A多次读取同一范围的数据,事物B在A读取期间对数据进行了增删并提交,发现结果集中的数据出现了之前不存在的数据或者之前存在的数据消失了。
undo log:回滚日志,记录了事物对数据库修改的逆向操作。用于事务的回滚,保证事务的原子性和一致性。他有以下几个特点
- 事务回滚:当一个事物提交时,数据库会将此次操作记录到undo log中,而不是直接写入磁盘,在数据库发生故障时,可以通过undo log来恢复。
- 并发控制:当一个事物尝试修改某个数据时,其他并发事务需要读取同样的数据,数据库可以利用undo log保证一致性读。
redo log:记录了事物对数据库修改的正向操作。用于数据的恢复和持久性,保证事务的持久性和数据的恢复。
mvcc:读写锁的优化,有点类似于java中的邮戳锁。允许读写同时进行,通过版本控制来保证数据一致性。
当每个事物读取数据时,会根据事物开始的时间戳创建版本号,当一个事物进行写操作时,会创建一个新的版本号,这样别的事物发现数据发生了变化,可以根据时间戳来选择合适的数据版本,避免读到未提交的数据
锁
读锁:for SHARE,读读共享,会阻塞写锁。它适用于一些对于数据的准确性不那么高的场景,多个事物并发读取,防止写操作干扰。
写锁:for update,只允许同一时间一个事物修改数据。适用于库存、金额等重要数据的修改。
行锁:对一条或多条数据加读锁或写锁。根据索引来实现。如where id in(1,2) for update,如果id不是索引,则产生表锁。
间隙锁:主要解决幻读,对一个范围内的数据加写锁,防止别的数据插入或者删除。如where id<200 for update
锁优化
降低锁竞争:尽可能使用细粒度的锁,或者使用无锁结构
配合索引使用
避免死锁:如修改数据时,直接申请写锁,而不是先获取读锁,改的时候在获取写锁。
RabbitMQ
消息队列中间件,主要解决系统间的异步通信和解耦
异步:系统之间的消息通信,不需要等待对方响应,将消息放到队列中,继续处理其他的业务。异步通信可以提高系统的响应速度
削峰:避免突然大量的请求压垮服务器,将请求放在队列中,根据自身的处理速度来慢慢处理。
解耦:服务之间的发布订阅,发送方将消息放到队列中,接收方去队列中取消息。双方不需要直接通信,也不需要对方的具体实现,降低了服务间的耦合度。
持久化:RabbitMQ允许将消息标和队列记为持久化,则消息会被写入磁盘,服务器重启依然可恢复。
ack:确认机制,消费者成功消费消息后会发一个确认给MQ,MQ才会将消息从队列中删除。如果消费者在消费过程中出现了异常,MQ会将消息重新入队并且发给其他消费者。
发布者确认:发布者发布消息后,MQ成功保存后会告诉发布者保存成功,如果没有,发布者可以选择重新发布,保证消息的可靠传输
Exchange :交换机负责接收消息和路由,根据绑定关系将消息路由到绑定的队列
Queue:队列是存储消息,同时消费者也从队列中获取消息。
消息传递模式:点对点,发布订阅模式等
死信:定义死信交换机和死信队列,与正常的交换机和死信绑定,当消息成为死信时则会被传入死信队列。
成为死信:ttl过期,队列长度超限制,消费失败并达到最大重试次数,消息被拒绝且不重新入队。
redis
是一个高性能的键值对内存数据库。
String:最基本的,用于缓存,计数器,也可以实现简单的分布式锁,setIfAbsent,释放就删除或者等他自己过期。也可以添加一个唯一标识如uuid,防止误删。
Hash表:适合存储对象,商品信息,token信息等
List:有序且支持首尾增删操作,
Set:无序且不可重复,并且支持交集并集差集等操作
SortedSet:有序不可重复,适合做排名或优先级之类的
持久化
AOF:追加写,将所有写指令记录下来,之追加不改写,还有重写机制,避免文件追加越来越大。数据完整性较好,但是占磁盘空间比RDB大,运行效率低。
RDB:快照,定时将内存的数据写入磁盘,适合大规模的数据恢复,并且对数据的准确性要求不高,因为意外宕机时会丢失最后一次快照后的所有修改
建议都开
内存淘汰策略
LRU:最近最少使用,根据最近被访问的时间进行淘汰。适合热点数据比较稳定的场景。默认LRU
LFU:最不经常使用,根据被访问的频率进行淘汰,频率较低的会被优先清除。
FIFO:先进先出,按照进入缓存的先后顺序进行淘汰,适用于没什么规律的场景
Random:没有具体的规则,随机选择要淘汰的key,适用于无法预测场景
双写一致
开启spring事物,先更新数据库,后更新缓存。
要求较高不使用事物的话,那么可以对更新缓存进行异常捕获,将失败的key放到消息队列中,由消息队列完成缓存的更新。
延时双删:先删缓存,再更数据库,异步阻塞一定时间再删缓存。这样的话写操作删完缓存,读操作读取不到缓存读取数据库更新旧值到缓存,写操作更新数据库,写操作延时删除缓存旧值,下次读操作就可以读取到正确的数据。
监听mysql的binlog日志:mysql更新数据库后的操作可以在binlog中找到,然后进行对应的redis缓存更新,但是这种方法实现起来较为复杂。
击穿:某个热点key时效时,大并发集中请求直接请求数据库,可能会造成数据库瘫痪。可以将热点key设为永不过期,或者使用缓存预热,在启动时批量的从数据库中查询数据批量同步到缓存中
穿透:大量的访问内存中不存在的key,则redis没有命中,请求访问到数据库,大量的请求也会造成数据库瘫痪。这种情况一般恶意请求的比较多,可以对请求参数进行校验,过滤掉恶意请求,可以使用布隆过滤器,先校验缓存是否存在,避免无效请求。
雪崩:大量的key在同一时间内时效,导致所有请求访问到数据库从而瘫痪。设置失效时间的时候添加随机值,将失效时间分散开来
批处理
executePipelined