1.谈谈面向对象
封装、继承、多态。
封装将一类事物的属性、方法构建成一个类,使其属性私有化,行为公开化,使得代码复用性更高。
继承:子类可以继承父类的属性和方法,并拓展自己的属性方法。提高了代码的复用性。
多态:表现为父类的引用指向子类对象。以父类形式接受子类对象,可以进行传参。但是不能使用子类特有的属性和方法,除非经过强制类型转换。实现了父类和子类的解耦。
2.怎样确定的数据库设计
数据库设计 应该在软件开发流程的设计阶段进行开发,依据项目功能,设计项目所需要的数据库、数据库表,以及表之间的关系、连接用的外键。但这个项目更多是现有的前端数据库数据,我在此项目中直接进行了导入,方便后端开发。
3.怎样创建表外键
在创建数据库表时使用Foreign Key,主键是Primary Key。指定外键时,需要指定被引用表中存在的值。例如订单和客户表中,当订单被创建时,订单中的custmoerID需是用户表中存在的值,通过外键连接两个表,可查询用户信息。
CREATE TABLE order(
orderID INT PRIMARY KEY,
TIME DATE,
customerID INT
FOREING KEY(customerID) REGERENCES customers(customer_id)
)
8.如何生成的JWT秘钥?
JWT秘钥可以用于用户身份验证。token包含头部、负载、签名
生成:由服务端生成,使用JWTBuilder,传入用户id、签名算法(HS256)、过期时间,即可生成token。token包含头部、负载、签名。头部包含token类型和签名算法,负载包含用户信息(用户id,用户名等),签名(秘钥+签名算法 加密后的头部和负载)。
使用:用户登录成功后,会收到一个JWT,此后发送请求时携带JWT验证用户身份。将JWT发送给客户端或者存储在服务器端,以便后续验证用户身份时使用。服务端收到用户端token后,使用秘钥解码token,获得其中的信息。使用相同的秘钥重新计算签名,并与token签名进行比较验证签名。
什么是签名:签名是发送方使用一个密钥将头部和载荷进行加密的过程;接收方使用相同的密钥进行解密,可以验证信息是否正确。
9.那如果套餐库存为0或者套餐下架业务逻辑是什么样呢
因为做到是一个餐厅平台,所以当时没考虑库存为0的情况。因为餐厅每天都会购进蔬菜等,无法明确库存数量。实现了菜品、套餐下架业务,需在后台手动下架商品。下架菜品没有阻碍,但是包含此菜品的套餐会自动下架;用户端不会查询到已下架的套餐。
10.如何保证操作多张表的时候数据一致性。
在service层的方法上加上@transactional注解,当发生异常的时候,事务会回滚。
11.什么情况可能导致事务异常?
除了数据库连接异常中断、服务器突然宕机的情况,如果 违反了数据库唯一约束条件,或者出现了死锁时,会导致事务异常。
死锁: 当多个事务相互等待对方释放锁资源时,可能发生死锁。如果系统无法解决死锁,某些事务将被终止,导致异常。
13.反射机制是什么
是什么:反射机制是在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。这种程序运行时动态获取和调用对象的方法称为反射机制。
通过反射的暴破方法甚至可以访问类的私有属性,具体方法可以将setAccessible设置为true。
而反射的底层原理是,当进行类加载时,类加载器会加载类信息,生成Class对象。class对象封装了类的信息(包括变量Field,方法Method、构造器constructor)。
所以获取字节码的class对象是第一步。有三种方式:
通过对象实例获取类的Class对象:Class presonclass = preson.getClass();
通过类名获取类的Class对象:Class presonclass = Person.Class(); // 性能最高
通过类的路径获取class对象:Class personclass = Class.forName("com.jdbc.Driver
")
获取类的class对象后,第二步可以调用class方法使用类中的内容:
getFields(“name”); getConstructor(“name”) getMethods(“name”)
获取所有公共字段(变量) Field[] fields = personclass.getFields();
使用构造器创建实例:Person person = personclass.getConstructor().newInstance();
获取所有方法:Method[] methods = personclass.getMethods();
写一个使用反射修改字段值的代码
Person person = new Person("zzz");
Class personClass = Person.class; // 通过类名获取class对象。
Field nameField = personClass.getDeclaredField("name"); //getDeclaredField甚至能访问私有属性,getFields只能访问public
nameField.setAccessible(true); // 爆破
nameField.set(person, "qzp")
Java反射创建对象效率高还是通过new创建对象的效率高?
通过new创建对象的效率比较高。new直接调用构造函数即可实现对象实例化。而反射需要查找类、构造函数列表获取构造函数、检查访问权限等步骤。
为什么要使用反射
反射允许运行时动态获取类的信息、调用类的方法。许多框架需要在运行时动态处理类的信息,例如SpringBoot中的依赖注入
springboot中哪里用到了反射机制
反射允许Spring在运行时动态地创建类的实例,而无需在编译时知道类的具体信息。
依赖注入: 使用@Autowired注解时,Spring容器会利用反射机制找到相应的bean,通过反射机制将bean注入到相应的属性中。
创建Bean容器: 使用@Component、@Controller、@Configuration、@Service、@Bean、@Repository(持久化层的组件)
AOP面向切面编程: Spring Boot 使用 AOP 定义横切关注点时,通过反射,可以在运行时动态创建一个代理对象,代理对象包含目标对象的业务逻辑和横切关注点的业务逻辑,从而实现切面编程。
15.谈谈IOC是什么
容器、控制反转、依赖注入。在SpringBoot中实现了将Bean交给IOC容器进行管理。可以使用@Autowired注入到所需要的地方。
IOC容器本质上是个Map集合,里面存放了各种对象。1.xml配置的bean节点、2.创建Bean容器: 使用@Component、@Controller、@Configuration、@Service、@Bean、@Repository(持久化层的组件),都会由IOC容器进行管理。
使用IOC容器可以实现控制反转,之前应用程序代码负责创建和管理对象之间的关系。而控制反转将这种权利反转到了IOC容器中,可以在运行时主动创建对应的对象,并通过@Autowired等注入到所需要的地方。
在项目中经常使用的注解,@Component、@Controller、@Configuration、@Service、@Bean、@Repository
18.线程安全
线程安全指的是在多线程并行访问共享资源时所产生的原子性、可见性、有序性问题。
原子性 指的是一个线程在执行一段程序时应当是不可中断的,否则可能出现前后结果不一致的问题。cpu中的上下文切换可能破坏原子性,可以使用Synchronized、Lock锁来解决原子性问题。可见性指的是在多线程中,线程对共享变量的修改对其他线程不可见。导致可见性的问题有1.缺乏同步措施。每个线程都有自己的本地缓存,当一个线程修改了共享变量,其他线程可能会读取自己本地缓存中的旧值。2.指令重拍。在多线程中可能出现读写重排、写写重排、写读重排。两者均可以使用volatile关键字,volatile(哇了泰哦)取消重排,并代表着线程本地内存无效,当一个线程修改共享变量后会被立即更新到主内存中,其他线程读取变量时,会直接从主内存中读取。
)。有序性指的是程序执行顺序按照代码先后顺序。但是为了执行效率,允许编译器或cpu对指令重排序,这不影响单线程运行结果。也可以使用volatile解决
19.如何解决git中的冲突问题
冲突问题是,多个开发者修改了同一部分内容,导致在合并分支时有冲突需要解决。首先获取最新代码git pull origin master,查看文件冲突git status,打开冲突文件,手动修改冲突,然后git add 到本地仓库。再git merge -continue合并文件,进行git commit -m "",git push origin master提交。
20.主存、辅存、缓存
主存就是内存,是用于存储程序运行数据的主要内存,通常是动态随机访问存储(Dynamic RAM)形式。
辅存就是用于持久性存储数据的存储介质,比如固态硬盘、机械硬盘。
缓存 cache是为了解决cpu和主存速度不匹配问题,基于静态随机访问存储(Static Random Access Memory,SRAM),减少对主存的频繁访问。
谈谈你对微服务的理解
微服务是分布式架构的一种。将一个项目进行拆分,业务分解成多个独立的项目,每个项目完成部分功能,独立开发和部署。这样会产生很多服务之间的调用关系,而微服务中的注册中心负责管理每个服务的ip、端口、功能。不同服务通过注册中心调用其他服务。微服务中的配置中心统一管理配置。
____________________________________________________________________
Redis篇
redis为什么这么快,是单线程还是多线程?
Redis4.0版本之前使用单线程模型。4.0之后增加了多线程的支持。单线程指的是网络I/O线程和读写由一个线程完成的。但是Redis的持久化、集群同步还是使用其他线程来完成的。
使用单线程很快的原因:
单线程不存在锁竞争,不存在死锁、线程切换、加锁等额外开销。
Redis将数据存储在内存中,纯内存操作。
采用I/O多路复用处理客户端的Socket请求。 (不需要为每个请求都建立一个线程,而是采用一个线程监听多个连接。)
Redis中的数据类型
字符串
哈希
列表
集合
有序集合
Redis缓存三兄弟:穿透、击穿、雪崩
缓存穿透:查询一个不存在的数据,会直接越过redis进入数据库查询,数据库查询不到所以也不会写入缓存。当攻击时使用假的id发起请求,会不断地冲击数据库,可能导致数据库宕机。
解决方案:1.缓存空数据,缓存{key,null}。但这样可能导致大量无用缓存。也可能后续key有数据了,出现数据不一致。2.布隆过滤器。在查询redis之前经过过滤器,若数据存在于过滤器中,则查找redis;否则直接返回。(布隆过滤器基于位图,就是二进制数组。将id进行三次哈希运算映射到位图中,将值更新为1。查询时看是否都为1判断是否存在。)(将系统中的部分或全部数据经过哈希函数映射到布隆过滤器的位数组中,将对应位置的位设置为1。)
缓存击穿:缓存重建时有大量请求。,此时数据库更新redis需要时间,这个时间段可能使得数据库宕机。(查询数据库虽然会更新缓存,但是更新是有时间的)。
解决方案:1.互斥锁(也叫分布式锁)。一个线程执行更新操作,其他线程堵塞。多个线程命中过期缓存时,只有一个线程能获得互斥锁,并进行缓存重建后释放锁,其他线程只能等待。强一致性,但性能差
2.逻辑过期。一个线程执行更新操作,其他线程堵塞,其他线程直接返回过期数据。将过期时间设置为字段。多个线程命中过期缓存时,一个线程A获得互斥锁,会开启一个新的线程执行缓存重建,线程A直接返回过期数据,其他线程在这个过程中获取锁失败,会直接返回过期数据。新线程重建完毕后新线程会释放锁。高可用性能好
缓存雪崩:(多key同时过期)
同一时间段内大量缓存key过期,或者redis服务宕机,导致大量请求到达数据库。
1.设计随机过期时间,防止同一时间大量key过期。
2.针对服务宕机,利用redis集群实现高可用。比如主从架构,哨兵模式。
3.服务降级、限流策略,例如nginx或spring cloud gateway
4.添加多级缓存。使用caffeine作为一级缓存,使用redis作为二级缓存。
Redis作为缓存,如何确保与mysql的一致性?(双写一致性)
1.针对读操作,缓存命中时直接返回;未命中则查询数据库,写入缓存。针对增删改,更新mysql时同时也要更新缓存。
2.但是是先删除缓存还是先更新数据库?(都不行,两步穿插出问题)
采用延时双删:删除缓存-修改数据库-延时之后再删除缓存(降低风险)
采用互斥锁:在修改数据库删除缓存的过程中加锁。(强一致,但是性能比较低)。
另外一种加锁的方法,因为缓存中的数据是读多写少,线程读时可以加共享锁,写时加排他锁。
Redis缓存怎样持久化(怎样保证数据不丢失)?
有RDB和AOF。
RDB:将内存中的所有数据记录到磁盘当中,在服务重启后可以恢复数据。(命令为save(主进程执行RDB)和bgsave(子进程执行RDB)。执行RDB分为主进程执行和子进程执行。主进程执行时会堵塞所有进程,而子进程不会。
子进程原理:主进程会调用(系统调用)fork,fork时操作系统会复制当前进程的内存空间、寄存器状态等信息,创建一个新的子进程执行命令。
AOF中会存储所有写命令,以文本的形式追加到文件中。(优化:同一数据只有最后一次写操作有意义,可以以增长百分比阈值触发重写操作。)
不同点:数据完整性:RDB不完整,两次备份之间的数据会丢失。而AOF相对完整。
文件大小:RDB更低,宕机恢复:RDB更快
AOF用于对数据完整性要求较高的场景。
RDB+AOF:使用RDB恢复数据,恢复期间记录AOF,恢复完成执行AOF。
在 Redis 4.0 后,增加了 AOF 和 RDB 混合的数据持久化机制: 把数据以 RDB 的方式写入文件,再将后续的操作命令以 AOF 的格式存入文件,既保证了 Redis 重启速度,又降低数据丢失风险。
AOF是写后日志,而MySQL是写前日志,有什么区别?
Redis在写入AOF日志之前,不对命令进行语法检查,所以只记录执行成功的命令,避免命令出错。效率高,占用少。缺点:执行完后宕机可能丢失日志。AOF记录到磁盘是由主线程执行的,可能堵塞后续操作。
在Redis中收到命令后,会将命令追加到相应的缓冲区中(比如AOF缓冲区)。然后,线程会立即返回,继续处理其他任务,而不会等待将缓冲区的数据同步到磁盘。
Redis key过期后会立即删除吗?(过期策略,即怎样删除)
惰性删除:过期之后不删除,命中key时检查是否过期,过期则删掉。(Cpu友好,内存不友好)
定期删除:又分为slow和fast。设置频率和每次执行操作的时长,减少对cpu的影响。
内存满了怎么办?(数据淘汰策略)
会使用数据淘汰策略,按照规则删除数据。
8种:1.不淘汰,禁止写入。(默认)
2.淘汰将快过期的(过期时间最小的数据)
3.随机淘汰、随机淘汰有过期时间的
5.整体使用LRU(最近没用)、有过期时间的使用LRU
6.整体使用LFU、(使用频率)有过期时间的使用LFU
Redis作为分布式锁怎么使用
森扩柰子只能解决同一jvm下的互斥,解决不了分布式相同代码部署到多个服务器的配置。
背景:商品抢购超卖的问题。
(用于 Java 程序的 Redis 客户端库)使用redisson的分布式锁:底层是setnx和lua脚本(保证原子性)。setnx是redis提供的命令,即如果不存在则设置值。释放锁则删除DEL Key
SET key value NX EX 10 效果等同于 SETNX key value EX 10 。
如果有值则必须等待删除后,才可以获得锁执行操作。
可能由于网络原因,锁一直存在,可以设置EX过期时间。
过期时间多少?
底层优化:加锁成功后,redisson分布式提供了额外的线程(看门狗)进行监控。每隔一定时间 (释放时间/3 )给锁续期。当工作执行完后删除所,通知看门线程无需续期。
Redission锁可重入吗?
可重入,但是得保证是否是当前线程。森扩柰子也是可重入锁。
Redisson锁能解决主从一致性问题吗
不能解决,但是可以使用其提供的红锁来解决,实现是加锁时将超过一半的redis实例上创建锁。
什么是乐观锁,什么是悲观锁?
悲观锁:修改数据就把数据锁住,释放锁之前不能进行任何操作。保证强一致性。
乐观锁:操作数据不加锁,数据提交时通过一种机制验证数据是否存在冲突。(例如CAS,再例如git添加版本号,提交时对比版本号,相同则提交修改版本号。)
Redis集群方案(如何保证Redis高并发高可用)
主从集群,哨兵模式、分片集群
单点redis并发能力有限,进一步提高redis并发能力要搭建主从集群,实现读写分离。主节点负责增删改,从节点负责读。各个节点要进行数据同步,即保证主从一致性。
主从同步原理:分为全量同步和增量同步。
全量同步:针对第一次同步,从节点发起请求。主节点判断为第一次请求,会同步版本信息(版本号和偏移)。主节点执行bgsave生成RDB文件,从节点通过RDB复制数据。执行期间的的写操作会记录到缓存,等从节点执行完RDB后同步缓存数据,保证数据一致。
增量同步(AOF):从节点向主节点发起请求,判断不是第一次请求,则同步偏移量值之后的数据。具体的是将缓冲区中存储的写命令赋值给从节点执行。
哨兵模式
提供了哨兵模式实现主从集群的故障恢复。
哨兵节点(三个以上)会监控主从集群是否正常工作。主节点故障,则会将从节点选举主节点,并将最新消息推送给redis客户端。
故障检测原理:采用心跳机制检测主从状态。每隔1s向每个redis实例发送ping,某个哨兵节点发现实例未响应则认为主观下线,超过半数则认为客观下线。
选举规则:1.与主从节点断开的时间长短 2.从节点的优先级 3.offset值 4.从节点id选小的。
脑裂:主从节点处于不同的网络中,会出现两个主节点。合并网络后主网络主节点可能被作为从节点,同步时丢失数据。解决方案:设置从节点最小为1;或设置数据复制和同步延迟不能超过5s(减少不一致)。
分片集群
主从集群和哨兵模式可以解决高可用、高并发读的问题。而分片集群解决高并发写的问题。分片集群中有多个主节点。 每个主节点都有多个从节点。此时多个主节点可以充当哨兵,无需再设置哨兵节点。每个主节点保存不同的数据,客户端请求通过key计算的哈希值来命中对应的节点,进行读取操作。
I/O模型解决的问题是用户空间和内核空间之间的数据等待问题。读数据时,要从设备读取数据到内核缓冲区,再交给用户缓冲区;写数据时,用户缓冲区数据要拷贝到内核缓冲区,再写入设备。
堵塞I/O:用户空间想要读取数据时,在内核空间准备数据,这个过程用户空间一直处于堵塞状态(其他进程也堵塞)。
非堵塞I/O:读数据时,用户空间发起请求立即返回执行其他任务,不断的发起recvfrom读取数据,直到数据准备就绪,进入拷贝过程的堵塞。
I/O多路复用:通过单个线程同时监听多个socket。当数据准备好后,内核通知用户进程数据就绪,进行数据拷贝。
什么是套接字
套接字是网络编程中的一种通信机制,不同主机通过socket进行数据交换。使用套接字进行网络通信
——————————————————————————
MySQL面试题
MySQL中如何定位慢查询?
现象:页面加载过慢、接口测试响应时间长。
出现情况:在 聚合查询、多表查询、单表数据量过大、深度分页查询。
1.使用一些开源工具例如skywalking来监听接口的运行情况,定位到响应慢的接口。
2.开启mysql的慢日志。(配置文件中进行修改,设置查询语句执行的时间阈值)
@Transactional可以修饰什么
类、方法。修饰类时,类中的所有方法都会具有事务性质。
数据库单表去重:
DISTINCT关键字去重。SELECT DISTINCT name FROM table_name
关键字执行顺序
FROM、WHERE、GROUP BY、HAVING、SELECT、DISTINCT、ORDER BY、LIMIT/OFFSET
如何解决慢查询?
聚合查询可以增加临时表;多表查询优化sql语句结构;表数据量过大可以添加索引;
针对某个sql语句,可以使用desc来查看当前语句的分析情况。通过key、key_len检查是否命中索引、通过type字段(确定查询类型)查看是否有进一步优化的空间、通过extra建议判断是否出现了回表的情况。
数据结构:二叉树、红黑树、B树(最多五个子节点)、B+树(只有叶子节点存储值)
SQL可以怎样优化?
1.创建表时选择合适的字段类型,例如char定长效率高,varchar可变长但是效率低。
2.不要使用Select *
3.使用union all 代替 union。union会自动去除重复项,效率低。
4.where子句中不使用表达式
5.inner join优于left join和right join(innerjoin会自动小表驱动。)
6.读操作多时,可以使用主从架构。
了解过索引吗
索引是帮助数据库高效查询数据的数据结构。在MySQL的InnoDB引擎中采用B+树的数据结构来存储索引。
B+树优点:
相比于二叉树,B+树阶数更多,路径更短(m阶,则非叶子结点(m/2,m)阶。)只有叶子结点存储数据,非叶子节点只存储指针,这样查询时无需加载其他结点数据。查询效率更高。并且叶子结点之间使用双向指针进行连接,便于扫库查询和区间查询。
B树特点:
多阶、数据为阶数-1个、每个结点都存储数据。
什么是聚集索引、非聚集索引
聚集索引:索引顺序和物理地址顺序一致,叶子结点存储行数据。一个表只有一个聚集索引。
聚集索引选取规则:一般是主键,不存在主键则使用第一个唯一索引,否则默认生成。
非聚集索引:可多个。叶子结点存储的是该行对应的主键。聚集索引如果通过索引无法查找到所需的列数据,则要回表查询(二次查询)。即根据主键再去聚集索引中进行查找。
唯一索引:唯一索引允许空值。一个表可以有多个唯一索引,但是每个唯一索引都必须包含不同的列。
普通索引允许列中存在重复的值,并且也允许空值。可多个
什么是回表查询(二次查询)
非聚集索引中叶子结点不存储实际数据,而是存储对应的主键值。如果通过索引无法查找所需的数据,则需要根据主键值在聚集索引中查找具体数据。这个过程称为回表查询。
什么是覆盖索引:
覆盖索引指的是想要查询的列被索引覆盖,通过索引就可以查找到所需数据,无需查找具体数据行。
尽量走覆盖索引,不要用select *
深度分页的情况怎么解决:
如果直接使用limit分页查询,则需要对数据进行排序,效率低。
limit 20,10=limit 10 offset 20
解决方法:覆盖索引+子查询。
索引建立的原则:
1.(数据量)单一表数据量过大时,应建立索引
2.(使用频率)索引应当建立在常作为查询条件的列中
3.(索引位置,覆盖索引)尽量建立联合索引,提高查询效率。(联合索引应符合最左前缀原则,即以最左边为首的所有组合)
4.(索引数量)索引数量不宜过多。会影响增删改的速度。
索引失效场景:一般都是违反最左前缀法则。
1.索引列参与运算、使用了聚合函数
2.范围查询时右边的列会失效
4.使用or连接的列有没使用索引的。(不是索引则要全表扫描)
5.违反最左前缀法则
6.like '%泽鹏' 百分号在最左边。
并发事务问题:
脏读、不可重复读、幻读、。可以通过设置数据库隔离级别来解决,有读未提交、读已提交、可重复度、串行化。
脏读:读后写。即读取了未提交的数据。一个事务先还未提交时另一个事务读取。
不可重复读:读写读。两次读取到的数据不相同。事务A读取,事务B修改,事务A读取。注重更新
幻读:查写查。两次查询同一范围内的数据不同。事务A查询,事务B新增或修改,事务A查询。注重增减
如何解决上面三个问题:
设置数据库隔离级别。
读未提交:不能解决脏读、可重复度、幻读问题。
读已提交:解决脏读。对于同一数据,一个事物写时,其他事务不可读。
可重复读:进一步解决不可重复读。一个事物读取数据时,其他事务不可读写。
串行化:进一步解决幻读。事务需序顺序执行,不能并发执行。
redo log 和undo log
都是InnoDB的日志。redo log可以用来宕机恢复数据。因为其记录的是数据页的物理变化。保证了持久性和完整性。undo log是逻辑日志,事务回滚时根据undo log恢复数据,保证了原子性和一致性。
事务中的隔离性怎么保证?
涉及到事务的隔离级别。
MySQL主从同步原理:
主从同步假设是一主多从,主节点负责写,从节点负责读,可以一定程度上解决高并发读的问题。mysql主从同步是通过bin log日志和relay log日志。主库完成事务提交会记录到binlog日志中。从库读取binlog日志写入到从库的relaylog日志中并重做,完成同步。
MySQL中的分库分表:
分库的目的是减轻单一数据库的负载,分表的目的是降低单一表的数据量,提高查询效率。分库分表通常是为了处理大量数据和高并发访问的情况。
-
1.1 分库
分库 就是将数据库中的数据分散到不同的数据库上。
垂直分库:
不同的表放到不同的数据库中
水平分库:
单一表数据量过大,拆分成多个表放在不同的数据库中1.2 分表
分表 就是对单表的数据进行拆分。
垂直分表:
抽出某些列,形成其他表
水平分表:
同一个表中,不同的行数据按照规则分成多个表
内网穿透Cpolar
可以将内网下的本地服务器,通过安全隧道暴漏到公网,使其可以被公网用户访问。
连接池
连接池通过预先创建数据库连接并将它们保存在池中,减少频繁连接和关闭数据库的开销。
其他的还有c3p0、dbcp、德鲁伊。
Druid:导入Jar包。在properties配置文件中配置jdbc驱动、数据库连接和密码、初始连接数、最大连接数等、获取连接最大等待时间。
代码中:定义Druid对象实例,由其调用加载配置文件方法,返回Connection实例。之后通过driud实例调用方法并传入Connection的实例,完成连接池。
——————————————————————————————
Spring问题Spring问题Spring问题Spring问题Spring问题
Spring中的Bean是单例的吗,是线程安全的吗
是单例的,单例模式保证一个类只存在一个对象实例。不是线程安全的。
一般注册为bean的类,其中的参数在运行时不会改变,所以没有线程安全问题。但如果在bean中定义了可修改的成员变量,那么要考虑线程安全问题。(无状态对象一旦被创建并定义好,就不再改变其内部状态)
@Scope("singleton")
AOP面向切面编程是什么
AOP(面向切面编程),指的是在程序运行期间,可以动态的将代码切入到类中的方法中执行。它解决的是与业务无关的重复性代码的抽取问题,例如日志记录、事务管理。
具体怎么使用AOP
在代码中可以通过@Acpect定义切面类,切面类中定义需要切入的方法,称为通知。通知分为前置通知、后置通知、返回通知、环绕通知、异常通知、。通知中需要写入切点表达式。通知中定义的功能称为横切关注点。最终可以在连接点执行所需要的代码。
切点表达式:@Before("execution(* com.example.myapp.service.*.*(..))")
execution+返回类型+全路径
AOP的原理
AOP的底层是动态代理模式,因为其需要在程序运行时动态的创建代理对象。代理对象包含目标对象的业务逻辑和横切关注点的业务逻辑,通过代理对象执行业务代码和横切关注点代码。
事务管理也是通过AOP的功能,也是动态代理模式。在执行方法之前开启事务,执行完之后提交事务或者回滚事务。
在项目中用到的AOP有:@Transactional事务、公共字段自动填充。对数据库的操作大多都会开启事务保证数据一致性。公共字段自动填充是个前置通知,每当增加或者修改菜品时,数据库的菜品表也会更新当前的修改时间和修改人这两个公共字段。也就是说连接点就是增加或者修改的方法。修改时间是定义的LocalDateTime.now(),而修改者id是通过threadLocal.get()获取的用户id。用户id在进行JWT校验时,会由threadLocal.set(id)方法存入到当前线程的局部变量中。这样在一个请求的处理周期内可以获得用户id。
切面:使用@Aspect定义切面类,在这个类中定义了一些方法(通知),使用切点表达式定义了在哪里执行这些通知。
连接点:切面可以介入并通知的地方。比如类中的方法。就是类中的方法。
横切关注点:与业务逻辑无关的功能。在连接点上插入横切关注点(日志、事务等)。
通知:就是在连接点处执行的代码。也是写在切面类中的方法。根据注解可以分为5个:前置通知@Before、后置通知@After、返回通知@AfterReturning、异常通知@AfterThrowing、环绕通知@Around(在连接点前后都执行,能够控制连接点的执行。)
切入点:切入点表达式。定义了在哪执行通知。(“execution(* com.方法全路径)”)。*指匹配任何返回类型。
引入:在不改变类继承关系的情况下,通过@DeclareParents向类中添加新的功能。(属性、方法)
代理:AOP在实现上采用了设计模式中的动态代理模式。AOP底层使用动态代理的主要原因是,在程序运行时动态创建代理对象,代理对象包含目标对象的业务逻辑和切面的横切关注点逻辑。这样当程序调用代理对象的方法时,可以执行横切关注点。
Spring中事务失效的场景
1.出现检查异常
2.而对非受检查的异常,自己写了try-catch但是没有抛出。
3.方法不是public的。
异常分为哪几类
异常主要分为两大类:检查异常(Exception子类)和非受检查异常(RuntimeException)
受检异常:必须处理,并抛出异常。例如文件错误、
非受检异常(数组越界、空指针):指不要求强制处理或声明的异常、非法参数数量
异常有哪些:空指针、数组越界、未找到、非法参数数量、
ERROR:栈溢出、内存溢出、
异常
编译异常和运行时异常。
运行时异常是RuntimeException类及其子类,有空指针异常、下标越界等等。
编译异常必须处理,否则编译不通过。例如IO异常-找不到文件。
Spring Bean的生命周期
1.通过BeanDefinition获取bean的定义信息。BeanDefinition是一个接口,可以获得bean的属性信息如 类名、属性、构造函数等。
2.实例化Bean。
3.Bean的依赖注入。注入其依赖的其他类的引用、属性注入。
4.处理各种Aware接口(BeanNameAware获得自己在容器中的名字、BeanFactoryAware自己所在的工厂)因为Bean是由容器管理的,Aware接口就是向容器索要自己的信息。
5.Bean的前置处理器。实现了BeanPostProcessor接口的类,接口中的前置方法和后置方法,用于对Bean进行个性化定制。
6.Bean的初始化方法。(初始化方法中可以自定义一些逻辑,比如资源加载(数据库连接、打开文件等))
7.Bean的后置处理器。实现了BeanPostProcessor接口的类,接口中的前置方法和后置方法,用于对Bean进行定制。
8.销毁Bean
什么是Spring循环依赖?
两个或多个对象间的循环依赖(循环引用)。bean的实例化、初始化。无法完成初始化。
循环依赖是允许存在的,Spring中提供了三级缓存,可以解决大部分循环依赖。
一级缓存:初始化完成的Bean。
二级缓存:未完全初始化的bean。
三级缓存:生成Bean实例的工厂。
出现了循环引用,实例化的Bean会首先放到二级缓存,三级缓存会发现需要这个引用的地方并返回。初始化完成后放到一级缓存。
但是不能解决构造方法中的循环依赖。可以通过@Lazy懒加载,什么时候需要对象再进行bean对象的创建。
SpringMVC的执行流程:
在传统的JSP应用中,前端页面的展示逻辑和后端业务逻辑通常耦合在一起,即在同一个 JSP 文件中包含了HTML和Java代码(jsp是在HTML标签中嵌入的Java代码,动态生成页面内容)
1.用户发出请求 到DispatcherServlet
2.DispatcherServlet会接收到请求调用 处理器映射器 HandlerMapping
3.处理器映射器 会找到具体的controller,生成controller对象及拦截器,返回。
4.DispatcherServlet 会调用 处理器适配器(HandlerAdapter)
5.处理器适配器调用controller,处理器完成后返回ModelAndView对象。视图名称和模型数据。处理器适配器调用前后执行一些自定义逻辑。
6.DispatcherServlet将ModelAndView传给视图解析器
8.视图解析器返回具体的View,
9.DispatcherServlet根据View和数据(model)进行渲染视图,响应给用户。
先去映射器拿处理器和拦截器,然后到适配器中调用处理器执行返回model和view对象,然后拿到结果给视图解析器找到具体的view,然后根据view进行渲染视图,返回给用户。
而针对目前的前后端分离,面向接口开发,则:
改变的地方:视图解析器调用处理器映射器,映射器会调用处理器,给出返回结果。处理器上会使用@ResponsetBody注解,底层通过HttpMessageConverter将返回结果转换为json并响应。
SpringBoot自动配置原理-旨在减少开发者在项目中繁琐的配置工作。主要是针对一些常用的框架和库,提供默认配置无需手动配置。比如@Slf4j默认配置。log.info(“输出”)
@EnableAutoConfiguration
自动配置需要开启自动配置注解。在项目启动类上有@SpringBootApplication,它可以分解为三个注解:@SpringBootConfiguraion(配置类)、@EnableAutoConfiguration自动配置和@ComponentScan(扫描包)、和。EnableAutoConfiguration是自动配置的核心注解,通过@Import注解加载对应的配置,自动配置应用程序所需的各种组件。
META-INF/spring.factories(迈特音符/spring法克锤丝)文件中所配置的类的全路径。(这些类中所定义的@Bean)会根据指定条件 决定是否导入到容器中。
是否将其导入是通过例如@ConditionalOnClass: 当类路径中存在指定的类时,配置生效。
例如还有@ConditionalOnMissingBean。
拓展:classpath
是Java虚拟机用来搜索类和资源文件的路径,例如jar包的完整路径就是它的classpath。META-INF/spring.factories
是Spring Boot自动配置的配置文件,用于配置各个模块或库的自动配置类,实现对应用程序的自动配置。
Spring、SpringMVC、SpringBoot中的常见注解、
Spring:
@Component:指的是标识一个类为Spring组件。
@Controller:标识一个类为控制器。
@Configuration:定义配置类,通常通过@Bean标识方法,一起使用。
@Service:标识一个类为业务层的组件
@Bean:使用在方法上,将返回值存储到Spring容器中。
@Autowired:依赖注入
@Repository:标识一个类为持久层组件。
@ComponentScan:指定要扫描的包。@ComponentScan(basePackages = "com.example")
@Import:被其导入的类回家再到IOC容器中。
@Aspect、@Before、@After、@Around、@Pointcut面向切面编程的注解。
@RequestParam:指定一个变量名,用于获取HTTP请求中相同变量的值。
@PathVariable:路径中有路径变量,使用 @PathVariable获取
@GetMapping("/example/{id}")
public String exampleMethod(@PathVariable Long id, @RequestParam("paramName") String paramValue) {
// 处理 id 的值
return "examplePage";
}
@ResponseBody:用在方法上,方法的返回值java-json
@RequestHeader:接收 HTTP 请求头中名为 "指定参数" 的头信息。
配置类注解:
@PropertySource("classpath:application.properties")
@Value 注入属性
————————————————————————
MyBatis的执行流程(原理)
MyBatis是一个持久层框架。
1.Mybatic-config.xml配置文件中配置数据库信息和映射文件(加载映射文件有两种:配置映射文件<mappers>具体路径、配置包路径(扫描包下的所有xml文件)
<mappers>
<mapper resource="com/example/mappers/UserMapper.xml"/>
</mappers>
<mappers>
<package name="com.example.mappers"/>
</mappers>
2.构建sql会话工厂SqlSessionFactory(接口),会话工厂负责创建sqlSession对象,其包含了执行sql语句的方法。
3.在执行SQL语句时,SqlSession调用Executor的方法,将MappedStatement作为参数传递,参数封装了SQL语句信息。
4.Executor操作数据库,将映射信息转变为sql能操作的类型,执行完后将数据库返回结果映射为java类型。
MyBatis是否支持延迟加载
延迟加载:比如一个订单表中,关联了多个菜品表的菜品信息。如果查询订单时将菜品信息也查询出来,称为直接加载。如果是需要时再加载,则成为延迟加载。
支持延迟加载,但是默认为关闭。在mybatis配置文件中将LazyLoadiingEnable=true可以开启。
延迟加载的底层原理:MyBatis通过动态代理机制创建一个代理对象,该代理对象实现了原始对象的接口,同时添加了延迟加载的逻辑。
MyBatis一级缓存和二级缓存:
一级缓存作用域为同一个SqlSession。sql语句执行会通过SqlFactory创建SqlSession,会话结束会关闭SqlSession。sqlSession具体由Executor执行,缓存也是创建在Executor中的本地缓存。默认开启一级缓存。一级缓存无法关闭
二级缓存作用域是namespace,可以被多个SqlSession共享。需要单独开启。当一级缓存session或二级缓存namespaces进行了增删改,则清除缓存。使用 CachingExecutor 装饰 Executor,先查询二级缓存,再查找一级缓存,然后查询数据库。
开启二级缓存通过:1.全局配置文件cacheEnable=true;2.指定的映射文件使用<cache/>标签。
二级缓存是PerpetualCache缓存类(泼派出奥 开车)的HashMap本地缓存。
namespace
来区分不同的映射器(文件)和 SQL 语句。例如:
<!-- 在命名空间 com.example.mapper.UserMapper 下定义的 SQL 语句 -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
MyBatis使用方法
1.通过在方法上使用增删改查注解,调用方法即可执行sql查询。
// 使用 XML 配置文件的方式
User user = sqlSession.selectOne("com.example.UserMapper.getUserById", 1);
// 使用注解的方式
@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(@Param("id") int id);
2.在映射文件(UserMapper.xml)中配置namespace(关联具体的java接口。是接口的全限定名)。使用html标签的增删改查编写数据库逻辑。指定id映射对应的方法。
<mapper namespace="com.sky.mapper.UserMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into user (openid, name, phone, sex, id_number, avatar, create_time)
values (#{openid}, #{name}, #{phone}, #{sex}, #{idNumber}, #{avatar}, #{createTime})
</insert>
#{id}和${id}的区别
#{}解析为SQL时,会将形参变量的取出,并自动为其添加引号。可以有效防止SQL注入问题。
${}解析为SQL时,会将形参变量值直接取出,拼接到SQL中。无法防止SQL注入问题。
username = "' OR 1=1 OR '"
————————————————————————————————
微服务篇:
什么是微服务:
是一种架构模式,将一个一个项目中的多个功能拆分成多个服务,每个服务可以单独部署运行
好处:1.灵活性、可维护性。每个微服务拥有自己的业务,使得单个业务的修改、更新更为灵活。
2.独立部署不影响整个体统的稳定性。
3.开发速度更快。不同微服务划分不同部门负责。
Spring Cloud 组件有哪些?
网关、负载均衡、注册中心、服务调用、服务熔断、。
网关:GateWay(服务入口)
负载均衡:Ribbon(平衡服务器压力)
注册中心:Eureka(宜瑞卡)、Nacos(奶考斯)(配置服务)
远程调用:Feign(服务之间调用)
服务熔断:Hystrix()
什么是网关:
Spring Cloud GateWay。网关服务是外部需要经过网关访问服务,访问内部的所有服务都必须先经过网关服务。网关服务的主要功能是消息解析过滤,限流,路由转发等。
服务注册和服务发现是什么?Spring Cloud如何实现服务注册发现?
服务注册: 不同的微服务需要将自己的信息注册到注册中心,例如名称、ip、端口。
服务发现:自动识别可用的服务实例,使得不同的服务之间可以发现彼此,以便能够协同工作和通信。
服务监控:为了剔除不健康的服务。通过心跳机制。服务需要在一定间隔内向注册中心发送心跳,服务中心一定间隔没收到则进行剔除。
eureka和Nacos区别:
Eureka心跳机制默认30s向注册中心发送信心跳,90s内没收到则认为服务过期,剔除服务。
Nacos中分为临时实例和非临时实例。临时实例也是采用心跳机制,但是频率比Eureka要快。规定时间内没有收到心跳则进行剔除。非临时实例采用主动询问的方式,如果非临时实例长时间宕机也不会进行剔除。
Eureka中客户端(发起调用的微服务)会定时从注册中心拉取服务。Nacos除了会定时拉取服务,当服务状态更新时会主动推送。
Nacos检测分为两种:集群分为AP和CP方式,有临时实例时采用CP方式,强调一致性。CP集群中如果某个节点发生故障,则系统会暂停服务,直到恢复一致状态。
AP强调可用性,CP强调一致性。
负载均衡如何实现
用户端发起的请求通过网关后,到达负载均衡Ribbon。Ribbon拉取 注册中心中的服务列表,采用指定的负载均衡策略选择服务节点。
Ribbon负载均衡策略有哪些
轮询、加权轮询(响应时间越长,权重越小)、随机、加权随机、最少连接数。Zone(区内)轮询、
怎样自定义(修改)负载均衡策略?
通过实现 IRule(I肉)
接口自定义负载均衡策略。
什么是服务雪崩?怎么解决?
不同微服务之间通过feign(愤)来发起的远程调用,这个过程可能涉及到多个节点。服务雪崩是一个服务失败,可能导致整条链路的服务都失败,从而影响系统整体的稳定性和可用性。
怎么解决:
1.服务降级。指的是限制接口的功能,只提供部分功能保证可可用性。比如一个电商项目有商品信息和评论,当评论业务出现问题是,可以对这个服务进行降级,只提供之前的评论信息,或者只能查看但是暂时无法评论。可以通过feign的fallback设置服务失败时的降级逻辑。
2.熔断机制。例如Hystris(嗨四锤死)。逻辑:10秒内服务请求失败率超过50%,触发熔断机制。之后所有请求都会快速失败。之后每5s会接受一条请求看是否失败。失败则继续熔断,成功则关闭熔断。
默认关闭,可以在引导类上添加@EnableCircuitBreaker(色Q特)注解开启。
服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑
微服务怎样进行监控?
(为什么要定位?有很多微服务,以及微服务链路。)
可以使用一些接口监控工具例如Spring-admin或者skyWalking。skyWalking是一个开源链路监控工具。skyWalking中可以监控响应较慢的微服务、响应慢的接口。并且提供了接口链路追踪、微服务调用关系拓扑图等功能。
微服务什么是限流?
防止突发流量影响系统稳定性和可用性。
令牌桶算法:请求消耗令牌桶中的令牌,令牌耗尽则排队等待。(我理解的就是信号量)
漏桶算法:请求添加到漏桶中,以恒定速率流出。
Tomcat 可以设置最大线程数(maxThreads),当并发超过最大线程数会排队等待。
Nginx 提供了两种限流手段:一是控制速率,二是控制并发连接数。
什么是CAP定理?
C:一致性 A:可用性(用户访问时应当得到响应,而不是超时或者拒绝) P:分区容错性(即使分网络分区了,可以等网络恢复)。
只有AP和CP。
什么是Base理论?
是一种解决分布式事务的思想:1.最终一致思想:微服务中各个事务分别执行提交,允许出现不一致情况,但要保证最终一致。(AP)2.强一致思想:各个事务不能提交,需等待彼此的结果,而后统一进行提交或者回滚。(CP)
如何解决分布式事务问题?
分布式事务中包含 多个服务之间的写操作,需要进行分布式事务控制。
2PC机制。依靠数据库层面。事务参与者(服务)和事务协调者(逻辑)。第一阶段:协调者通知参与者,开启各自的事务。第二阶段:所有参与者进行提交,如果有任何一个出现问题则全部回滚。
3PC机制。因为2PC中当某个参与者不可用,会浪费其他参与者的资源。第一阶段增加了询问阶段,询问是否可用。
Seata框架。XA模式(强一致)、AT模式(最终一致)、TCC模式。(资源管理器、事务管理器、事务协调者,个人更喜欢叫参与者)
XA:参与者注册、执行SQL但是不提交,报告状态到协调者。事务协调者根据状态确定是否全部提交或者全部回滚。(缺点:需要相互等待状态提交,线程锁死性能差。)
AT:参与者执行SQL并提交,记录undo log日志,然后报告状态给协调者。协调者检查状态如果全部成功则删除undo log。失败则执行undo log回滚。
TCC: Try(检查并预留)、Confirm(执行提交)、Cancel(释放预留)。
MQ。RocketMQ支持。首先给Broker发送事务消息,事务消息此时对消费者不可见。发送成功后发送方开始执行事务操作。成功发送提交消息,失败发送回滚消息,丢弃Broker中的消息。如果是 Commit 那么消费者就能收到这条消息,然后再做对应的操作,做完了之后再消费这条消息即可。
(用户下单,提交扣除库存和生成订单的事务,失败则没发生过。成功则生成订单服务会消费这条消息)
分布式服务的接口幂等性如何设计:
什么是幂等性?多次重复调用方法的效果应当与第一次调用的效果相同。例如提交订单按钮点击多次,只有一次会成功。需要幂等的场景:1.用户重复点击(网络波动)2.MQ消息重复消费 3.失败重试机制。
采用redision锁:请求会在redis中生成token,token会返回给客户端。当客户端携带token请求业务接口时,会检查redis中的token,如果有token则处理业务并删除token。没有token则失败,直接返回。
采用分布式锁:加锁处理,第一次请求获得锁。
================================================================
消息中间件篇:
RabbitMQ和Kafka。
什么是MQ
是消息中间件,是常用于服务间的的消息传递系统,可以异步、削峰、解耦。
异步:
指的是不同服务间的消息传递是异步的,一个服务作为生产者发送消息到消息队列,另一个服务作为消费者监听消费队列进行消息处理,实现异步通信。实现了解耦和异步调用。
削峰:
出现大量突发请求时,MQ可以可以实现削峰。将无法及时处理的消息放入消息队列,后端可以从MQ中慢慢消费消息,放入消息队列消息不会丢失,虽然可能响应较慢。
目前可供选择的有RabbitMQ、KafKa(卡发卡)、RocketMQ。
RabbitMQ单机可实现万级吞吐量。us级时效性。主从架构
KafKa(卡发卡)、RocketMQ。10万级吞吐量,ms级时效性。分布式架构
(不同的MQ吞吐量不同、时效性不同、架构不同)
消息队列是在消息传递时保存消息的容器,通常有消费者和生产者两个角色。生产者只负责发送消息到消息队列,消费者只负责消费消息。
RabbitMQ
生产者发送消息-交换机处理消息-队列存储消息-消费者监听队列-消息消费-消息确认(可选)
看RabbitMQ的官方文档时写了基本的连接接收方式。
创建连接工厂ConnectionFactory,连接工厂要指定IP地址、用户名和密码。
连接工厂会创建连接Connection,连接创建通道Channel进行信息传输。
生产者发送数据时,要为channel.queueDeclare指定队列声明,声明:队列名称、消息持久化、队列消息共享等信息。调用channel.basicPublish指定交换机、队列名称、发送的消息.getBytes。
队列声明、基础提交
消费者接收数据时,要指定channel的队列声明:channel.queueDeclare(QUEUE_NAME, false, false, false, null); 同时编写回调方法DeliverCallback和失败回调方法CancelCallback。使用channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);接收消息,传入队列名称和回调、失败回调。 // 参数:队列名称、接收确认
队列声明、基础消费
RabbitMQ如何保证消息不丢失:(还没用呢就丢了)
这和消息队列的过程有关。生产者发送消息到交换机,交换机将消息传递给队列,消费者监听队列,消费消息。这几个过程都有可能出现消息丢失。
1. 开启生产者确认机制。可以让生产者知道消息是否成功的发送到交换机和队列,如果没有可以重发消息。
2.开启持久化功能。分为交换机持久化、队列持久化,确保消息没被消费之前不糊消失
3.开启消费者确认机制。消费者处理完消息后会向MQ发送消息,MQ收到消息后删除消息。
RabbitMQ消息重复消费问题
redission锁
消费者确认机制中,由于网络原因消费者处理完消息后没来得及发送消息。
唯一标识符。每个消息设置唯一 标识,首先检查标识是否处理过,处理过则不再处理。(可以存储到Redis中,使用setnx。消费者为消息设置唯一表示存储到Redis中,消费消息时首先检查标识是否存在。
RabbitMQ死信交换机、延迟队列
死信交换机:用于处理死信消息。消息过期、消息被拒绝、消息满载后淘汰的消息。死信消息进入死信队列。
延迟队列:消息会延迟消费的队列。(场景:超时订单、限时优惠)
延迟队列=普通队列不被消费+过期时间(TTL)+死信交换机+死信队列。可以设置一个死信队列成为延迟队列,等待消息过期进入死信队列。
RabbitMQ消息堆积
生产者发送消息的速度大于消费者消费消息的速度。
1.增加消费者。
2.消费者开启线程池。
3.扩大队列容积。采用惰性队列,将消息存储到磁盘当中。需要时再读取到内存。
RabbitMQ高可用机制
(高可用一般就代表集群。集群分为各个节点)
普通集群、镜像集群、仲裁队列。镜像集群用的最多。
普通集群:多个节点的集群(交换机是共享的)。每个节点中队列是不同的。但是节点中有其他节点队列的引用信息。当访问的是引用信息时,会找到对应的节点中的队列消费消息。缺点:队列所在的节点宕机,队列中消息就会丢失。
镜像集群:个人理解就是将普通集群节点中的队列的引用改成了主从关系。一个节点的队列会在各个节点中进行备份。所有操作都是主节点完成的,主节点要给镜像节点同步消息。主节点宕机,镜像节点会替代成为新的主节点。缺点:同步时主节点宕机,同步失败信息丢失。可以使用仲裁队列。
仲裁队列:与镜像队列一样是主从模式,支持主从数据同步。只是在数据同步时,基于Raft协议,能保证强一致性。
Raft协议是一种领导者选举算法,目的是保证分布式系统中多个节点的强一致性。1.日志追加和复制。主节点收到客户端请求时会追加自己的日志,并将日志同步到其他节点。超过半数的节点同步成功才能执行相应操作。2.选举投票。会根据日志新旧的程度进行投票,节点获得超过半数的票会选举成为领导者。
RabbitMQ:生产者、消费者、交换机、队列、死信交换机、死信队列
Kafka:生产者、消费者、Broker、Topic、Topic的不同分区。独立的kafka服务器称为Broker。一个Broker可以有多个Topic,kafka的消息通过Topic进行分类。一个Topic有多个分区,每个分区都是一个带偏移量的有序队列。在集群中一个庞大的Topic可以分布到不同的Broker中。
Kafka如何保证消息不丢失
消息丢失分为三种:1.生产者发送消息到Broker丢失 2.消息在Broker中丢失 3.消费者从Broker接收消息丢失。
生产者发送消息可以使用同步发送或者异步发送。同步可能发生堵塞,一般使用异步。
确认机制、偏移量、
1.生产者发送消息到Broker丢失
设置异步发送,发送失败可以进行重试,设置重试次数。。
2.消息在Broker中丢失(因为Kafka每个分区都有主从节点。这里指的是主从同步失败)
消息确认机制。Broker中的主节点和从节点保存数据后进行响应。(acks=0 无需等待响应。acks=1 需要等待leader相应 acks=all 等待所有leader和follower响应)
3.消费者从Broker接收消息丢失。具体的是在分区重平衡(节点宕机,重新调整)的时候丢失。消费者处理消息和提交消息并不是同步的。可能出现提交了偏移量,但是还没及时处理消息就宕机了,这时出现消息丢失。可能处理了消息没来得及提交偏移量,这时出现重复消费。
可以设置消费完消息就进行提交。关闭自动提交偏移量。
Kafka如何应对重复消费(处理了消息没来得及提交偏移量,这时出现重复消费)
1.消费完消息就进行提交。关闭自动提交偏移量。
2.幂等方案。为消息设置1.唯一标识符。2.唯一 token 凭证。(每次请求都生成唯一token进行验证。)
kafka中同一个topic中的不同分区都是由偏移量(offset)来记录消息的消费情况。消息实际存储在topic分区中的不同偏移量中。
Kafka如何保证消息顺序性(聊天界面消息顺序)
在kafka中消息是分区存储的,每个分区中消息时按照顺序进行存储的,但是不能保证跨分区消息的顺序处理。每个业务可以只提供一个分区,以保证消息的顺序性。可以在发送消息时同一业务指定分区号。
Kafka高可用
1.搭建kafka集群 2.分区备份机制
1.卡夫卡集群,某个broker宕机,其他broker可以正常工作。
2.topic中有不同的分区。分区是有主从关系之分。主节点负责提供服务,主节点宕机可以选举从节点成为主节点。
选举leader的机制:首先follower中分为两种:一种是同步follower,一种是异步follower。同步更能保证一致性,但异步可用性更高。1.从同步follower中选定leader。2.同步follower都不行,选异步follower。
Kafka数据清理机制?
消息清理机制:1.消息超过了存活时间。2.根据topic存储数据大小,topic所有分区下数据总和达到阈值,则开始删除最久的消息。
Kafka中实现高性能的设计:
1.消息分区。增强了并行处理能力。
2.顺序读写。分区中的消息是追加的方式顺序读写的,(顺序磁盘寻址性能高)
3.页缓存,零拷贝。将磁盘数据读到内存中,减少I/O
零拷贝(减少磁盘I/O和网络I/O)
两种流程:
1. Mmap+Write操作过程。此时内核缓存区的数据不会真正复制到用户缓存区,而是通过映射。
2.sendFile。直接由内核缓冲区复制到socket缓冲区。
=================================================
集合合集:
ArrayList
用连续内存空间存储相同数据的数据结构。
栈内存中存放array,其指向了自己在堆内存中存储空间的首地址0x1110。
通过下标查找:O1
随机查找:On
二分查找(要求顺序排序):Ologn
插入和删除:On
为什么数组索引从0开始,而不是1?
拿arraylist来说,其寻址公式是:a[i] = 首地址+ i*数据类型所占地址。从0开始刚好。但是如果从1开始,公式中的i得改为i-1,cpu多了一个减法指令。影响性能。
ArrayList底层原理是怎么实现的?
ArrayList底层使用的是动态数组。初始容量为0,第一次添加数据才会初始化为10。
扩容时为原来容量的1.5倍,每次扩容都需要拷贝数据。
添加数据时,计算size+1后是否能够存入数据。不能则扩容为1.5倍。然后将新增的数据添加到size的位置上。
ArratList list = new ArrayList(10) 扩容了几次? 没有扩容,只是指定了容量为10。
数组怎样和List转换?
数组转list:List<String> list = Arrays.asList(strs);
list转数组:
使用Arrays.asList转成list后,修改了数组,list受影响。因为这里的list只涉及到了对象的引用,并没有创建新的对象,list和数组指向的是用一个地址。
使用list.toArray(new String[list.size()]),修改了list,数组不受影响。因为底层会重新拷贝数据到新的数组中,其地址发生了改变。
长度:数组length集合size
ArraysList和LinkedList的区别?
1.底层数据结构。ArrayList底层是动态数组。LinkedList底层是双向链表。
2.时间复杂度。ArrayList按照下标查询为O1,LinkedList不支持下标查询。
没有下标则两者都是On。
3.空间占用
4.线程安全:都不是线程安全。1.但使用时可以在方法内使用,局部变量是不存在线程安全问题的。2.可以使用 Collections.synchronizedList 封装,保证线程安全。
HashMap
二叉树:每个节点最多有两个叉。
二叉搜索树(排序树、有序二叉树):左边小于父节点,右边大于父节点。
红黑树:一种自平衡的二叉搜索树。查、增、删均为Ologn
散列表(哈希表):数组+链表(转换红黑树)。通过key来访问value
哈希函数: HashMap
使用哈希函数将key映射到哈希表的特定位置,实现快速存储。但存储时可能会发生哈希碰撞。哈希碰撞是多个key映射到同一个数组下标位置。
冲突处理:发生冲突时,方法有链地址法(Chaining)和线性探测(Open Addressing)等。
链地址法:对key进行hash运算、定位hash表位置发现碰撞,判断碰撞位置key是否相同、不同则在链表中查找、没有则插入到链表中。
线性探测:碰撞则根据规则寻找下一个可用位置。
动态扩容: 元素达到一定数量就会进行扩容,扩容因子为0.75,扩容为当前的两倍。链表长度大于8,数组长度大于64时转化成红黑树。链表中小于6个,会退化成链表。
HashMap的底层原理?
底层是使用的数组+链表+红黑树。
1.使用put添加元素时,哈希函数通过key计算当前元素在数组中的下标。
2.存储时,先通过key计算哈希值,如果哈希值相同则进行判断:如果key相同,则覆盖;如果key不同,则发生了哈希碰撞,将当前的元素存入链表中。如果链表中有元素则遍历看是否存在相同的key,有则覆盖,没有则加入链表。(1.8以后尾插)
3.获取时,直接找到hash值对应的下标,然后进行判断。key相同则查找成功,不同则去链表中进行查找。
HashMap中put方法的流程
1.判断当前数组是否为空,为空则进行初始化,长度为16。 0.75、两倍
2.根据hash值找到对应位置,判断当前下标是否为空。为空则直接插入不为空则进行判断。
1.table中key一样,覆盖。否则:
2.当前是否为红黑树节点,如果是则在树中遍历,没有则插入。
3.如果不是红黑树,则遍历链表,如果没有相同的key则在尾部进行插入。然后判断链表长度是否大于8,如果大于则转化成红黑树,
HashMap中扩容机制
第一次加入数据进行初始化,长度为16。此后超过0.75的扩容阈值则进行扩容,是之前容量的两倍。
扩容创建新的数组,把老数组数据挪到新数组中:&代表按位与,如果两个操作数的对应位都是1,则结果的对应位为1,否则为0。异或运算:不同为1,相同为0。
使用哈希值&(当前容量-1)直接存入hash值对应的索引。
HashMap的寻址算法
1.计算hashCode值,
2.调用hash()方法进行二次哈希,hashcode值右移16位再与原先的hashcode进行异或运算,让哈希分布更加均匀,因为可以使得其高位和低位都能包含哈希信息。
3.得到的hash值&按位与(数组长度-1),得到索引。
HashMap的长度一定是2的n次幂?
1.初始化为16,之后每次触发扩容都增加原来的一倍。所以一定是2的n次幂。
2.这样可以使用按位与运算代替取模,计算索引效率更高。
HashMap在1.7中的多线程死循环问题
1.7之前没有红黑树,是数组+链表的数据结构。数组扩容时,链表使用的是头插法,所以在数据迁移过程中可能导致死循环。
死循环使是由两个特点产生的:
1.数据迁移过程中没有新对象产生,只是改变了对象的引用。
2.头插法。
两个线程都触发了扩容,一个线程读取了A的Next进行等待。另一个线程完成了扩充操作。此时第一个线程认为:A->B->A->B.....
(线程A准备扩容;线程B介入完成扩容;线程A陷入死循环。)
你对ConcurrentHashMap的理解:
是线程安全的HashMap。
1.7底层使用的是分段数组+链表。
1.8采用的与hashmap一样,数组+链表+红黑树。采用CAS和Synchronized保证并发时的线程安全。
因为出现线程安全问题的地方有:1.多个线程同时映射到哈希表中的同一个值想进行修改,此时使用CAS机制保证只有一个线程能修改成功。2.多个线程同时想修改链表或者红黑树,则使用Synchronized锁住首节点。
Arraylist与Vector的区别
Vector是线程安全的,ArrayList不是线程安全的
ArrayList在底层数组不够用时在原来的1.5倍,Vector是原来的2倍。
和ArrayList和Vector一样,同样的类似关系的类还有HashMap和HashTable,StringBuilder和StringBuffer,后者是前者线程安全版本的实现。
扩容:
ArrayList:非线程安全。初始化为0,第一次到10,超过当前容量,扩容1.5倍。
Vector:线程安全。初始化为10,超过当前容量扩容2倍。
Hashtable:初始化为11,扩容因子0.75,扩容2n+1。(质数碰撞概率较小)
HashMap:初始化16,扩容因子0.75,扩容2倍。
17.hashtable与hashmap的区别
线程安全性:
HashMap 是非线程安全的。Hashtable 是线程安全的。
性能:
由于 Hashtable 的所有方法都是同步的,因此在单线程环境下的性能可能比 HashMap 差。
允许null键值:
HashMap 允许键和值都为 null。
Hashtable 不允许键和值为 null。
迭代器:
HashMap 的迭代器是快速失败的。在迭代过程中,如果有其他线程修改了 HashMap 的结构(增加或删除元素),会抛出 ConcurrentModificationException 异常。
Hashtable 的迭代器不是快速失败的。由于所有方法都是同步的,因此在迭代时不会发生结构上的改变。
(同步:同步是一种控制多线程并发访问共享资源的机制,确保多线程情况下的读写操作不会导致数据不一致问题。)数据不一致:(多线程并发,读后写问题、写后写问题。)数据不完整:没写完就读,同时写问题。
++++=========================================================
多线程篇
进程和线程的区别
1.进程是系统进行资源分配和调度的基本单位。线程是cpu执行的基本单位。一个进程包含多个线程,每个线程执行不同的任务。
2.不同进程相互隔离,使用不同的内存空间,同一进程中的线程资源共享。
3.线程上下文切换成本较低。(上下文切换指的是线程切换)
并行并发的区别:
单核情况下只有并发,指的是不同线程轮流使用cpu时间片,微观上其实是串行。
多核时,每个核都可以同时运行多个线程,这时线程是并行的。
创建线程的方式
1.继承Thread类,重写run方法。.start()启动
2.继承runnable接口,重写run方法。.start()启动
3.继承Callable接口,重写call方法,start()启动,具有返回值,能抛出异常。
4.线程池创建线程。继承runnable接口,重写run方法, 创建线程池ExecutorService(伊克斯Q特 色维斯)。提交就可以执行。
线程运行.run()方法和.start()方法的区别?
.start是启动线程,完成run方法中的代码逻辑。
.run()方法调用普通方法,可以被调用多次。
线程包含哪些状态?状态之间的切换?
新建、就绪、运行、阻塞、睡眠、等待、死亡。
新建:线程新建,但并未运行。
可执行:分为就绪态和运行态。就绪态分得cpu时间片变为运行态。
阻塞:无法获得锁变为堵塞态,获得锁后变为就绪态,再进入运行态。
等待:wait()进行等待,其他线程调用notify()方法会将其唤醒,进入就绪态。
定时等待:sleep(500)。进入就绪态。
死亡:执行结束,变成垃圾。
新建、可运行、阻塞、等待、定时等待、终止。
如何保证线程的顺序执行?thread1.join()
thread1.join()方法。当前线程b要等到线程a执行完成再执行,则b可以调用a.join(),等待a线程执行完毕再执行。
notify和notifyAll()
notify随机唤醒一个,notifyAll唤醒所有wait线程。
synchronized (lock) { // 一些代码 lock.notifyAll(); // 唤醒所有等待的线程 // 更多代码 }
wait和sleep方法有什么不同?
1.wait()是Object类的方法,sleep()是thread类的静态方法。
2.sleep方法可以让当前线程休眠,可以在任何地方使用。wait()会让线程等待,并且释放锁,需要在synchronized中使用。而且wait()后需要使用notify()或者notifyAll()唤醒。
代码:
定义一个对象锁:
static final Object LOCK = new Object();
wait必须配合synchronized锁使用:
synchronized(LOCK){
LOCK.wait()
}
如何停止一个正在运行的线程?
(PS:主线程是指程序开始执行时创建的第一个线程,也称为"main"线程。所有的线程都是由主线程创建的。)
1.使用Thread.stop()方法终止。(不安全,作废)
2.通过循环设置退出标志。
3.threadTest.interrupt();目标线程正阻塞于 wait、sleep 等方法时,首先会清除目前线程的中断状态。
线程中的并发安全问题——————————————
synchroinzed底层原理
synchronized是对象锁,只有一个线程能持有对象锁。
底层是monitor机制实现的,monitor底层是c++实现的。线程获得锁需要使用对象锁关联monitor。
monitor内部有三个属性,分别是owner、entrylist、waitset。
- 当多个线程同时获取锁时,首先会进入到EntryList中,然后将owner字段设置为当前线程,失败则进入到EntryList中。
- 当获取锁的线程调用
wait()
方法,则会释放锁和owner,进入WaitSet中,等待被唤醒。 - 当前线程执行完成,释放锁和owner。
synchronized中的monitor属于重量级锁,了解过锁升级吗
monitor属于重量级锁,因为其是c++实现的,涉及到了用户态和内核态的转变,成本较高。
对象锁的对象头中的MarkWord会尝试与Monitor中的owner相关联。Monitor是操作系统的对象。
还有两种锁,轻量级锁和偏向锁。
轻量级锁、偏向锁
偏向锁是针对不存在竞争时,只有一个线程访问同步代码块时,偏向锁会记录该线程ID(通过CAS),如果这个线程后续再访问同步代码块,则不需要获得锁。
当存在竞争时,偏向锁会撤销,升级为轻量级锁。
轻量级锁使用CAS机制来争夺Mark Word标记。(两个线程)
当多个线程(两个以上)竞争锁时,升级为重量级锁。
Synchronized和Lock的区别
1.synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现;
Lock 是接口,源码由 jdk 提供,用 java 语言实现。
2.synchronized 时,自动释放, Lock 手动调用 unlock 方法释放锁。
3.Lock 提供了额外功能,例如获取 公平锁、可打断、可超时、多条件变量
公平锁:构造函数传入true;可打断:等待锁时可打断lockInterruptibly();可超时:tryLock(long time, TimeUnit unit)
方法,可以在一定时间内尝试获取锁,超过指定时间后获取失败。
4.在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能较好
在竞争激烈时,Lock 的实现通常会提供更好的性能。
自旋锁
线程获得锁失败进入阻塞,切换状态影像系统性能。
循环获锁避免状态切换,等待次数或者时间超时再进行切换。
谈谈JMM,即java内存模型
JMM,java内存模型定义了线程在共享内存中的读写规范。
JMM将内存分为两部分,一部分是共享内存(主内存),另一部分是线程的私有内存。线程和线程之间相互隔离,并且线程和线程之间的交互需要通过主内存。
JMM模型和Happen-Before原则,主要作用是:
1.保证线程之间的可见性。确保一个线程对共享变量的修改,对其他线程来说是可见的。
2.保证有序性。
3.避免指令重排序,通过volatile实现的。
CAS你知道吗
CAS,自旋锁,实现多线程同步的无锁机制。维护了内存位置、旧值、新值。仅当内存位置当前的值与旧值相匹配时,将内存位置的值更新为新值,否则不做任何操作。
不加锁的线程安全:
CAS自旋锁、乐观锁
讲讲对volatile的理解
挖了太奥。是个关键字,用于保证线程间的可见性和有序性,和禁止指令重排序。(也可以停止jvm自带的JIT(即时编译器)对代码进行的优化)
volatile
变量会强制线程从主内存中读取最新的值,而不是使用线程自己的缓存。从而保证线程可见性。
什么是AQS(state修改,线程排队)
AQS是抽象队列同步器
Lock锁=AQS+CAS
Lock锁底层使用AQS,1.使用变量state当做锁。strate为0为无锁,为1是加锁。修改state时使用CAS机制。2.维护了先进先出双向队列保证线程公平
RenntrantLock可重入锁 (锐恩蠢特),Lock接口的实现类
1.都是可重入锁,同一线程可调用多次lock,而不会堵塞。
2.底层使用AQS实现。
3.支持公平锁和非公平锁。(方法中的加锁,帮助线程加锁)
synchronized和Lock的区别:
语法层面:
synchronized是关键字,源码在jvm中,c++实现。锁自动释放。(关键字修饰代码块或方法)
Lock是接口,由jdk提供,java实现。子类ReentrantLock继承了它。锁使用unlock手动释放。
功能层面:
都属于悲观锁,支持锁重入。(锁重入代表支持同一线程多次获得锁,而不会被堵塞。)
Lock额外提供:公平锁、可打断、可超时(设置超时时间,不至于无限等待锁释放)、多条件变量(针对不同的等待条件创建不同的线程等待队列。)
(Lock用于可重入锁、读写锁,常用的是ReentrantLock)
底层实现不一样。Lock底层使用AQS,1.使用变量state当做锁。strate为0为无锁,为1是加锁。修改state时使用CAS机制。2.维护了先进先出双向队列保证线程公平。
Synchronized是对象锁,底层是monitor实现的,monitor是jvm级别的对象,底层是c++实现的。涉及到了用户态和内核态的转变,效率较低。monitor内部维护了:owner、entrylist、waitset。获得锁的线程关联owner。
死锁产生的条件
资源竞争。
互斥、请求保持、不剥夺、循环等待。
可以使用jdk自带的工具:JPS和Jstack。JPS输出JVM中的进程状态信息。Jstack通过日志查看线程内的堆栈信息。
导致并发程序出现问题的根本原因?怎么保证多线程的执行安全?
线程安全三大特性:原子性、可见性、有序性。保证线程安全,也就是要维持这三个特性。
针对原子性问题,可以使用Synchronized和Lock锁解决。
针对可见性,可以使用volatile、Synchronized和Lock锁解决。
针对有序性,可以使用volatile解决。主要针对指令重排序。
线程池是什么,说一下核心参数和原理 (Thread 死rai的)(Executor 伊克斯Q特)
线程池可以重复利用已创建的线程,来降低线程的创建、销毁开销。
线程池创建类:例如ThreadPoolExecutor。其中有个七个核心参数,包含:
1.核心线程数(主要执行任务的数量)
2.最大线程数(等于核心线程+救急线程)
3.过期时间
4.阻塞队列类型(LinkedBlockingQueue
、ArrayBlockingQueue
)
5.拒绝策略。所有线程都在工作,堵塞队列满,则会触发拒绝策略。
流程:出现新任务时,判断核心线程数是否已满,没满直接执行,满了堵塞队列。堵塞队列是否已满,没满不管,满了创建救急线程。线程数是否超过最大线程数,没超过创建救急线程,超过了则拒绝策略。
拒绝策略:1.直接抛出异常 2.主线程执行当前任务 3.丢弃堵塞队列最久的任务,执行当前任务。 4.丢弃任务
线程池种类
单例线程池
SingleThreadExecutor:最大线程数设置为1
适合需要顺序执行任务的场景。
固定线程池
FixedThreadPool:只有核心线程。
适应于任务量稳定的情况,避免过多线程数导致资源浪费。
缓存线程池
CachedThreadPool 只有救急线程
任务量不确定、执行任务时间较短,频次高(频次低了过期了)。
4.定时线程池
ScheduledThreadPool 指定核心线程数的定时线程池
适应于定时任务、周期性数据处理等任务。
常见的堵塞队列:
1.ArrayBlockingQueue 有界队列,数组,利用 ReentrantLock 实现线程安全,使用 Condition 来阻塞和唤醒线程。可公平。
2.LinkedBlockingQueue,默认误无界,链表,利用 ReentrantLock 实现线程安全,使用 Condition 来阻塞和唤醒线程。非公平。
3.SynchronousQueue 无容量队列,更多的是传递任务,不存储。
4.PriorityBlockingQueue 优先阻塞队列。自定义排序(不想先进先出)。无界队列
5.DelayQueue 延迟队列。过会取出
两把锁:入队锁出队锁
如何确定核心线程数:
一般根据I/O操作密集的场景,例如网络请求等,设置核心线程数为2N+1;
CPU密集型任务设置N+1。
为什么不用Executors创建线程
Executors返回的线程池中的FixedThreadPool和SingleThreadPool允许的请求队列长度为无界,可能导致大量请求堆积,导致OOM
CachedThreadPool缓存线程池允许创建的线程数量为无界,可能导致OOM。
线程池的使用场景
1.线程池+Future实现用户下单后的商品数据查询。数据汇总
例如:订单信息、包含的商品、物流信息三部分。如果是单线程,会依次查询所需要的信息,其延迟是叠加的。但如果提交(submit)到线程池中执行,三个任务可以并行执行,延迟取最高。
Future对象:该对象可以用于获取任务的执行结果或取消任务的执行。
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 提交任务给线程池,得到Future对象
Future<String> futureResult = executorService.submit(() -> {
// 模拟一个耗时任务
Thread.sleep(2000);
return "Task completed";
});
->{}箭头函数,又叫Lamada表达式,用于替代匿名内部类。匿名内部类适用于只需使用一次的情况,它可以使代码更紧凑,不需要专门为一个小的功能实现定义一个新的类。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class StringPrinterThreadPool {
public static void main(String[] args) {
// 创建一个具有固定线程数量的线程池
ExecutorService executor = Executors.newFixedThreadPool(3); // 例如,这里使用3个线程
// 提交任务到线程池
for (int i = 0; i < 5; i++) {
String message = "Message " + i;
executor.submit(new PrintTask(message));
}
// 关闭线程池
executor.shutdown();
}
static class PrintTask implements Runnable {
private final String message;
public PrintTask(String message) {
this.message = message;
}
@Override
public void run() {
// 循环打印字符串
for (int i = 0; i < 3; i++) { // 例如,这里打印3次
System.out.println(message);
try {
Thread.sleep(1000); // 模拟打印间隔
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
2.异步调用
例如保存历史搜索记录。当输入关键字进行搜索时,线程A负责搜寻数据。此时需要开辟一个额外的线程B来进行历史记录保存工作。
方法上使用鹅森克@Async(“线程池”) 来开启异步调用并指定线程池。表明该方法由额外的线程执行。
异步:一个任务的执行不会等待另一个任务的完成,而是立即执行其他操作。
如何控制某个方法允许并发访问的线程数量:
可以通过Semaphore(涩马蜂)信号量,来限JWT 啊实打实的阿萨德制线程数量。
Semaphore 类指定信号量数量, .acquire()减少 .release() 释放信号量
底层是AQS实现的。
对ThreadLocal的理解
ThreadLocal为每个线程都分配了一个独立的空间,创建自己独立的变量。这个区域在线程和线程间相互隔离。数据存储于ThreadLocal下的ThreadLocalMap中的Entry数组。这个Entry数组继承了WeakReference。其中有get和set方法,还有用于清除的remove。set是以ThreadLocal自己作为key,传入的值作为value。
(每个线程都有自己的堆栈,堆栈中存储了线程执行方法时的局部变量、操作数栈、方法出口等信息。)
ThreadLocal内存泄露问题:这个问题涉及到强引用、弱引用、软引用、虚引用。
强引用代表一个对象处于有用状态,不会被GC回收。
Use user = new User();
弱引用代表一个对象处于可能有用的状态。会被GC回收。
WeakReference weakReference = new WeakReference(user);
Entry数组继承了WeakReference。其中的key是弱引用的ThreadLocal实例,value是强引用。所以垃圾回收时可能造成内存泄露情况。解决方法就是必须使用remove清除存放的数据。
=================================================================
JVM篇
JVM是什么?
java代码编译成.class字节码文件,JVM就是字节码文件的运行环境。做到一次编写到处运行,自动内存管理,垃圾回收机制。由栈、堆、方法区(元空间)、本地方法栈、程序计数器组成。
解释器:解释字节码
JWT即时编译器:优化执行过程
元空间保存 编译后的代码、类信息、静态变量、常量、。java7中堆有方法区/永久代,java8后移动到了本地内存中,称为元空间。(因为其大小不可控,太大了浪费,太小了不够)
什么是程序计数器?
程序计数器线程私有,保存的是字节码的行号。当前执行的字节码指令的地址
介绍一下java堆
什么是虚拟机栈
每个线程运行时所需要的内存,称为虚拟机栈。方法的执行和结束代表着栈帧的出栈和入栈。每个线程只能有一个栈帧,对应着正在执行的方法。
栈内存默认大小为1024K,栈帧过大会导致线程数变少。
方法内的局部变量是线程安全的吗?
如果没有逃离方法的作用范围,则是线程安全的。如果局部变量引用了对象,脱离了方法作用范围,则需要考虑线程安全问题。
什么情况下导致栈内存溢出?
栈帧过多时。例如递归调用。
栈帧过大导致栈内存溢出。
栈和堆的区别?
栈存放局部变量和方法调用,而堆存储对象实例和数组。堆会被GC管理,栈不会。
栈线程私有,而堆是线程共享的。
解释一下方法区(Hospot)元空间
方法区主要存储 编译后的代码、类信息、静态变量、常量。
介绍一下运行时常量池
常量池:可以看做是一张表,记录着要执行的类名、方法名、参数类型、字面量(值)等信息。
运行时常量池:当类被加载,类的对应的常量池就会放入运行时常量池,并把符号地址变为真实地址。
你听过直接内存吗
直接内存不属于JVM内存结构,它是虚拟机的系统内存。常见于NIO操作时,使用直接内存进行数据读写。NIO使用ByteBuffer.allocateDirect进行读写。
NIO面向缓冲区,使用直接内存。
为什么好?磁盘文件读取需要加载到系统缓冲区,再拷贝到java缓冲区,java才能对其操作。直接内存可以代替这两个缓冲区,java代码可以直接访问。
什么是类加载器?类加载器有哪些?
类加载器的作用是将字节码文件加载到JVM中。
启动类加载器(加载java核心库,jre)
扩展类加载器(加载jre拓展,jre/lib/ext)
应用类加载器(加载开发者自己编写的java类)
自定义类加载器。
什么是双亲委派模式?
加载一个类,先委托上一级的加载器进行加载,如果上一级加载器也有上级,则继续委托上级。如果上级加载器没有加载,则由子加载器去加载该类。
1.采用双亲委派机制可以避免某个类被重复加载。
2.可以保证类库的API不会被修改。(自己写的与核心API库相同的类,无法使用)
说一下类加载的执行过程:
加载、验证、准备、解析、初始化、使用和卸载。
加载:当需要某个类时,通过类加载器加载该类的字节码文件到内存中。
验证:验证加载的类是否符合规范。
准备:为类中的静态变量分配内存,并将其初始化为默认值(0,null)。(静态常来那个在编译时就确认好了。)
解析:把类中的符号引用转变为直接引用。
初始化:对类的静态变量赋予初始值,执行静态代码块。
什么时候会触发类加载
创建类实例(new)、访问类中的静态变量、调用类中的静态方法、反射、子类初始化。
垃圾什么时候会被垃圾回收器回收?
首先垃圾回收指的是堆中的对象。但是堆内存是有限的,需要定时清理无用的对象。
而无用的对象,指的就是如果没有任何引用指向它,那么这个对象就是垃圾,可能被GC回收。
定位垃圾的方法:1.引用计数法(循环引用) 2.可达性分析(沿着GC Root对象为起点应用链进行扫描。).
能作为GC Root 的可以是:静态属性引用的对象、常量引用的对象、虚拟机栈中引用的对象。个人理解的是,堆空间之外的一般都可以作为GCRoot。
垃圾回收算法有哪些(找到了垃圾,怎么回收)
标记-清除算法
在原地释放内存空间,将其标记为可用空间。
缺点:操作简单,但是会产生内存碎片,长期运行影响CPU效率。
标记-整理
复制算法的改进,满了之后将存活的移到一端,清理掉另一端。
缺点:每次都要移动,移动效率低,但这样没有内存碎片。
复制算法
将内存分为两块,每次只使用一块。使用的满了之后将存活的移动到另一块,清除掉之前的那块。
缺点:没有内存碎片,移动效率高,但是内存空间减半。
分代收集算法-针对不同的代采用不同的回收算法。
年轻代:复制算法。存活对象较少,复制算法每次复制不会有太大负担。复制过程:将s0和s1作为两块内存,来回复制其中存活的和Eden中存活的。
老年代:标记整理算法。存活对象较多,减少内存碎片。
什么是Minor GC、Mixed GC、Full GC的区别是什么?
Minor GC 是发生在年轻代的垃圾回收。(有个暂停时间STW,暂停所有应用线程的时间)
Mixed GC 是年轻代+老年代部分区域的垃圾回收,是G1收集器特有的
Full GC是对年轻代+老年代完整的垃圾回收,暂停时间长,尽量避免。
有哪些垃圾回收器?
串行垃圾回收器 Serial
并行垃圾回收器
并发垃圾回收器 Parallel Old/New
G1垃圾回收器
串行垃圾收集器:Serial 作用于年轻代,复制算法。Serial Old 作用于老年代,标记整理算法。只有一个线程在工作。(色锐澳)
并行垃圾收集器:Parallel New 新生代,复制算法。Parallel Old,老年代,标记整理算法。多个线程工作,JDK8默认的回收器。(破锐澳)
CMS并发垃圾收集器:针对老年代的标记清除算法。特点是停顿时间短。过程分为:初始标记(阻塞)、并发标记、重新标记(阻塞)、并发清理。初始标记GC Root关联的,并发标记引用链上的,重新标记后来引用的。
G1垃圾回收器
1.应用于年轻代和老年代,JDK9之后默认使用G1。
2.划分为多个区域,每个区域都可以充当edan、survivor、old、还有专门为大对象准备的区域humongous(hiu芒果丝)。
3.采用复制算法。年轻代回收:年轻代触发阈值(5%-6%)后,会将所有的年轻代、幸存代进行垃圾回收,复制到新的幸存带当中。其中幸存带满足存活条件的,会进入老年代中。老年代回收:老年代触发(45%)阈值后,会并发标记,并进行混合收集(年轻、存活、老年),但并不是一次收集所有的老年代,这样耗时太长。为了更快的响应会设置停顿时间,优先回收存活少的老年代,兼顾响应时间与吞吐量。
4.并发失败:垃圾回收速度小于分配新对象的速度,触发FullGC。
强引用、弱引用、软引用、虚引用的区别:
与垃圾回收有关。
被强引用的对象不会被垃圾回收。
被弱引用的对象会被垃圾回收。 Weak Reference
被软引用的对象,当多次垃圾回收后内存依然不够时,会被垃圾回收。Soft Reference
被虚引用的对象回收时,虚引用会入队,用于回收后执行特定的操作。
JVM调优的参数在哪设置?
针对Tomcat的war包和 Spring的jar包,设置的地方是不一样的。
war包部署在tomcat中设置,修改TOMECAT_HOME/bin/catalina.sh
jar包,命令行启动spring项目的时候可以添加参数
JVM调优的参数有哪些?
(年轻代、老年代、元空间)堆空间大小、虚拟机栈设置、年轻代Eden区和Survivor区的大小比例、年轻代晋升老年代的阈值、垃圾回收器。
堆空间大小:-Xms -Xmx 一般设置为物理内存的1/4。堆太小会频繁导致垃圾回收。
虚拟机栈的设置:-Xss 每个线程默认大小为1M,但一般256k就够用了。
年轻代中eden和幸存区比例:默认8:1:1,
年轻代晋升老年代阈值:存活15次晋升老年代。
垃圾回收器: -XX
JVM调优工具
jps:进程状态信息
jstack:java进程内线程堆栈信息
可视化工具:jconsole和VisualVM。
CPU飚高排查方案:
Linux中可以使用Top命令查看cpu使用排行,查找占用较高的进程ID。
通过查看进程中的线程信息,查看占用过高的线程信息。
通过jstack查看进程id下的线程堆栈信息,查看对应线程的日志,找到导致cpu占用过高的代码行数。
进行修改。
————————————————————————
设计模式篇
如果代码在初期没有一个良好的代码结构,后期会造成代码难以阅、冗余度高、复用性低等问题。而设计模式是针对面向对象编程,所提出的代码规范。
七大原则
- 单一职责原则(职责应单一)
- 开闭原则 (类的改动应当扩展,而不是修改。编写父类,继承父类)
- 里氏替换原则(任何依赖的接口或抽象类,都能替换为其实现类)
- 依赖倒置原则(业务逻辑层应该依赖抽象接口,而不是具体实现类。保证业务逻辑层只需要修改传参即可。)
- 接口隔离原则(接口按照功能细分)
- 合成复用原则(修改父类会影响子类,那么他们应该组合使用,而不是继承)
- 迪米特法则 (类与类之降低耦合,降低依赖)
Java 中一般认为有23种设计模式,当然暂时不需要所有的都会,但是其中常见的几种设计模式应该去掌握。
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
单例模式
一个类只允许产生一个实例化对象。
分为懒汉式和饿汉式。
饿汉式:类初始化时就创建实例。私有构造方法+初始有+静态方法返回
懒汉式:第一次获取实例才会创建实例。私有构造方法+初始空+静态方法实例化后返回。
懒汉式:
public class Singleton{
private Singleton() {} 私有化构造方法
private static Singleton single=null; 空
//静态工厂方法
public static Singleton getInstance() { 调用静态方法,才实例化
if (single == null) {
single = new Singleton();
}
return single;
}
}
饿汉式 :
public class Singleton {
private Singleton() {}
private static final Singleton single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}
工厂设计模式
简单工厂模式、工厂方法模式、抽象工厂模式。主要解决七大原则中的开闭原则,减少耦合性。
简单工厂模式:
所有业务使用一个工厂,工厂根据接收到的传入参数,创建相应的业务对象。如果新增产品,则需要修改代码。
缺点:违反开闭原则。创建一个产品,就需要修改工厂。
工厂方法模式:
每个业务创建一个工厂,额外定义一个用于创建对象的抽象类或接口,让子类决定实例化哪一个类。 客户端通过接口的多态创建子工厂,由子工厂进行创建。
缺点:工厂太多,客户端需要知道所有的子工厂
抽象工厂方法模式:
针对多个维度的产品,例如产品家族、型号等。假设有商品品牌和种类。一个总的抽象工厂负责管理各自品牌的工厂。各自品牌的工厂负责管理旗下不同的产品。
策略模式:
封装了一系列算法,使其可以相互替换。
因为是继承关系,所以可直通过父类创建并返回子类实例。
优点:策略类之间可以自由切换。易于拓展。
登录案例:工厂模式+策略模式
新增功能时,将功能封装成策略并让Spring容器管理。工厂通过配置文件管理策略。service通过工厂来调用策略。这样,只需要增加策略,增加配置文件,即可实现开闭原则。
责任链设计模式
为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过所继承的父类的next值指向下一个处理者,从而连成一条链;当请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
客户只需要将请求发送到责任链上,无需关心请求的处理细节和请求传递过程。
优点:降低了对象之间的耦合度。增强了系统的可拓展性。简化了和对象之间的连接(每个对象只需保持一个指向其后继者的引用)。
责任链包含以下角色:客户类、抽象处理者、具体处理者。
例子:请假审批流程。3天找班主任,7天找系主任,30天找院长。
设计:一个领导类,里面包含next、请假方法。 班主任类、系主任类、院长类继承实现领导类,并指定next指向的领导。
模拟:首先组装责任链,然后传入进行执行。
观察者模式
定义了一种一对多的依赖关系,让多个观察者对象可以同时监听一个主题对象。主题对象状态变化时,可以通知所有观察者。
适配器模式:
将一个接口转换为客户端所期待的接口,从而使两个接口不兼容的类可以在一起工作。个人理解就是创建一个适配器类,封装期望的接口和实现类,通过接口可以调用实现类。
项目中使用的设计模式(Spring Boot中的设计模式)
Spring Boot 是一个基于 pring 架的快速开发框架,它使用了很多设计模式来实现其功能。其中常用的设计模式有:
1.工厂模式: Spring Boot 使用工厂模式来创建和管理 Bean 对象。
2.单例模式: Spring Boot 中的 Bean 默认是单例模式,保证了 Bean 的唯一性和共享性
3.代理模式: Spring Boot 中使用代理模式来实现 AOP,以实现对方法的拦截和增强等功能
4.观察者模式: Spring Boot 中的事件监听机制就是基于观察者模式实现的。@EventListener
5.模板方法模式: Spring Boot中的一些模板类,如JdbcTemplate、RedisTemplate 等,都使用了模板方法模式
6.适配器模式:Spring Boot 中的适配器模式来适配不同的数据库。针对不同的数据库操作规范,实现统一的接口。
装饰器模式和代理模式的区别:装饰器模式强调增强自身,代理模式强调的是创建代理对象,由代理对象控制原有对象。7.装饰器模式: 指在不改变现有对象结构的情况下,动态地给该对象增加一些功能。Spring Boot 中的过滤器链和拦截器链就是基于装饰器模式实现的。
常见技术场景
单点登录(SSO)怎么实现?
单点登录指的是,只需要登陆一次,就可以访问所有信任的应用系统。
单个Tomcat服务中session可以共享,但在微服务中不行。可以使用JWT。
权限认证怎么实现
后台管理系统更加注重权限的控制。最常见的是使用RBAC(基于角色的控制模型)模型来实现。
具体实现分为五张表或七章表。
五张表:(用户表、角色表、权限表、用户角色中间表、角色权限中间表)
七章表:(用户表、角色表、权限表、菜单表、用户角色中间表、角色权限中间表、权限菜单中间表)
上传数据的安全性怎么控制?
或者可以称为,网络通信中怎么保证安全性。
使用非对称加密或者对称加密,给前端一个公钥让其将数据加密传到后台,后台解密后再处理数据。
对称加密:文件加密和解密使用相同的密钥。
非对称加密有:公钥加密和私钥解密。例如ssh,RSA
MySQL语言篇
子查询:子查询只能出现在括号中,且只返回一个数据(可以是一列、一行)。
distinct 去重关键字。 SELECT DISTINCT name FROM....
IN 在里面:where id IN id2
WHERE ID NOT IN ...不在里面
SELECT DISTINCT cust_id_2 FROM orders WHERE order_value > 5000;
SELECT * FROM customers WHERE cust_id IN cust_id_2
SELECT * FROM customers
WHERE cust_id IN (SELECT DISTINCT cust_id FROM orders
WHERE order_value > 5000);
INSERT INTO premium_customers
SELECT * FROM customers
WHERE cust_id IN (SELECT DISTINCT cust_id FROM orders
WHERE order_value > 5000);
SQL AND & OR 运算符 - 菜鸟教程 (cainiaojc.com)
HTTP
超本文传输协议,是为了提供一个用于发布和接收HTML页面的协议。客户端通过URL访问网页内容。超文本包括文本、图片、视频、音频等等。 URL:协议-IP(域名)- 端口号-路径。如果是域名,则会使用DNS将其转换为IP地址。HTTP端口号80,HTTPS端口号443。 HTTP依赖TCP协议,所以其也是可靠的。UDP不可靠。 HTTP运行在经典的C/S架构上,即客户端服务端模式。硬件通过主机或服务器、软件通过Nginx或Apache。 连接过程:1.客户端通过URL与服务端建立TCP连接。TCP三次握手-客户端发送请求(我要连接),服务端发送请求(我在,你在吗),客户(我在,我要发了)。第四次握手断开连接。 2.客户端发送HTTP请求。请求有 GET、POST等请求。发送的是报文,有请求报文和应答报文。请求报文:请求方式、路径、请求内容、版本;请求头;。应答报文:状态码及解释、应答内容、版本;应答头;。状态码:200-299成功、300-399重定向、400-499客户端错误,例如404、500-599服务端错误。 3.服务器响应HTTP请求,客户端得到HTML代码。 4.客户端解析HTML代码,渲染成页面进行展示。
HTTPS:
解决了HTTP的安全问题。http是明文传输,端口号80,对于一些账号密码来说具有安全隐患。 HTTPS端口号443。
加密模型
对称加密和非对称加密。数据在发送时使用秘钥进行加密,接收时使用秘钥进行解密。秘钥相同则为对称加密,秘钥不同分为公钥和私钥,私钥进行解密。
数字证书
用于验证网站是否可靠。数字证书的作用是确保双方的安全连接。主要包含网站名称、有效期、公钥。如果通过证书验证则为安全连接,传输的内容不会泄露。
SSL/TLS
安全套接层,一种加密协议。通过加密数据保证数据的安全性。使用SSL时,客户端和服务端会进行一次握手,以确认加密算法并建立SSL连接。所以数据首先在SSL连接中进行加密,然后在TCP连接中传输。
HTTP长连接,短连接是什么
每次请求都建立一个连接,请求结束断开连接称为短连接。 打开一个页面建立一个连接,之后维持连接直到页面关闭,称为长连接。
HTTP是无状态协议,怎么保存状态?
通过Session。假设是短连接,每次短连接没法记录是哪个用户,可以通过Session记录用户信息。Session就是个记录用户的数据,可以存储在Redis或MySQL中。
HTTP 1.0和HTTP 1.1的主要区别是什么?
1.0默认短连接。1.1默认长连接
HTTP 和 HTTPS 的区别
HTTP端口号80,HTTPS端口号433
HTTP建立在TCP上,明文传输,不安全。HTTPS建立在SSL\TSL和TCP上,加密传输更安全。
OSI分层模型
应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
TCP是传输层协议,SSL位于传输层之上。
各层常见的协议:
应用层:HTTP、HTTPS、DNS、FTP、SMTP、。邮件传输协议有:SMTP、POP3、IMAP、MIME
表示层:JPEG
会话层:RPC
传输层:TCP、UDP
网络层:IPV4、IPV6 (IP)
数据链路层:PPP、ARP(地址解析IP->MAC)
物理层:IEEE 802.3
OOM排查及处理
out of memory 代表内存耗尽,即JVM没有足够的内存为对象分配空间,并且垃圾回收器没有足够的空间可回收。
分为内存泄露和内存溢出。内存泄露指的是申请的内存在使用完后没有释放,不断累积。内存溢出指的是申请的内存超过了JVM能提供的内存大小。
1.JAVA7中的永久代溢出。而1.8以后使用元空间代替了永久代,元空间位于本地内存,可以动态调整大小。
2.栈溢出StackOverflowError。栈帧设置过小、或者栈帧设置过大。通过-Xss调整栈帧大小。
3.堆内存溢出Java Head space。1.内存泄漏泄露问题 2堆内存设置不合理。
线上如遇到 JVM 内存溢出,可以分以下几步排查
-
jmap -heap
查看是否内存分配过小 -
jmap -histo
查看是否有明显的对象分配过多且没有释放情况 -
jmap -dump
导出 JVM 当前内存快照,使用 JDK 自带或 MAT 等工具分析快照
双重检测锁
在实现单例模式中,如果未考虑多线程的情况,可能出现多个实例。
双重检测锁是一种优化方式。1.将实例声明为volatile。2.检测实例是否为null,为null则进入森扩柰子锁,再次检测是否为null,进行实例化。
再次检测的目的是为了这种情况:1.多个线程同时通过了第一层判空 2.未获得锁之前有个线程完成了单例创建,此时通过第一层的会再次获得锁创建实例。
volatile是为了防止new时出现的指令重排序问题。new分为三个步骤:1.分配内存空间、2.初始化对象、3.将对象指向内存空间。可能发生重排序导致2.3交换,使得另外一个线程获得未初始化的对象并返回。
变量、常量、静态变量、静态常量
变量:1.存在于方法中的局部变量,只有调用方法时才会被创建并赋值,分配到栈内存当中。2.存在于类中的实例变量。在类实例化的时候被创建,位于堆内存当中,默认分配0或null。3.静态变量,属于类而不是对象,被所有类的实例共享,在类加载时被创建,位于方法区中。
常量池主要存储了符号引用和字面量。符号引用包括类的全限定名(包名类名)、描述符、字段(变量名)、描述符、方法名。
类加载的执行顺序(代码块、构造方法)
父类静态代码块-->子类静态代码块-->初始化父类成员变量和普通代码块-->父类构造方法-->初始化子类成员变量和普通代码块-->子类构造方法。
元空间、永久代、方法区
方法区是一种概念,例如1.7中Hotspot的永久代就是方法区的一种实现方式。而在1.8中元空间代替了之前的永久代,一是为了解决永久代中的OOM问题,二是为了版本兼容。元空间存在于本地内存当中,可以动态调整大小。元空间存放了即时类信息、编译后的代码、静态变量、常量。运行时常量池在元空间中。
字符串常量池在堆当中。
IP地址
32位,每位最多表示到255。255.255.255.255
A类:0.0.0.0~127.0.0.0
B类:128.0.0.1~191.255.0.0
C类:192.168.0.0~239.255.255.0
快速排序:
基于分治的思想。从数组中选择一个值划分为两个数组,一个数组的值都小于这个值,另一个数组的值都大于。然后将两个数组再次分治。
过程是low、high交换。
堆
堆是完全二叉树,即节点需要从左到右依次排,但不满。
最大堆:堆中的父结点大于 等于孩子结点。
最小堆:父节点小于等于孩子结点。
析构函数
构造函数在对象创建时自动调用,而析构函数是在对象销毁时自动调用。java有自动内存回收机制,无需程序员手动释放。
void finalize()
回溯法暴力解决
也可以解决最优路径问题、组合问题。
1.定义
01背包
1.确定dp数组下标含义。2.确定递推公式。3.dp数组初始化。4.确定遍历顺序。5.举例推导dp数组。
dp[][]数组大小:n个物品,target+1的背包容量。
双指针数组排序问题
StringBuffer
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
面试错题集锦
Linux权限:
指定三种权限:所有者、用户组、其他用户
rwx = 7,即读、写、执行。
rw- = 6,读、写。采用的是二进制 110 (8421)
ava线程之间可以通过什么方法实现通信
只能在同步方法或同步块中调用wait()方法。和wait()方法一样,notify()方法也要在同步块或同步方法中调用,即在调用前,线程也必须获得该对象的对象级别锁。执行notify方法之后,当前线程不会立即释放其拥有的该对象锁,而是执行完之后才会释放该对象锁,被通知的线程也不会立即获得对象锁,而是等待notify方法执行完之后,释放了该对象锁,才可以获得该对象锁。
wait()/notify()方法总结:
(1)wait()/notify()要集合synchronized关键字一起使用,因为他们都需要首先获取该对象的对象锁;
(2)wait方法是释放锁,notify方法是不释放锁的;
wait和sleep
sleep和wait的区别: 1、sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。 2、sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)。 3、它们都可以被interrupted方法中断。
interface只能用public或者默认
视图是什么
视图是一个表或多个表导出的虚表,数据库只存放其定义,所以虚表不存储数据,数据存在原来的表当中。对视图的更新,也会更新其对应的数据。
视图可以隐藏一些私密信息。
关键字:VIEW
CREATE VIEW view_student AS SELECT id, name, class FROM student;
bindService
是安卓的一个东西。
接口和抽象类的区别
接口只能默认和public,抽象类跟正常类一样,但是抽象方法只能是public或者protect
1.抽象类使用abstract,接口使用interface。
2.类和接口的区别。一个类只能继承一个父类,但可以implement多个接口。
3.抽象类可以有普通方法,但接口没有普通方法。抽象类可以有静态代码块,接口没有静态代码块。
4.接口中默认变量类型为public static final;且只能为public。 抽象类中任意。
5.都不能被实例化,只能被继承;含抽象方法的一定是抽象类,但抽象类不一定含抽象方法;抽象方法修饰符只能是public和protected。
6.子类继承抽象类必须实现其抽象方法,除非子类声明为抽象类。
Java文件读写发送
BufferedInputStream
字符流 Reader、Writer 文本等
字节流 InputStream/OutputStream 图片音频等。
FileInputStream从文件中读取字节流,FileOutputStream将字节流写入文件。
—————————2024.4.3———————————————————————
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root); // 插入队列
queue.size(); 队列长度
queue.isEmpty()判断是否为空
queue.poll();移除队头并返回
重写equals为什么也要重写hashcode
为了保证equals相等的对象,hashCode也相同。
先说一下object类,它有equals()、hashCode()、toString()、wait\notify\notifyAll等方法。
首先,equals:地址是否相同,相同为true; 地址不同值是否相同
hashCode:hashCode是否相同,相同则equals是否相同,相同为true。
但想要高效的进行查找,还是需要hash定位。
hashCode不同的对象一定不相等,但不同的对象hashCode可能相等。所以比较逻辑:如果hashCode相等,则使用equals比较两个对象值是否相等。
三点,equals、hashCode、地址。
CAS 比较和交换
一种保证数据一致性的无锁操作。维护了旧值、新值、内存位置。只有当修改时内存位置的值等于旧值时,才会进行修改。无锁操作减少加锁带来的资源消耗。
自旋锁
自旋锁的目的是不让线程频繁的进行状态切换。因为线程如果获得锁失败,应当进入阻塞状态。自旋锁可以保证线程处于就绪态。
通过循环不断地获取锁,比如设置超时时间的tryLock(),设置超时时间,超过时间再进入阻塞状态。
Synchronized和volatile和Lock修饰范围
synchronized修饰 方法、代码段。
volatile修饰变量。
Lock不修饰,就是在代码块中用
_____________________2024.4.15___________________________________________
String
str.lastIndexOf(' '); 返回最后一个指定字符的下标,没有则返回-1;
.indexOf;返回第一个索引
str.replaceAll(1,2);
str.toLowerCase();
s.replaceAll("[^0-9a-zA-Z】", "") // 正则表达式,【】包围,^取反
TCP三次握手建立连接
客户端:发送SYN、seq
服务端:发送SYN、seq、ACK
客户端:发送ACK
TCP四次挥手断开连接
因为是全双工的,所以需要 两次 结束-确认。一共四次。
客户端:发送FIN
服务端:发送ACK
服务端:发送FIN
客户端:发送ACK
C语言相关基础
int *p = &a 定义一个指针p指向a的地址。此时p=a的地址 星p等于a的值
int *p = a 编译错误
*p++ 不会+1,这里会先访问p的值,然后使得p+1。
Linux之间进程通信方式
1.管道 2.(命名管道)FIFO 3.消息队列 4.信号量 5.共享内存 6.socket
进程之间通信是因为进程之间独立的内存空间,互不干扰。进程间的通信目的是实现多个进程之间数据交换和共享。
1.管道。(匿名管道) 管道特点:单向半双工、字节流、同步阻塞。
可以简单的理解为缓冲区。用于有亲缘关系进程之间的通信,例如父进程和兄弟进程。单向传输。
在linux中,可以使用pipe()创建管道,使用write()、read()读写。C中可以将一个数据使用pipe()定义为管道。int a; pipe(a).
perror()函数会将错误信息格式化并输出到标准错误。
关闭管道:close(pipefd[0]); close(pipefd[1]);
过程:父进程使用pipe创建管道,然后使用fork创建子进程,子进程会继承父进程的文件句柄,所以可以通过管道进行通信。管道在linux内核中,有缓冲区。因为进程用户空间相互独立,但内核空间是公用的,所以要内核提供服务。
2.命名管道(FIFO)
这个管道有路径名,像是文件一样。
mkfifo(fifo_path, 0666); 指定路径和权限。权限对应:文件所有者、用户组、其他用户。
使用open()、read()、write()和close()等函数来进行。
过程:父进程使用mkfifo创建管道
3.消息队列
消息队列是存储于内核中的消息链表,进程可以通过操作系统提供的接口向队列中发送、接收消息。
特点:异步通信、不同类型的消息、消息可缓存、容量限制、持久化。
使用msgget()创建: int msgget(key, int) // 键值,可以理解为队列名。 权限
msgsnd()发送消息,msgrcv()接收消息,msgctl()函数删除消息队列。
4.信号量
Semaphore。用于进程间同步和互斥。
特点:计数器、原子操作、信号量为0会阻塞其他进程。
5.共享内存
多个进程访问同一内存区域,实现数据共享。性能更高。但是需要解决同步问题。
实现方式:不同进程的虚拟内存映射到相同的物理内存地址。通过shmget() // shared memory get
6.socket
不同计算机之间的进程进行通信。
特点:全双工、可靠
创建socker、bind()绑定ip和端口号、listen()监听、connect()连接、send、recv发送和接收。
Linux栈大小设置
pthread_attr_getstacksize
Linux查看磁盘空间大小
df // disk free
du // 当前目录 disk usage
TCP/IP 应用层、传输层、网络层、数据链路层
目前的建立连接基本都是基于TCP的。TCP传输控制协议。
应用层表示层会话层、传输层、网络层、数据链路层、物理层。
数据 TCP头部+数据 IP头部+TCP头部+数据 MAC头部+IP头部+TCP头部+数据+尾部
因为,数据需要根据 ip+MAC地址定位目标主机。
DNS和ARP
DNS是域名解析协议,根据域名找到对应的ip地址。
ARP是地址解析协议。根据ip可以定位到主机,但需要根据ARP定位MAC地址。
TCP如何保证可靠性
-
三次握手,四次挥手,保证连接和断开的可靠性。
-
有状态,会记录哪些数据发送了,哪些被接收了,哪些没有被接收,保证输出传输不会出差错。
-
报文检验、ACK应答、超时重传、流量控制、拥塞控制等。
超时重传
发送数据后,接收方会发送ACK确认报文。如果一定时间没有收到则触发超时重传。
多少时间合适?一个往返时间称为RTT。而超时重传时间RTO一般设置为略大于RTT。
怎么确定重传什么? TCP提供了SACK方法,带选择确认的重传。就是接收到的报文的序列号范围。
TCP滑动窗口
TCP发送一个数据需要确认才能发送下一个,效率低。滑动窗口是操作系统开辟的缓存空间,范围内可以允许发送方发送一定数量的数据而无需等待确认。
TCP头部有个16位窗口大小,可以告诉对方接收缓冲区还能容纳多少。
滑动窗口分为两种:发送窗口和接收窗口。
TCP流量控制
针对接收端(接收窗口)。发送端根据接收端的TCP头部的16位窗口大小判断窗口容量。
发送方发送速度,和接收方接收速度不匹配。发送过快,接收方可以存到缓冲区,但缓冲区满了,只能丢掉数据包。
TCP通过滑动窗口实现流量控制。根据TCP头部的16位窗口大小判断窗口容量。窗口容量为0则暂时停止传输,
————————————————————————————————————————
TCP拥塞控制
网络传输中网络有容量限制。拥塞控制防止网络中的流量超出网络容量,避免拥塞和数据丢失。
四个机制:慢启动、拥塞控制、快重传、快恢复
// 拥塞窗口大小为几,发送端就能一次发几个。
慢启动:慢启动阈值。连接建立初期,拥塞窗口为1并成倍增加。超过慢启动阈值则触发拥塞避免,拥塞窗口每次+1。如果出现超时重传,则判断产生拥塞,拥塞窗口为1,慢启动阈值减半。
但可能某个包丢失了而已,没必要再慢启动,可以进行快重传。
快重传:发送方发现某个报文没有按照序号发送,而是产生了中断,则会重复发送上一个报文的确认信息。接收方连续三次收到相同确认,则触发快重传,重新传递丢失的报文,发送方会发送当前的确认信息。(重复确认没有超过超时时间)快恢复:既然丢了包那可能也存在问题,但别直接慢启动了。会将拥塞窗口减半,进入拥塞避免。
IP相关知识=======================
数据链路层为数据增加以太网协议头,并进行CRC(差错检验)编码。 数据链路层把网络层报文封装成帧,加头部和尾部。
IP地址
网络层依据IP来标识不同的主机。IP有32为,分为网络位和主机位。
IP地址=网络地址+主机地址
子网掩码:前面全是1,后面全是0。全是1代表网络位,全是0代表主机位。
子网掩码:255.255.255.224->11111111.11111111.11111111.11100000 (255.255.255.224的二进制子网掩码)有5个0,则允许的最大主机数为2^5-2=30。减2是因为不包含网络地址和广播地址。
网络地址:IP地址网络位不变,主机位全部变为0。转换成十进制。
广播地址:IP地址网络位不变,主机位全为1对应的十进制。
ARP地址解析协议
根据IP查询MAC地址。如果查询自己的ARP高速缓存查询不到,则向网络发送一个ARP协议广播包,包含待查询的IP地址。收到广播包的所有主机都会查询自己的缓存,如果有则返回。
Ping
ping检验的是单位时间内有多少包被送达,可以大致判断网络状况。
TCP和UDP
TCP可靠,UDP不可靠。
TCP面向字节流,UDP面向报文。
TCP全双工,UDP一对一、一对多、多对一,多对多。
TCP流量控制通过滑动窗口。UDP无
TCP拥塞控制通过 慢启动、拥塞避免、快重传、快恢复。
HTML相关知识=========================
超文本传输协议。
HTTP和HTTPS区别
HTTP端口号80,HTTPS端口号443。
HTTP明文传输,HTTPS=HTTP+SSL/TLS,加密传输。
HTTP请求报文和应答报文:
请求报文包含:请求方式、请求路径、内容。
应答报文包含:状态码及解释、应答内容。
状态码:1xx:正在处理; 2xx成功; 3xx重定向; 4xx:客户端错误(404资源不存在); 5xx:服务端错误500
HTTP 1.0和HTTP 1.1的主要区别是什么?
1.0默认短连接。1.1默认长连接
HTTP是无状态协议,怎么保存状态?
通过Session。假设是短连接,每次短连接没法记录是哪个用户,可以通过Session记录用户信息。Session就是个记录用户的数据,可以存储在Redis或MySQL中。
数字证书
用于验证网站是否可靠。数字证书的作用是确保双方的安全连接。主要包含网站名称、有效期、公钥。如果通过证书验证则为安全连接,传输的内容不会泄露。
C++创建链表:
首先声明一个结构体,然后 Node *head = new Node; Head->next=NULL; Node *pre = head;
struct Node{
int val;
Node *next;
}
copy-on-write这个优化是干了什么,怎么实现的?(Linux文件管理系统也采用了写时复制策略)
COW写时复制。思想:多个调用者同时请求相同资源,则会获取相同的指针指向相同的资源。直到某个调用者想修改资源时,才会复制一份专用副本给调用者,而其他调用者所见到的最初的资源仍保持不变,这个过程是透明的。
优点:没有修改资源就不会创建副本,确保只读时可以共享一份资源。
例如Linux中的fork() 创建子进程。按理来说不同进程内存空间应当是私有的,所以创建子进程时,会将父进程中的内容复制到子进程中(包括进程代码、数据和堆栈)。但这样在只读时会占用过多内存。此时写时复制只会复制其页表,fork之后的父子进程的地址空间指向相同的内存。只有当某个进程想执行写操作时,才会创建一个新的副本,有新的页表。
数据:静态变量、全局变量。
页:内存基本单位,内存由页组成。每页有4kb、8kb、16kb等。通过页表来管理页。
页表:记录虚拟页面到物理页面的映射关系。
虚拟内存:虚拟内存使得每个程序都认为自己拥有连续可用的内存,实际上被分隔成多个物理内存碎片,并且还有部分暂存在磁盘当中,需要时进行数据交换。
说一说大小端
对于2字节16位的整数,在内存中存储有两种方法:
1.低序字节存储在起始位置,称为小端。
2.高序字节存储在起始位置,称为大端。
为什么?硬件商设计硬件时的不同标准。
typedef有啥用,他和define有什么区别?
作用都是为一个对象起别名。
#define 是C中定义的语法,在预处理时不做正确性检查,只进行简单的字符替换。还可以定义常量等。
typedef是关键字,编译时检查。
进程切换时间片,会进入就绪状态。
io多路复用讲一下,除了epoll还有哪些,有什么区别?
多路指的是有多个Socket网络连接;复用指的是用一个线程检查多个Socket的状态(遍历文件描述符fd)。
阻塞非阻塞:请求资源时是否直接返回。
同步异步:同步主动询问,异步系统处理完直接通知。
三种:select、poll、epoll。
底层实现:select底层是数组、poll底层是链表、epoll底层是红黑树。
——————————————————————通知方式不同:select和poll遍历fd文件描述符,复杂度为On。epoll当fd就绪时会触发回调函数,将fd放到readyList中,时间复杂度为O1。
QT是C++图形用户界面应用程序。
int a:3是什么意思?
指的是定义一个int类型的变量,并且占3个bit位(二进制位)。
SQL去除重复数据
-
DINSTINCT去重然后开新表存储:
CREATE TABLE new_table_name AS SELECT DISTINCT * FROM old_table_name;
DROP TABLE old_table_name;
-
GROUP BY name HAVING count(name)>1
多线程-在工具类里
主函数中使用Thread thread = new Thread(new ClassName, "Thread_Name");
那么这里需要编写类的函数方法,并且重写public void run()就可以了。
快排-在工具类里。
—————————2024.4.17———————————————————
多线程-IO密集型任务和CPU密集型任务
针对不同类型的任务,应当动态的确定正确的线程数量来最大化程序的运行速度。最终目标应当是充分利用CPU的资源。
CPU密集型任务:I/O请求可以快速的完成,但CPU还有很多运算需要处理。
线程数量一般设置为:线程数量=CPU核数+1
《Java并发编程实战》中的一句话:计算密(CPU)集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。
个人理解:替补。不出错的话等于CPU核数就好了,但可能某个线程由于某些原因暂停,空出的CPU核数没有其他线程进行接替,造成资源浪费。
IO密集型任务:CPU运算操作完成后,还有很多I/O操作要进行。但是I/O的过程CPU是空闲状态,所以可能造成CPU资源的浪费。
设置为:最佳线程数 = (1/CPU利用率) = 1 + (I/O耗时/CPU耗时)
因为如果I/O耗时越多,CPU空闲的概率也就越大。所以线程数量应当与I/O时间增长成正比。
MySQL表连接有几种
6种。内连接、左连接、右连接、全连接、交叉连接、联合查询。
1.内连接:返回两个表中共有的行 不共有删除
SELECT * FROM table1 INNER JOIN table2 ON table1.name=table2.name
2.左连接:左表全部都会保留,不匹配的设置为NULL
SELECT * FROM table1 LEFT JOIN table2 ON table1.name=table2.name
3.右连接:右表全部都会保留,不匹配的设置为NULL
SELECT * FROM table1 RIGHT JOIN table2 ON table1.name=table2.name
4.全连接:返回左表和右表的所有值。将字段值相等的行合并成一行。笛卡尔积。
SELECT column_name(s) FROM table1 FULL JOIN table2 ON table1.column_name = table2.column_name;
5.交叉连接:CROSS JOIN 返回笛卡尔积的所有组合。
SELECT column_name(s) FROM table1 CROSS JOIN table2;
6.联合查询。两个或者多个表数据合并成一个结果集,不包含重复行,要求列数和数据类型一致。
SELECT column_name(s) FROM table1 UNION SELECT column_name(s) FROM table2;
事务的并发?事务隔离级别,每个级别会引发什么问题,MySQL默认是哪个级别?
读未提交、读已提交、可重复读、串行化。脏读、不可重复读、幻读。默认是可重复读。
MySQL常见的三种存储引擎(InnoDB、MyISAM、MEMORY)的区别?
InnoDB:支持事务、支持行锁表锁、支持外键约束、B+树索引
MyISAM:不支持事务、不支持外键、只支持表锁
Memory:内存存储作为缓存,不存储数据。
什么是临时表,临时表什么时候删除
临时表关键字:temporary
CREATE TEMPORARY TABLE tmp_table (
NAME VARCHAR (10) NOT NULL,
time date NOT NULL
);
select * from tmp_table;
临时表只在当前连接可见,关闭连接后会自动删除表并释放所有空间。
视图为CREATE TABLE
临时表和视图的区别
多次查询影响效率,建立临时表。
临时表是在数据库中临时创建的表,结束于当前会话结束时。临时表可以增删改查,但不会影响原来的数据。存储在tempdb中,当不再使用时会删除。
隐私查询想返回部分数据使用视图。
视图建立在基本表之上,是为了满足查询要求而建立的一个对象。可以不给用户接触表知道表结构的机会。
临时表的目的是存储中间结果集,例如多表查询、聚合查询等等,提高查询速度。
视图更多的想保护用户隐私,过滤不能展示的数据。
MySQL B+树索引和哈希索引
-
Hash索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位;
-
B+树索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问;
Hash索引:
就是通过hash值直接映射到所需要的数据。基本复杂度O1。但是对于存在大量Hash冲突而形成的链表,会降低Hash索引的查询速度。
无序查询。对于范围查询(><)来说,Hash索引无法起作用。
无法排序。因为映射后的哈希值没有大小可言。
无法模糊查询。很好理解,需要使用完整的key来进行映射。
不支持联合索引。
Float有正0和负0之分。
数据页是MySQL存储的基本单位
MySQL中所有数据都放在表空间当中,在表空间中又分为段、区、页、行(偏移量)
页是InnoDB存储数据的基本单位,也是I/O交换的基本单位。
页当中的数据是单向链表,页之间使用双向链表连接。页内维护了最小记录和最大记录,方便快速换页查找。
开发中常用的设计模式及思路
单 工 代 委 策 观 模
代理模式:**适用于想拓展某个对象方法的时候。** 一个对象拓展
定义一个接口,接口包含方法。然后定义实现接口的实现类A。这个类想实现一个固定的方法。
需求:在此方法上添加额外的功能,为其进行拓展。
创建一个额外的类B,接收类A的对象传参。调用B类,即可完成A类的功能,并且附带B类的附加逻辑。
工厂模式:**适用于多个相同类型的产品,需要返回不同对象的时候。**多个取一个对象
共同方法创建接口,不同子类实现接口。由工厂类接收到的不同参数,返回不同的子类实例。
但是增加一个作业就得增加一个实体类,并且编写工厂。
工厂方法:上面违反了开闭原则,维护起来冗余。那么就创建多个工厂,一个总工厂和多个对应的分工厂。
单例模式:返回一个本身的单例。一个对象提供方法
委派模式:委派任务选定多个方法中的一个进行执行。一对多。
发布任务,由Leader指派实现任务的具体对象。
策略模式:不同的折扣需要写不同的if-else,繁琐。定义了一系列的算法,并且使他们可以相互替换。对象替换,进行不同工作。
观察者模式:发布订阅模式,一对多模式。一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
在抽象类里有一个 ArrayList 存放观察者们。并且有值记录状态。
拍卖时,拍卖师监控价格,通知给其他竞价者。
模板模式:定义一个骨架,子类重写其存在方法,拓展自己的方法。例如MyBatis中的创建session,关闭session等。
一条SQL语句的执行过程
Java系统中会使用MySQL驱动与数据库连接,一般是JDBC。一个请求会建立一个连接,多个请求会建立多个。但是频繁的建立连接和释放会造成性能下降,可以使用连接池(DBCP、C3P0、德鲁伊)维护一定数量的连接,称为数据库连接池。解析器-优化器-执行器执行
java中编写的SQL会由 查询解析器 翻译成SQL认识的语言。
查询优化器会优化查询过程。对于存在多个索引的数据表,会依照最小成本原则选择对应的索引。最小成本原则包括I/O成本和CPU成本。最小I/O成本指的是将磁盘中的数据页加载到内存的成本,因为数据页是MySQL存储的基本单位,每次加载都得加载一页,所以最小I/O成本与页大小有关。CPU成本是将数据读入内存后,要将数据页数据检测是否满足筛选条件和排序等CPU操作。优化器选出最优索引,会调用存储引擎接口,开始执行解析和优化过的SQL语句。
查询优化器会调用存储引擎接口执行SQL,真正执行SQL的是存储引擎。
执行器会调用存储引擎接口执行SQL。
UNDOLOG、REDOLOG、BIN LOG
undo log日志记录数据被修改前的值,便于事务回滚。
redo log日志记录数据更改后的值,无论是否提交事务(分为预提交和最终提交),是InnoDB级别的。值会先存储在InnoDB的内存中,有个单独的缓冲区,等待时机持久化到磁盘中。
bin log是MySQL级别的日志,记录的是偏向于逻辑方面,对数据库有变更的操作都会记录到binlog里面来,通过binlog来归档、验证、恢复、同步数据。
Spring Bean的生命周期
1.通过BeanDefinition获取bean的定义信息。BeanDefinition是一个接口,可以获得bean的属性信息如 类名、属性、构造函数等。
2.实例化Bean。
3.Bean的依赖注入。注入其依赖的其他类的引用、属性注入。
4.处理各种Aware接口(BeanNameAware获得自己在容器中的名字、BeanFactoryAware自己所在的工厂)因为Bean是由容器管理的,Aware接口就是向容器索要自己的信息。
5.Bean的前置处理器。实现了BeanPostProcessor接口的类,接口中的前置方法和后置方法,用于对Bean进行个性化定制。
6.Bean的初始化方法。(初始化方法中可以自定义一些逻辑,比如资源加载(数据库连接、打开文件等))
7.Bean的后置处理器。实现了BeanPostProcessor接口的类,接口中的前置方法和后置方法,用于对Bean进行定制。
8.销毁Bean
不安全的HTTP方法
根据HTTP标准,HTTP请求可以使用多种方法,其功能描述如下所示。
HTTP1.0定义了三种请求方法: GET、POST、HEAD
HTTP1.1新增了五种请求方法:OPTIONS、PUT、DELETE、TRACE 、CONNECT
不安全的方法:PUT、DELETE、trace、PATCH、OPTIONS。
put、delete不带验证机制。options会造成服务器信息暴露。delete可以删除服务器上特定资源。
怎样实现Arraylist线程安全
Vector、Collections.synchronizedList、CopyOnWriteArrayList(volatile、RenntrantLock)
频繁的Minor GC
可能运行时会创建过多重复且一次性的对象,可以考虑设置为静态。
年轻代空间过小。
引用链较长,可达性分析较长。
x++、++x、&
x值都会+1。 &会判断两个是否为true,&&会依次判断。
基本数据类型字节数
平台/类型 | char | short | int | long | long long |
---|---|---|---|---|---|
16位 | 1 | 2 | 2 | 4 | 8 |
32位 | 1 | 2 | 4 | 4 | 8 |
64位 | 1 | 2 | 4 | 8 | 8 |
float 4 doubel 8
线程池的类继承
图片来自https://blog.csdn.net/zhao_miao/article/details/88072721
MyBatis标签
insert \ delete \ update \ select
resultMap 将数据库表中的列与java对象中的属性对应起来。
if \ foreach \ where \ set
这些关键字直接写也行,但有时候会有最后一个if不执行缺少逗号的情况。所以使用标签可以自动补充。and或逗号
id一般映射为方法名称,resultType为接收的返回类型,要写入类的全名称。
抽象类和接口的区别
使用abstract声明抽象类。抽象类除了不能实例化对象之外,其他的功能仍然存在。抽象方法只能是public或者protect
接口interface:没有普通方法,声明只能是默认和public。变量方法只能是public。
常见的面试题mybatis、JDBC、Hibernate
名称 | 使用性能 | 编写效率 |
---|---|---|
JDBC | 高 | 低 |
Hibernate | 低(SQL优化比较困难) | 高(不需要使用SQL,自动化的持久层框架) |
Mybatis | 中(封装少,映射多样化,支持存储过程) | 中(半自动化的持久层框架) |
Mybatis更利于SQL优化,编写SQL更加灵活。
Hibernate SQL编写更加友好,自动化的持久层框架。
JDBC灵活度更高,但是编写效率低。
JDBC:连接数据库,注册驱动和数据库信息。操作Connection打开Statement对象执行SQL将结果返回到ResultSet对象。将结果转化为具体的Java对象。
Hibernate:建立对象和数据表之间的联系,可以将对数据库的操作转化为对对象的操作,自动化程度高。
通过@Entry注解声明持久化类,使用@Table指定映射表。通过session.save().session.delete()操作数据库。
Mybatis:配置MyBatisConfig.xml文件中的数据库。操作数据库可以用过xml文件和注解来实现。select、insert、update、delete。指定接收数据的实体类。SQLSessionFactory \ SQLSession \ Executor。提供了二级缓存,SQLSession级别和namespace级别。