一、mybatis:
1、当实体类中的属性名和表中的字段名不一样 ,怎么办
1、使用as关键字起别名
<sql id="Base_Column_List">
<!--数据库种表的字段 as 实体类属性-->
uid as id, userName as name, age as age,
email as email, create_time as createTime,
update_time as updateTime, is_deleted as deleted
</sql>
2、使用resultMap标签映射:column的值为数据库中的字段名,property的值为实体类中的属性名
<!-- type:指定需要映射的实体类 -->
<resultMap id="userMap" type="User">
<!-- column:数据库表中的字段名 -->
<!-- property:实体类中的属性 -->
<id column="uid" property="id" />
<result column="userName" property="name" />
<result column="age" property="age" />
<result column="email" property="email" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
<result column="is_deleted" property="deleted" />
</resultMap>
<select id="getUserList" resultMap="userMap">
select uid,userName,age,email,create_time,update_time,is_deleted from t_user;
</select>
2、#{}和${}的区别是什么
#{}是预编译参数占位符,可以防止sql注入。
${}是字符串替换占位符,无法防止sql注入。
sql注入:sql注入是指攻击者向数据库中插入恶意的sql语句,从而来获取或篡改数据库中的数据。
3、Mybatis 分页插件的原理是什么
MyBatis 分页插件的原理是通过拦截 SQL 执行过程,在执行前和执行后对 SQL 进行改写,添加分页相关的语句,实现数据库分页查询功能。
具体原理包括:
1、拦截器:MyBatis 分页插件通过拦截器拦截 Executor 的 query 方法,在执行 SQL 之前和之后进行处理。
2、SQL 改写:在执行前,插件会将原始 SQL 语句改写成分页查询语句,通常是在原始 SQL 后面添加 LIMIT 或 OFFSET 等关键字,限制返回结果的数量。
3、参数处理:插件还会处理分页相关的参数,如页码和每页数据量,以便构建正确的分页查询语句。
4、结果处理:在执行后,插件会对查询结果进行处理,将结果封装成分页对象,包括总记录数、当前页码、每页数据量等信息。
通过以上步骤,MyBatis 分页插件实现了对 SQL 查询结果的分页处理,使得开发者可以方便地进行数据库分页查询。
4、一对一、一对多的关联查询
1、一对一使用association标签
2、一对多使用collection标签
案例:一个商品对应一个价格,一个价格对应多个商品
实体类:
public class Product {
private Long id;
private String name;
private Price price;
// Getters and setters
}
public class Price {
private Long id;
private Double value;
private List<Product> products;
// Getters and setters
}
xml:
<select id="selectProductWithPrice" resultMap="productWithPrice">
SELECT p.id AS product_id, p.name, pr.id AS price_id, pr.value
FROM product p
LEFT JOIN price pr ON p.id = pr.product_id
</select>
<resultMap id="productWithPrice" type="Product">
<id property="id" column="product_id"/>
<result property="name" column="name"/>
<association property="price" javaType="Price">
<id property="id" column="price_id"/>
<result property="value" column="value"/>
</association>
</resultMap>
<select id="selectPriceWithProducts" resultMap="priceWithProducts">
SELECT pr.id AS price_id, pr.value, p.id AS product_id, p.name
FROM price pr
LEFT JOIN product p ON pr.product_id = p.id
WHERE pr.value > 100;
</select>
<resultMap id="priceWithProducts" type="Price">
<id property="id" column="price_id"/>
<result property="value" column="value"/>
<collection property="products" ofType="Product">
<id property="id" column="product_id"/>
<result property="name" column="name"/>
</collection>
</resultMap>
5、Mybatis 的一级、二级缓存
一级缓存是 MyBatis 默认开启的,作用于同一个 SQLSession 中,生命周期较短。当 SQLSession 关闭时,一级缓存就会被清空。
二级缓存须在 Mapper.xml 中配置,作用于不同的 SQLSession 中,生命周期较长。二级缓存在整个应用程序的生命周期内有效,当应用程序关闭时,二级缓存也会被清空。
总之,一级缓存和二级缓存都旨在提高数据访问的性能并减轻数据库压力,对于相同的查询都是从缓存中获取的。
6、如何获取自动生成的(主)键值
1、在标签中添加一个属性useGeneratedKeys="true"即可。(该属性只能放在insert标签中)
案例:
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, password) VALUES (#{username}, #{password})
</insert>
2、使用selectKey标签也行,它可以在执行插入语句数据库之前,通过一个查询来获取主键值。(该标签只能放在insert标签中)
案例:
<insert id="insertUser" parameterType="User">
<selectKey keyProperty="id" resultType="java.lang.Long" order="AFTER" >
SELECT LAST_INSERT_ID() AS id
</selectKey>
INSERT INTO users (username, password) VALUES (#{username}, #{password})
</insert>
二、redis:
1、谈谈你对redis的理解
1、对redis的理解:数据存储在内存中,读取速度快,是一种非关系型数据库。
2、数据结构和用途:Redis支持多种数据结构,如string、hash、list、set、zset,适用于缓存、消息队列和会话存储等场景。
3、分布式特性:Redis支持主从复制和集群模式,实现高可用性和横向扩展。
2、Redis 的数据类型
1、字符串 (String):
简述: 存储字符串、整数或浮点数等基本数据类型。
应用场景: 缓存、计数器、计时器等。
2、哈希 (Hash):
简述: 存储对象的属性和对应值的键值对集合。
应用场景: 存储用户信息、对象属性等。
3、列表 (List):
简述: 按插入顺序排序的字符串元素集合,支持头部和尾部的增删改操作。
应用场景: 消息队列、任务队列等。
4、集合 (Set):
简述: 无序的字符串集合,不允许重复元素。
应用场景: 标签系统、好友关系等。
5、有序集合 (Sorted Set):
简述: 有序集合,元素按分数升序排列,每个元素都有一个分数。
应用场景: 排行榜、范围查询等需要按分数排序的场景。
3、Redis 是单进程单线程的吗
是的。
4、Redis 的淘汰策略
1、LRU(Least Recently Used):淘汰最近最少使用的数据。
2、LFU(Least Frequently Used):淘汰最少频繁使用的数据。
3、TTL(Time To Live):自动淘汰过期的数据。
4、Random(随机淘汰):随机选择一个数据进行淘汰。
5、No Eviction(无淘汰):当内存达到最大限制时,拒绝写入新的数据,而不进行淘汰。
5、redis 过期键的删除策略
Redis 中过期键的删除策略包括定期删除和惰性删除。
定期删除:Redis 默认采用的是定期删除策略。在设置键的过期时间时,同时创建一个定时器,在键的过期时间到达时,定时器会检查该键是否过期,如果过期则删除。定期删除可能会带来性能问题,特别是当大量键在短时间内过期时,可能导致性能下降。
惰性删除:在每次获取键时,Redis 会先检查键是否过期,如果过期则删除。这种策略避免了定期删除可能带来的性能问题,但是可能会造成过期键一直占用内存空间,直到被访问时才删除,增加了内存使用。
定期删除和惰性删除可以结合使用,定期删除主要用于辅助惰性删除,确保内存占用在可控范围内。
6、怎么理解 Redis 事务
Redis 事务是一种将多个命令打包成一个操作单元,然后一次性、顺序执行的机制。在 Redis 中,事务是通过 MULTI、EXEC、DISCARD 和 WATCH 等命令来实现的。
1、MULTI: 开启一个事务,标志着事务的开始。
2、EXEC: 执行事务中的所有命令。
3、DISCARD: 取消事务,放弃执行事务中的所有命令。
4、WATCH: 监视一个或多个键,当这些键被其他客户端修改时,事务将被打断。
在 Redis 事务中,命令序列会被一次性发送到服务器执行,直到 EXEC 命令被调用为止。事务中的命令要么全部执行成功,要么全部执行失败,不存在部分执行的情况。这种原子性保证了事务的一致性。
通过使用 Redis 事务,可以将多个命令打包成一个原子操作单元,保证了数据的一致性,同时减少了网络通信的开销,提高了性能。
7、redis雪崩,击穿,穿透,以及在项目中如何解决的
在 Redis 中,存在三种常见的问题,即雪崩、击穿和穿透,它们分别是:
1、雪崩:指在缓存中大量的数据同时过期失效,导致大量的请求直接访问数据库,造成数据库负载剧增,甚至宕机的情况。这种现象类似于雪崩一样,一旦发生,会导致系统的崩溃。
2、击穿:指查询一个不存在的键,导致请求直接访问数据库,由于缓存中不存在该键,所以会直接请求数据库,如果并发量较大,会导致数据库负载过大。
3、穿透:指查询一个不存在的数据,而且查询的条件恒为真,导致请求直接访问数据库,如果请求量很大,会对数据库造成很大的压力。
针对这些问题,可以采取以下解决方案:
1、对于雪崩问题,可以采取多种手段,如设置不同的过期时间、使用缓存预热、实现缓存数据的永不过期策略等,以减轻缓存数据同时失效的情况。
2、对于击穿问题,可以使用互斥锁或分布式锁等机制,防止大量请求同时查询数据库,同时可以考虑使用哨兵节点等方式提高 Redis 的高可用性。
3、对于穿透问题,可以在缓存中设置空值或默认值,并且可以使用布隆过滤器等技术拦截恶意请求,减少对数据库的直接访问。
综上所述,针对不同的问题,可以采取不同的策略和技术手段来解决,在项目中需要根据具体情况综合考虑采取适当的措施来保障系统的稳定性和性能。
8、redis在你项目中的应用场景
缓存token、缓存树状图、获取系统最大限制长度(例如excel的导入和导出最大长度)。
9、谈谈redis的哨兵机制
作用:Redis的哨兵机制监控主库是否故障,一旦主库宕机,哨兵会自动将一个从库切换成主库,无需人工干预,确保服务的连续性和可用性。这种自动化的故障转移减少了人工干预的需要,提高了系统的稳定性和可靠性。
原理:哨兵通过发送命令,等待redis响应,从而监控多个运行的redis实例。
10、redis持久化机制,RDB和AOF
RDB(Redis DataBase)和 AOF(Append Only File)是Redis提供的两种持久化机制。
RDB持久化是一种快照的方式,定期将内存中的数据集快照写入磁盘。它的优点是生成的快照文件相对较小,恢复速度快,适合用于备份数据或者灾难恢复。缺点是可能会在发生故障时丢失最后一次持久化之后的数据。
AOF持久化是一种追加写入日志文件的方式,记录了服务器执行的所有写操作命令,以此来记录数据变更。AOF文件在Redis重启时会根据文件内容重新执行命令来恢复数据。它的优点是能够保证较高的数据完整性和持久性,可以根据需要选择不同的同步策略。缺点是相比RDB,AOF文件通常较大,恢复速度相对较慢,且可能会有部分数据丢失。
11、谈谈redis集群
1、主从复制模式:
在主从复制模式中,主库负责处理客户端的读写请求,从库提供读操作的服务。当主库宕机后,需要手动将某个从库转换成新的主库,这个过程需要人工干预。
2、主从哨兵模式:
在主从哨兵模式中,除了主库和从库外,还有一个或多个哨兵节点负责监控主库的运行状态。当主库宕机后,哨兵会根据投票的方式自动将某个从库转换成新的主库,无需人工干预。
3、Cluster集群模式:
Redis Cluster是redis提供的一种分布式集群模式,该模式将数据分散存储在多个节点上,提供了高性能,高可用性和可伸缩性的数据存储解决方案。
三、mysql:
1、MySQL事务
1、原子性(Atomicity):指事务中的操作要么全部成功,要么全部失败。不存在部分操作成功而其他操作失败的情况。
2、一致性(Consistency):指事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。即事务开始前和结束后,数据库的完整性约束没有被破坏。
3、隔离性(Isolation):指多个事务并发执行时,每个事务都相互隔离,彼此不会互相干扰。不同的隔离级别(读未提交、读已提交、可重复读、可串行化)决定了事务之间的隔离程度。
4、持久性(Durability):指一旦事务提交,对数据库的修改就会永久保存在数据库中,即使数据库发生故障也不会丢失提交的数据。持久性通过将事务的日志保存在磁盘上来实现。
对于MySQL,可以使用BEGIN、COMMIT和ROLLBACK语句来控制事务的开始、提交和回滚。
2、MySQL索引,索引失效场景
索引:
1、普通索引:最基本的索引类型,用于提高数据的查询速度。
2、主键索引:同一张表中只能有一个,值不能为NULL,创建主键时会自动创建,主要用于保证每一行数据的唯一性。
3、唯一索引:同一张表中可以有多个,用于保证字段的唯一性以及提高数据的查询速度。
4、外键索引:创建外键时会自动创建,使两表之间产生关联。
5、联合索引:同时提高多个字段的查询速度,需满足最左匹配原则,否则会索引失效。
最左匹配原则:
MySQL 的最左匹配原则是指在使用联合索引(也称为多列索引)时,查询条件中的列顺序应该与索引定义的列顺序相匹配,并且优先使用左边的列。
具体来说,如果在一个联合索引中定义了多个列,例如 (列 1, 列 2, 列 3) ,那么在查询时,
MySQL 会首先根据列 1 进行索引搜索。如果列 1 的值存在多个匹配项,那么 MySQL 会继续根据列 2 的值进行筛选,以此类推。
如果查询条件中只使用了联合索引的一部分列,例如只使用了列 2 和列 3,而没有使用列 1,那么索引仍然可以生效,但效果可能会受到一定影响。在这种情况下,MySQL 会利用已使用的列进行索引搜索,但无法利用索引的全部效果。
最左匹配原则的目的是为了提高查询效率和索引的利用效率。通过按照索引定义的顺序使用列,可以更好地利用索引的有序性和筛选能力,减少需要扫描的数据量,从而提高查询性能。
需要注意的是,最左匹配原则并不是绝对的,实际的查询优化还会受到其他因素的影响,例如数据分布、数据量、查询的复杂度等。在实际应用中,需要根据具体情况进行测试和分析,以确定最合适的索引策略和查询方式。
索引失效场景:
1、全表扫描
2、索引列进行了计算
3、使用函数进行索引列的操作
4、like操作符使用通配符开头
5、or条件中的部分条件没有索引
6、联合索引不满足最左匹配原则
3、sql优化
1、使用索引:为经常用于查询条件的列创建索引,加快数据检索速度。
2、避免使用select *:只检索需要的列,减少数据传输和处理时间。
3、适当分页查询:使用limit和offset进行分页,避免一次性加载大量数据。
4、使用连接(JOIN)优化:使用inner/left/right join等连接方式,避免笛卡尔积。
5、避免使用子查询:尽量避免使用子查询,可以使用join或者临时表代替。
6、使用 EXPLAIN 分析查询:通过EXPLAIN关键字分析查询执行计划,找出慢查询和需要优化的地方。
7、定期维护和优化数据库:定期清理无用数据、重建索引、分析表统计信息等,保持数据库性能稳定。
8、合理使用事务:将需要同时执行的操作放在同一个事务中,减少事务的开销。
9、定期优化数据库结构:定期检查和优化数据库表结构,包括索引、字段类型等。
10、适当使用缓存:对一些频繁查询但不经常变化的数据,可以考虑使用缓存,减少数据库压力。
11、避免在 WHERE 子句中对字段进行函数操作:这会导致索引失效,影响查询性能。
12、合理使用字段类型:选择合适的字段类型,避免使用过大或不必要的数据类型,减少存储空间和提高查询效率。
4、MySQL 中有哪几种锁
5、MySQL 中 InnoDB 支持的四种事务隔离级别名称,以及逐级之间的区别
1、读未提交(read uncommitted):
读未提交是指事务A可以读取到事务B未提交的数据。
这种隔离级别存在一种脏读现象,我们称读到了脏数据。
读未提交是最低的隔离级别。
2、读已提交(read committed):
读未提交是指事务A只能读取到事务B提交之后的数据。
这种隔离级别解决了脏读现象,但是存在一种不可重复读现象。
不可重复读是指在事务开启之后,第一次读到的数据是3条,当前事务还没有结束,可能第二次再读取到的时候,读到的数据就是4条,3不等于4称为不可重复读取。这种隔离级别是比较真实的数据,每一次读到的数据是绝对的真实。
oracle数据库默认的隔离级别就是读已提交。
3、可重复读(repeatable read):
可重复读是指事务A开启之后,不管是多久,每一次在事务A中读取到的数据都是一致的。即使事务B将数据已经修改,并且提交了,事务A读取到的数据还是没有发生变化,这就是可重复读。
这种隔离级别解决了不可重复读现象,但是存在一种幻读现象。
mysql数据库默认的隔离级别就是可重复读。
4、可串行化(serializable):
可串行化每一次读取到的数据都是最真实的,但是效率是最低的。
可串行化解决了所有的问题。
可串行化是最高的隔离级别。
6、CHAR 和 VARCHAR 的区别
1、存储方式:
CHAR 类型固定长度,当存储字符数不足指定长度时会用空格填充,占用空间固定。
VARCHAR 类型可变长度,实际占用的空间根据存储的数据长度而变化。
2、空间利用:
对于包含大量可变长度字符串的列,使用 VARCHAR 可以节省空间,因为它只存储实际使用的数据长度。
对于固定长度的字符串,使用 CHAR 可能更节省空间,因为它不需要存储额外的长度信息。
3、查询效率:
在查询和排序等操作时,CHAR 类型的效率可能更高,因为它的存储方式是固定长度的,无需额外的长度信息,而 VARCHAR 则需要额外的长度信息。
7、mysql的 ACID
1、原子性(Atomicity):指事务中的操作要么全部成功,要么全部失败。不存在部分操作成功而其他操作失败的情况。
2、一致性(Consistency):指事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。即事务开始前和结束后,数据库的完整性约束没有被破坏。
3、隔离性(Isolation):指多个事务并发执行时,每个事务都相互隔离,彼此不会互相干扰。
不同的隔离级别(读未提交、读已提交、可重复读、可串行化)决定了事务之间的隔离程度。
4、持久性(Durability):指一旦事务提交,对数据库的修改就会永久保存在数据库中,即使数据库发生故障也不会丢失提交的数据。持久性通过将事务的日志保存在磁盘上来实现。
对于MySQL,可以使用BEGIN、COMMIT和ROLLBACK语句来控制事务的开始、提交和回滚。
8、查询索引的sql
SHOW INDEX FROM table_name;
9、查询当前数据库版本的sql
SELECT VERSION();
10、mysql 中 in 和 exists 区别
IN 主要用于比较某个字段的值是否在子查询结果集中,适用于简单的值匹配查询。
SELECT column_name(s)
FROM table_name
WHERE column_name IN (SELECT column_name FROM table_name WHERE condition);
EXISTS 主要用于检查子查询是否返回结果,适用于需要判断子查询是否为空的情况。通常用于复杂的条件过滤和连接查询。
SELECT column_name(s)
FROM table_name
WHERE EXISTS (SELECT column_name FROM table_name WHERE condition);
11、mysql统计函数有哪些
COUNT:用于统计查询结果集中的行数。
SUM:用于计算数值列的总和。
AVG:用于计算数值列的平均值。
MAX:用于获取数值列的最大值。
MIN:用于获取数值列的最小值。
12、mysql存储引擎有哪些
InnoDB:MySQL的默认存储引擎,支持事务、行级锁、外键等特性,适合于需要高并发、事务安全的应用场景。
MyISAM:适合于读密集型的应用,不支持事务和行级锁,但是速度快、存储占用小。
MEMORY:将数据存储在内存中,适合于临时表或者存储较小的数据集。
CSV:将数据以CSV格式存储在文件中,适合于导入导出数据。
ARCHIVE:使用压缩算法来存储数据,适合于存储大量历史数据。
NDB Cluster:适合于高可用、高并发的分布式数据库集群环境。
13、score表中有id、name、score三个字段,查询出每个成绩对应的数量,升序排
SELECT score, COUNT(*) AS count
FROM score
GROUP BY score
ORDER BY score ASC;
13、student表中有id,name字段,只查出name重复的字段
SELECT name
FROM student
GROUP BY name
HAVING COUNT(*) > 1;
四、springboot:
1、Spring Boot 有哪些优点和缺点
优点:
1、快速启动:Spring Boot 提供了快速启动器(starter)和自动配置,使项目创建和部署变得更加简单快捷。
2、简化配置:采用约定大于配置的原则,减少了繁琐的配置工作,提供了默认配置,开发人员可以根据需要进行自定义配置。
3、集成简单:Spring Boot 集成了大量常用的框架和组件,如Spring MVC、Spring Data、Spring Security等,大大降低了集成的复杂度。
4、内嵌服务器:Spring Boot 内置了多种常用的Web服务器,如Tomcat、Jetty等,无需额外配置,即可快速启动应用程序。
5、微服务支持:Spring Boot 对微服务架构提供了良好的支持,集成了Spring Cloud等微服务相关的技术,方便开发和管理微服务应用。
缺点:
1、学习曲线:对于初学者来说,Spring Boot 可能有一定的学习曲线,需要掌握Spring框架的基础知识,并了解Spring Boot的特性和用法。
2、过度依赖:Spring Boot 集成了大量的框架和组件,有时候可能会导致项目产生过多的依赖,增加了项目的复杂性和维护成本。
3、自动配置复杂性:尽管Spring Boot的自动配置功能可以简化项目的配置,但有时候也可能会导致配置变得复杂难以理解,特别是在定制自动配置时。
4、集成冲突:由于Spring Boot集成了多个框架和组件,可能会出现版本冲突或集成问题,需要谨慎处理依赖关系和版本兼容性。
5、项目启动速度:由于Spring Boot需要加载大量的自动配置和依赖,有时候可能会导致项目启动速度较慢,尤其是在应用程序规模较大时。
2、SpringBoot 自动装配
Spring Boot 自动装配是指 Spring Boot 框架根据项目的依赖和配置,自动配置应用程序的各种组件和功能,简化了开发过程,提高了开发效率。
在 Spring Boot 中,通过 @EnableAutoConfiguration 注解来启用自动配置功能。Spring Boot 根据应用程序的依赖,例如 Maven 或 Gradle 中引入的各种 Starter 依赖,以及配置文件中的配置项,自动配置应用程序所需的各种组件,如数据库连接、Web 容器、消息队列等。
Spring Boot 自动配置的原理是通过条件注解和 Spring Boot 提供的各种 Starter 来实现的。条件注解根据项目中的依赖和配置来判断是否需要应用某项配置,如果符合条件,则自动配置相应的组件;Starter 是一种特殊的依赖,它包含了一系列的默认依赖和配置,可以快速集成各种常用的技术栈,如 Spring MVC、JPA、Redis 等。
通过 Spring Boot 的自动配置,开发者无需手动配置大量的 XML 文件或 Java 代码,而是可以专注于业务逻辑的开发,大大简化了项目的配置和搭建过程,提高了开发效率。
3、SpringBoot 有哪几种读取配置的方式
1、使用value注解:
使用@Value注解可以直接将配置文件中的属性值注入到Spring Bean中。
yml:
my:
property: example-value
代码:
@Value("${my.property}")
private String myProperty;
2、使用ConfigurationProperties注解:
yml:
my:
property1: value1
property2: value2
代码:
@Component
@ConfigurationProperties(prefix = "my")
public class MyProperties {
private String property1;
private String property2;
// getters and setters
}
3、使用Environment接口:
@Autowired
private Environment env;
public void someMethod() {
String property = env.getProperty("my.property");
}
五、spring:
1、Spring常用注解
1、@Autowried:实现服务类的自动注入,默认按照byType的方式进行装配。
2、@Post/Get/Put/DeleteMapping:分别用于处理POST/GET/PUT/DELETE请求的方法。
3、@Controller:用于处理Web请求。
4、@Value:用于注入配置文件中的属性值。
5、@Transactional:声明方法或类在事务的管理下运行。
6、@Service:被该注解标识的实现类会被Spring交给Ioc去管理。
7、@RequestParam:将请求参数绑定到方法参数上。
8、@PathVariable:将URL中的路径参数绑定到方法参数上。
还有常见的@RequestMapping、@RequestParam、@ResponseBody、@Repository、@Component等Spring常用注解。
注解Autowried和resource的区别:
Autowried是Spring的注解,默认按照byType的方式进行装配
resource是JavaEE的注解,默认按照byName的方式进行装配
2、spring的aop和ioc的区别
IOC:控制反转,也称依赖注入。在没有Spring之前,若需要频繁的创建对象,对于开发者而言是费时费事的;出现Spring之后,Spring会将频繁创建对象的这个一过程都交给ioc去处理,对于开发者而言可以将省去的时间都集中在业务层面,省时省事。
应用场景:
1、被Service注解修饰的实现类会被Spring交给Ioc去处理
2、被注入的服务类会被Spring交给Ioc去处理
3、被Configuration注解修饰的应用配置类以及类中的Bean,会被Spring交给Ioc去处理
AOP:面向切面编程。它是通过将代码在程序的运行过程中切入或切出到某个类的方法中。底层主要是通过Jdk动态代理和Cglib动态代理来实现的。Jdk动态代理则是提高反射机制和拦截器来实现的,而Cglib动态代理则是提高ASM框架来实现的。
应用场景:
1、被Transactional注解修饰的方法,通过aop去实现事务的一致性
2、日志记录,在方法执行前后,记录方法的入参,返回值、执行时间等信息
3、异常处理,在方法执行后捕获异常,实现异常的同一处理逻辑
3、bean的生命周期
1、实例化(Instantiation):在容器启动或需要时,根据配置信息实例化Bean对象。
2、初始化(Initialization):在Bean实例化后,容器会调用初始化方法,如构造函数、@PostConstruct注解的方法等,对Bean进行初始化设置。
3、使用(Usage):在初始化后,Bean会被容器管理,应用程序可以通过容器获取和使用Bean,执行各种业务逻辑。
4、销毁(Destruction):在应用程序关闭或Bean不再需要时,容器会调用销毁方法,如@PreDestroy注解的方法等,对Bean进行清理和资源释放。
六、微服务:
1、谈谈你对微服务的理解,以及和单体架构的区别
微服务架构是一种软件架构风格,其中应用程序被拆分成一组小型、松耦合的服务,每个服务专注于特定的业务功能,并通过轻量级通信机制进行交互。微服务架构强调将应用程序拆分成多个服务,每个服务都可以独立部署、扩展和更新,以提高灵活性、可扩展性和快速交付能力。
与传统的单体架构相比,微服务架构有以下区别:
1、拆分度和模块化:
单体架构通常由一个单独的应用程序组成,所有功能和业务逻辑都集中在一个代码库中。而微服务架构将应用程序拆分成多个小型服务,每个服务专注于特定的业务功能,实现了更高的拆分度和模块化。
2、技术多样性:
单体架构通常使用统一的技术栈和编程语言开发整个应用程序。而微服务架构允许每个服务使用不同的技术栈和编程语言,根据业务需求选择最适合的技术,提高了灵活性和创新性。
3、部署和扩展:
单体架构的应用程序通常作为一个整体进行部署和扩展,无法针对特定功能进行独立部署和扩展。而微服务架构允许每个服务独立部署和扩展,根据业务需求对单个服务进行调整和优化,提高了灵活性和可扩展性。
4、团队组织和自治性:
单体架构通常由一个团队负责开发和维护整个应用程序,所有功能和业务逻辑都集中在一个代码库中。而微服务架构允许每个服务由专门的团队负责开发和维护,提高了团队的自治性和创造力,促进了团队之间的协作和交流。
5、故障隔离和弹性:
单体架构中一个模块的故障可能导致整个应用程序的崩溃,难以实现故障隔离和弹性。而微服务架构将应用程序拆分成多个服务,一个服务的故障不会影响其他服务的运行,提高了系统的弹性和容错性。
总的来说,微服务架构强调将应用程序拆分成多个小型、松耦合的服务,通过独立部署、技术多样性、团队自治性等特点实现了更高的灵活性、可扩展性和快速交付能力,但也增加了系统的设计和运维成本。
2、nacos心跳机制
nacos首先会计算出服务的最新一次心跳时间与当前时间的1差值,接着再于系统指定的阈值作比较,如果大于指定阈值,Nacos会将其服务标记为不健康或将其从注册列表中移除。
如果服务恢复并重新注册到Nacos,它将再次变为健康状态。
3、对gateway的理解
Gateway(网关)是一个充当访问点的服务器或服务,它作为系统的入口点,接收来自客户端的请求,并将请求转发到后端的微服务或其他系统。Gateway 在软件架构中起着重要的作用,可以实现诸如安全认证、请求路由、负载均衡、缓存、日志记录等功能,从而实现对系统的统一访问和管理。
在微服务架构中,API Gateway 是一种常见的网关形式,它负责管理和处理来自客户端的 API 请求,并将这些请求转发到后端的微服务。API Gateway 可以实现路由转发、安全认证、授权、限流、日志记录等功能,同时还可以对请求进行转换和加工,以适配后端微服务的接口。
总的来说,Gateway 提供了一个中心化的访问点,可以实现对系统的统一管理和控制,提高系统的安全性、性能和可用性,同时也方便了系统的扩展和维护。Gateway 在现代软件架构中扮演着重要的角色,是构建可靠、高效、安全的系统的关键组件之一。
4、微服务的优缺点
优点:
1、模块化和松耦合:微服务架构将应用拆分成多个小型服务,每个服务都专注于特定的业务功能,使得系统更加模块化和松耦合,便于开发、测试和维护。
2、灵活性和可扩展性:每个微服务都可以独立部署、升级和扩展,可以根据业务需求对单个服务进行调整和优化,从而提高系统的灵活性和可扩展性。
3、技术多样性:微服务架构允许每个服务使用不同的技术栈和编程语言,可以选择最适合特定业务需求的技术,从而提高开发效率和灵活性。
4、弹性和容错性:微服务架构将应用拆分成多个服务,一个服务的故障不会影响其他服务的运行,提高了系统的弹性和容错性。
5、快速部署和交付:微服务架构支持持续集成和持续交付,可以快速部署新功能和更新,缩短开发周期,提高交付效率。
6、更好的团队组织:每个微服务都可以由专门的团队负责开发和维护,提高了团队的自治性和创造力,促进了团队之间的协作和交流。
缺点:
1、分布式系统复杂性:微服务架构引入了分布式系统的复杂性,包括服务间通信、服务注册与发现、服务治理、数据一致性等方面的挑战,增加了系统的设计和运维成本。
2、服务间通信开销:微服务架构通过网络进行服务间通信,可能引入额外的延迟和带宽消耗,影响系统的性能和吞吐量。
3、数据管理:微服务架构中每个服务都有自己的数据存储,可能导致数据一致性和数据管理方面的挑战,增加了系统的复杂性和管理成本。
4、部署和监控:微服务架构中涉及多个服务,需要对每个服务进行独立部署和监控,增加了部署和运维的工作量。
5、服务拆分和边界定义:微服务架构需要对系统进行合理的拆分和边界定义,划分服务的粒度需要平衡系统的灵活性和复杂性,需要经过合理的设计和评估。
6、技术选型和管理:微服务架构允许每个服务使用不同的技术栈和编程语言,可能导致技术选型和管理方面的挑战,需要进行合理的规划和管理。
七、java基础:
1、获得一个类的类对象有哪些方式
1、使用类名的.class语法:
Class<?> clazz = MyClass.class;
2、调用对象的getClass()方法:
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();
3、使用Class类的forName()静态方法:
Class<?> clazz = Class.forName("com.example.MyClass");
4、使用类加载器ClassLoader的loadClass方法:
ClassLoader classLoader = MyClass.class.getClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");
2、在 Java 中,如何跳出当前的多重嵌套循环
在 Java 中,要跳出多重嵌套循环,可以使用带标签的 break 语句。具体步骤如下:
1、在外层循环之前使用一个标签,如 outer。
2、在需要跳出多重嵌套循环的地方,使用 break outer; 语句。
以下是一个示例代码:
outerLoop:
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i * j > 6) {
System.out.println("Breaking out of outerLoop at i = " + i + " and j = " + j);
break outerLoop;
}
System.out.println(i + " " + j);
}
}
在上面的示例中,当 i * j 大于 6 时,程序会输出提示信息并跳出外层循环。
3、Java 中有几种类型的流
1、字节流(Byte Streams):字节流以字节为单位进行读写操作,适用于处理二进制数据。主要包括InputStream和OutputStream。
2、字符流(Character Streams):字符流以字符为单位进行读写操作,适用于处理文本数据。主要包括Reader和Writer。
3、缓冲流(Buffered Streams):缓冲流在字节流和字符流的基础上增加了缓冲区,提高了读写性能。主要包括BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter。
4、对象流(Object Streams):对象流用于读写Java对象,可以直接将对象写入流或从流中读取对象。主要包括ObjectInputStream和ObjectOutputStream。
5、数据流(Data Streams):数据流用于读写基本数据类型和字符串,提供了直接读写基本类型数据的方法。主要包括DataInputStream和DataOutputStream。
6、文件流(File Streams):文件流用于读写文件数据,可以直接与文件进行交互。主要包括FileInputStream和FileOutputStream。
4、java创建对象有哪些方式
1、使用构造方法创建对象:
public class Main {
public static void main(String[] args) {
// 创建对象
Person person = new Person("Alice", 25);
// 使用对象
System.out.println("Name: " + person.getName() + ", Age: " + person.getAge());
}
}
2、使用静态工厂方法创建对象:
public class Main {
public static void main(String[] args) {
// 创建对象
Person person = Person.createPerson("Bob", 30);
// 使用对象
System.out.println("Name: " + person.getName() + ", Age: " + person.getAge());
}
}
3、使用反射创建对象:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static void main(String[] args) {
// 使用反射创建对象
try {
Class<?> clazz = Class.forName("Person");
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Person person = (Person) constructor.newInstance("Charlie", 35);
// 使用对象
System.out.println("Name: " + person.getName() + ", Age: " + person.getAge());
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
4、使用克隆创建对象:
public class Main {
public static void main(String[] args) {
// 创建对象
Person person1 = new Person("David", 40);
Person person2 = null;
try {
person2 = (Person) person1.clone();
// 使用对象
System.out.println("Name: " + person2.getName() + ", Age: " + person2.getAge());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
5、列出一些你常见的运行时异常
1、NullPointerException(空指针异常):
String str = null;
System.out.println(str.length()); // 抛出 NullPointerException
2、ArrayIndexOutOfBoundsException(数组下标越界异常):
int[] arr = new int[5];
System.out.println(arr[5]); // 抛出 ArrayIndexOutOfBoundsException
3、ClassCastException(类转换异常):
Object obj = new Integer(10);
String str = (String) obj; // 抛出 ClassCastException
4、NumberFormatException(数字格式异常):
String str = "abc";
int num = Integer.parseInt(str); // 抛出 NumberFormatException
5、ArithmeticException(算术异常):
int result = 10 / 0; // 抛出 ArithmeticException
6、ConcurrentModificationException(并发修改异常):
List<String> list = new ArrayList<>();
list.add("A");
for (String s : list) {
list.remove(s); // 抛出 ConcurrentModificationException
}
7、IllegalArgumentException(非法参数异常):
int age = -5;
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
8、IllegalStateException(非法状态异常):
List<String> list = Collections.emptyList();
list.add("A"); // 抛出 IllegalStateException
9、IndexOutOfBoundsException(索引越界异常):
List<String> list = new ArrayList<>();
list.add("A");
System.out.println(list.get(1)); // 抛出 IndexOutOfBoundsException
10、NoSuchElementException(没有元素异常):
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
System.out.println(iterator.next()); // 抛出 NoSuchElementException
6、String 类的常用方法都有那些
1、长度相关方法:int length(): 返回字符串的长度。
String str = "Hello, World!";
int length = str.length();
System.out.println("字符串长度为:" + length); // 输出:字符串长度为:13
2、字符获取方法:char charAt(int index): 返回指定索引处的字符。
String str = "Hello, World!";
char ch = str.charAt(6);
System.out.println("索引6处的字符为:" + ch); // 输出:索引6处的字符为: W
3、字符串比较方法:boolean equals(Object anObject): 比较两个字符串是否相等。
String str1 = "hello";
String str2 = "hello";
boolean isEqual = str1.equals(str2);
System.out.println("str1 和 str2 是否相等:" + isEqual); // 输出:str1 和 str2 是否相等:true
4、查找方法:int indexOf(String str): 返回指定子字符串在字符串中第一次出现的位置。
String str = "Hello, World!";
int index = str.indexOf("World");
System.out.println("子字符串 'World' 在字符串中的位置:" + index); // 输出:子字符串 'World' 在字符串中的位置:7
5、截取方法:String substring(int beginIndex, int endIndex): 返回从指定位置开始到指定位置结束的子字符串。
String str = "Hello, World!";
String substr = str.substring(7, 12);
System.out.println("截取后的子字符串:" + substr); // 输出:截取后的子字符串:World
6、转换方法:String toUpperCase(): 将字符串转换为大写形式。
String str = "Hello, World!";
String upperCaseStr = str.toUpperCase();
System.out.println("转换为大写形式后的字符串:" + upperCaseStr); // 输出:转换为大写形式后的字符串:HELLO, WORLD!
7、判断方法:boolean startsWith(String prefix): 判断字符串是否以指定前缀开头。
String str = "Hello, World!";
boolean startsWithHello = str.startsWith("Hello");
System.out.println("字符串是否以 'Hello' 开头:" + startsWithHello); // 输出:字符串是否以
7、== 和 equals 的区别是什么
"=="用于比较对象的引用,即判断两个对象是否指向内存中的同一个地址,只有在引用相同时返回true。
"equals()"方法用于比较对象的内容,即判断两个对象的内容是否相同,通常需要重写该方法以实现自定义的比较逻辑。
举个例子:
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); // 输出false,因为str1和str2引用的不是同一个对象
System.out.println(str1.equals(str2)); // 输出true,因为str1和str2的内容相同
8、面向对象的特征有哪些方面
1、封装(Encapsulation):封装是将数据和方法组合成一个单独的单元,并对外部隐藏其实现细节。通过封装,对象的内部状态可以被保护,只能通过对象的公有接口访问和操作。
2、继承(Inheritance):继承是一种机制,允许一个类(子类)继承另一个类(父类)的属性和方法。通过继承,子类可以重用父类的代码,并且可以添加自己的新功能或修改继承的功能。
3、多态(Polymorphism):多态是指同一个方法在不同的对象上有不同的行为。在面向对象编程中,多态性允许不同的对象对同一消息做出不同的响应。多态性提高了代码的灵活性和可维护性。
4、抽象(Abstraction):抽象是将复杂的现实世界建模成合适的抽象类或接口。抽象可以隐藏对象的复杂性,只展示对象的必要特征和行为,从而简化程序设计和理解。
9、抽象类与接口区别?接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)
抽象类可以有构造方法/接口不能有构造方法
一个类只能继承一个抽象类/但可以实现多个接口
抽象类中可以有普通方法的实现/接口中的方法默认都是抽象的,不能有方法的实现
抽象类中能包含静态代码块和实例代码块/接口不能包含
接口可以继承接口。
抽象类可以实现接口。
抽象类可以继承具体类。
10、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分
重载:同一个类中定义多个方法,方法名相同但参数列表不同,重载方法的返回值类型可以相同也可以不同。
重写:重写是指子类继承父类并且重写(覆盖)父类中的方法,方法名、参数列表和返回值类型必须与父类中的方法相同,用于实现多态性。
不行。重载的方法必须根据方法的参数类型、个数和顺序进行区分,而不能根据返回类型进行区分。重载的方法的返回类型可以相同也可以不同,但是不能仅仅根据返回类型来进行区分。
11、int 和 Integer 有什么区别
1、默认值:
int的默认值是0,Integer的默认值是null。
2、数据类型:
int是基本数据类型,Integer是对象类型。
3、存储位置:
int存储在栈内存中,Integer存储在堆内存中。
4、性能和内存消耗:
int具有更高的性能和较小的内存消耗,Integer具有更高的内存消耗和较低的性能。
12、内存泄露和内存溢出的区别
1、内存泄露:
内存中无用的资源越来越多,并且一直都没有被释放。随着时间的推移,就发生了内存泄露。
解决方案:即时释放堆中的不再使用的对象,避免循环引用。使用内存分析工具检测和解决内存泄露问题。
2、内存溢出:
程序不断的向系统申请内存,随着时间的推移,程序申请的内存超出了内存的实际容量,就发生了内存泄露。
解决方案:增加JVM的堆内存大小。
设置IDEA内置JVM内存大小:
设置IDEA内置JVM内存大小(堆空间大小设置)_idea设置jvm内存大小-CSDN博客
linux查看项目的堆内存大小:
要查看正在运行的 Java 项目的堆内存大小,可以使用以下步骤:
1、首先,确定正在运行的 Java 进程的 PID(进程标识符),可以使用 ps 命令来查看:
ps aux | grep java
这将列出所有包含 "java" 关键字的进程,并显示它们的详细信息,包括 PID。
2、找到你想要查看的 Java 进程的 PID,并记下来。
3、然后,使用 jmap 命令来查看该 Java 进程的堆内存使用情况:
jmap -heap <PID>
其中,<PID> 是你想要查看的 Java 进程的 PID。这将显示该进程的堆内存使用情况,包括堆的大小、已使用空间和空闲空间等信息。
linux设置项目的堆内存大小:
要设置 Java 项目的堆内存大小,可以通过设置 JVM 启动参数来实现。具体步骤如下:
1、打开你要运行 Java 应用程序的启动脚本(例如 .bat 文件或 .sh 文件)。
2、找到其中设置 JVM 参数的地方,一般是通过 -Xms 和 -Xmx 参数来指定堆内存的初始大小和最大大小。
-Xms<size>:指定 JVM 的初始堆内存大小,单位为字节,默认值通常是物理内存的 1/64。
-Xmx<size>:指定 JVM 的最大堆内存大小,单位为字节,默认值通常是物理内存的 1/4。
3、根据需要,设置 -Xms 和 -Xmx 参数的值为你希望分配的堆内存大小。例如,如果你希望将初始堆内存大小设置为 512MB,最大堆内存大小设置为 2GB,可以这样设置:
-Xms512m -Xmx2g
4、保存文件并关闭编辑器。
5、使用修改后的启动脚本启动 Java 应用程序。
需要注意的是,增加堆内存大小可能会影响系统的性能和资源使用情况,特别是在物理内存有限的情况下。建议根据实际情况调整堆内存大小,避免过度分配导致系统资源紧张或性能下降。
13、Java中finally和return执行顺序
如果try中代码块没有return的话,会先执行try中的代码块,然后再执行finally中的代码块
如果try中代码块有return的话,会先执行try中的代码块,然后再执行finally中的代码块,最后再执行try中的return
下面是一个示例代码来说明 finally 和 return 的执行顺序:
public class Main {
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
try {
System.out.println("Try block is executed.");
return 1;
} finally {
System.out.println("Finally block is executed.");
}
}
}
输出结果为:
Try block is executed.
Finally block is executed.
1
14、String 和 StringBuilder、StringBuffer 的区别
1、不可变性:
String是不可变的,一旦被创建,它的值就不能被修改。
StringBuilder和StringBuffer是可变的,它们的值可以被修改。
2、线程安全性:
String是线程安全的。
StringBuilder是非线程安全的,它的方法都不是同步的。
StringBuffer是线程安全的,它的方法都是同步的。
3、性能:
String每次操作字符串都会产生新的对象,可能会导致频繁的对象创建和销毁,影响性能。
StringBuilder操作字符串不会产生新的对象,性能较高。适用于单线程环境下对字符串进行频繁操作的场景。
StringBuffer由于它的方法是同步的,因此性能会比StringBuilder略低。适用于多线程环境下对字符串进行频繁操作的场景。
15、解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法
1、栈(Stack):
栈是用于存储线程执行方法时的局部变量、方法参数、方法调用和返回值等信息的内存区域。
每个线程都有自己的栈,栈的大小是固定的。
栈内存管理由系统自动分配和释放,随着方法的执行结束或方法调用的返回,栈中的数据会自动销毁。
2、堆(Heap):
堆是用于存储对象实例和数组对象的内存区域。
堆内存由Java虚拟机(JVM)动态分配和释放,它的大小是可变的。
所有线程共享堆内存,堆中的对象可以被多个线程访问。
堆中的对象由垃圾回收器(Garbage Collector)负责管理,当对象不再被引用时,垃圾回收器会自动释放对象所占用的内存。
3、方法区(Method Area):
方法区是存储类信息、常量、静态变量、编译器编译后的代码等数据的内存区域。
方法区与堆一样,是所有线程共享的内存区域。
方法区由Java虚拟机(JVM)动态分配和释放,它的大小也是可变的。
方法区中的数据由Java类加载器负责加载,并且在Java虚拟机启动时创建,随着Java虚拟机的退出而销毁。
16、对象创建过程
1、类加载:在程序中使用到一个类时,Java 虚拟机会检查该类是否已经加载。如果尚未加载,则会执行类加载过程。类加载过程包括加载、链接和初始化三个阶段。
2、内存分配:在类加载完成后,Java 虚拟机会为该类的实例分配内存空间。内存分配的方式有多种,常见的包括堆内存分配和栈内存分配。
3、初始化:在内存分配完成后,Java 虚拟机会调用类的构造方法来初始化对象。初始化过程包括对对象的成员变量进行默认初始化,执行实例初始化块,然后调用构造方法。
4、对象引用:对象创建完成后,可以通过对象引用来操作和访问对象的属性和方法。
17、类加载过程
类加载过程是Java虚拟机将类的字节码加载到内存中并转换为运行时数据结构的过程。类加载过程包括以下步骤:
1、加载(Loading): 加载是指查找并加载类的字节码文件。当程序需要使用某个类时,Java虚拟机会根据类的名称查找类文件,并将其加载到内存中。
2、链接(Linking): 链接分为三个阶段:
验证(Verification): 验证确保类的字节码文件符合Java虚拟机规范,不会损害虚拟机的安全性。
准备(Preparation): 准备阶段为类的静态变量分配内存并设置默认初始值。
解析(Resolution): 解析阶段将类、接口、字段和方法的符号引用转换为直接引用。
3、初始化(Initialization): 在初始化阶段,Java虚拟机会执行类构造器<clinit>()方法,对类的静态变量进行初始化赋值操作,以及执行静态代码块。
需要注意的是,类加载过程中的链接和初始化阶段并不是必须的,某些类可能在运行时才会被动态加载、链接和初始化。
类加载过程由Java虚拟机的类加载器完成,Java虚拟机会根据双亲委派模型,采用一种层级结构加载类,保证类加载的顺序和一致性。
18、Stream常用方法
filter(Predicate predicate):过滤出符合条件的元素。
map(Function mapper):将流中的每个元素映射为另一个元素。
flatMap(Function mapper):将流中的每个元素映射为流,并将这些流合并为一个流。
sorted():对流中的元素进行排序。
distinct():去除流中重复的元素。
limit(long maxSize):截取流中的前 maxSize 个元素。
skip(long n):跳过流中的前 n 个元素。
forEach(Consumer action):对流中的每个元素执行指定的操作。
collect(Collector collector):将流中的元素收集到集合或其他容器中。
reduce(BinaryOperator accumulator):对流中的元素进行累积操作。
anyMatch(Predicate predicate):判断流中是否存在符合条件的元素。
allMatch(Predicate predicate):判断流中所有元素是否都符合条件。
noneMatch(Predicate predicate):判断流中是否不存在符合条件的元素。
findFirst():返回流中的第一个元素。
findAny():返回流中的任意一个元素。
19、Java 中如何实现序列化,有什么意义
让类实现 Serializable 接口:Serializable 接口是一个标记接口,没有任何方法,用于标识类的对象可以被序列化。
对于不需要被序列化的属性,使用 transient 关键字修饰:transient 关键字可以在对象序列化时忽略某些属性,这样可以避免序列化那些不需要保存的属性。
20、阐述 final、finally、finalize 的区别
final:是Java中的关键字,用于修饰类、方法和变量。
finally:是Java中的关键字,用于异常处理的结构中,表示无论是否发生异常,都会执行其中的代码块。
finalize:是Java中的一个方法,是在对象被垃圾回收器回收之前调用的方法。
21、Java 语言如何进行异常处理,关键字:throws、throw、try、catch、finally 分别如何使用
在 Java 中,异常处理主要通过以下关键字来实现:
throws:用于声明一个方法可能抛出的异常类型,通常放在方法的声明部分。示例:
public void method() throws IOException {
// 方法实现
}
throw:用于手动抛出一个异常对象,通常在方法内部使用。示例:
public void method() throws IOException {
if (someCondition) {
throw new IOException("Some error message");
}
}
try:用于包裹可能会抛出异常的代码块,后面可以跟随 catch 或 finally 或两者同时使用。示例:
try {
// 可能抛出异常的代码块
} catch (Exception e) {
// 异常处理代码
} finally {
// 无论是否发生异常都会执行的代码块
}
catch:用于捕获并处理 try 块中抛出的异常,可以跟随一个或多个异常类型。示例:
try {
// 可能抛出异常的代码块
} catch (IOException e) {
// 处理 IOException 异常
} catch (Exception e) {
// 处理其他异常
}
finally:用于定义无论是否发生异常都会执行的代码块,通常用于释放资源等清理工作。示例:
try {
// 可能抛出异常的代码块
} finally {
// 无论是否发生异常都会执行的代码块,用于释放资源等清理工作
}
这些关键字可以组合使用,以实现对异常的捕获、处理和资源清理等操作。
22、Java 中的 final 关键字有哪些用法?
1、修饰变量:该变量不可变
2、修饰方法:该方法不可以被重写
3、修饰类:该类不可以被继承
23、&和&&的区别
&: 逻辑与运算符会对左右两个条件都进行计算,然后返回计算结果。如果左右两个条件都为 true,则返回 true;否则返回 false。
&&: 短路逻辑与运算符在执行时,首先会计算左侧的条件。如果左侧条件为 false,则直接返回 false,不再计算右侧的条件。只有左侧条件为 true 时,才会继续计算右侧的条件并返回其结果。
24、访问修饰符 public,private,protected,以及不写(默认)时的区别
public: 公开的,任何地方都可以访问。
private: 私有的,只能在当前类中访问。
protected: 受保护的,当前类、同一包内的其他类以及子类可以访问。
默认(不写修饰符): 同一包内的其他类可以访问,但其他包中的类无法访问。
25、谈谈双亲委派机制
在Java中,java.lang包下的类在加载时会优先由Bootstrap类加载器加载,而Bootstrap类加载器是无法被Java代码所改写的,因此即使你在java.lang包下定义了一个与String同名的类,也不会被加载。这确实是双亲委派机制的限制所致。
双亲委派机制就像是一个家庭中的父母和孩子之间的关系。在Java中,每个类加载器都有一个父类加载器,就像孩子有父母一样。当一个类需要被加载时,Java会先询问父类加载器是否能加载这个类,就像孩子会先问父母能否满足他们的需求一样。如果父类加载器可以加载这个类,那么就由父类加载器来加载,否则就由子类加载器自己来加载。
这种机制就好像孩子们不会轻易跑去找外人(其他类加载器)帮忙,而是先问问自己的父母(父类加载器)。这样一来,类加载器之间形成了一种类似于家庭的层次结构,每个类加载器都像是一个家庭的一员,负责加载自己的“孩子”类。这种家庭关系保证了类加载的有序性和安全性,同时避免了重复加载相同的类。
26、String a = new String("a");和String b = new String("a");用==和equals比较的输出结果分别是什么;String a = "a";和String b = "b";用==和equals比较的输出结果分别是什么
String a = new String("a");
String b = new String("a");
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
String a = "a";
String b = "b";
System.out.println(a == b); // false
System.out.println(a.equals(b)); // false
使用new String("a")创建了两个不同的String对象,虽然它们的内容相同,但是它们在内存中的地址是不同的。因此,a == b比较的是两个String对象的引用,所以结果是false。
String a = "hello";
String b = "hello";
System.out.println(a == b); // 输出 true
在上面的示例中,a 和 b 都指向字符串常量池中的同一个 "hello" 对象,因此 a == b 返回 true。
八、java集合:
1、List、Set、Map 是否继承自 Collection 接口,以及各自的特征
List、Set、Map 均继承自 Collection 接口。
List接口:元素有序,允许有重复值。
Set接口:元素无序,不允许有重复值。
Map接口:以键值对的形式存储数据,键不可以重复,值可以重复。
2、Collection 和 Collections 的区别
1、Collection是集合框架的核心接口之一/Collections是对集合进行操作的工具类。
2、Collection代表一组对象的集合/Collections提供了一系列静态方法,用于操作和管理集合对象。
3、hashset去重原理
HashSet 是 Java 中的一种集合,它实现了 Set 接口,底层基于哈希表实现。HashSet 的去重原理简单来说就是利用哈希表存储元素,并通过元素的 hashCode() 方法和 equals() 方法来判断元素是否重复。
1、哈希表存储:HashSet 内部使用哈希表来存储元素,通过哈希算法将元素映射到不同的数组位置。
2、hashCode() 方法:在向 HashSet 中添加元素时,会调用元素的 hashCode() 方法获取其哈希码,然后根据哈希码找到对应的数组位置。
3、equals() 方法:如果该位置上已经存在元素,则需要调用元素的 equals() 方法来判断是否为相同元素。如果两个元素的 hashCode() 相同,并且 equals() 方法返回 true,那么认为这两个元素是相同的,后添加的元素不会被加入 HashSet 中。
4、去重效率:由于 HashSet 内部使用哈希表存储元素,查找、添加和删除元素的时间复杂度为 O(1),因此 HashSet 可以高效地进行去重操作。
综上所述,HashSet 的去重原理是利用哈希表存储元素,并通过 hashCode() 方法和 equals() 方法来判断元素是否重复,从而实现高效的去重功能。
hashset集合中判断对象是否重复:
1、先判断hashcode是否一样,满足相同的情况下
2、再判断对象的equals方法
3、两个都相同,表示同一个对象
4、hashmap数据结构
HashMap本身就是一个程序/工具类,主要用来存储数据。
1、HashMap的数据结构在1.8之前是“数组+链表”。
数组是采用一段连续的存储单元来存储数据,特点是查询速度快,插入速度慢。
链表是一种物理存储单元上非连续、非顺序的存储结构,特点是插入速度快,查找速度慢。
HashMap会通过字符串算出它的ascii码,进行取模,算出哈希表中的下标。如果下标相同则会发送”哈希冲突“。
HashMap对产生哈希冲突的数据会以“链表”的形式来存储它们,其它数据则以“数组”的形式存储。
2、HashMap的数据结构在1.8之后是“数组+链表+红黑树”
由于“链表”查询速度慢的原因,所以新增了“红黑树”。
需要注意的是HashMap默认的数据结构一开始仍然是“数组+链表”,但是当链表的阈值达到8的时候就会变成“数组+红黑树”。
红黑树相对于链表来说,在大规模数据情况下,由于它的自平衡特性,插入操作的时间复杂度更稳定。
5、ArrayList 和 LinkedList 有什么区别
ArrayList 是基于数组实现的,因此查询(随机访问)效率高。
LinkedList 是基于链表实现的,因此插入和删除操作效率较高。
6、数组与链表的区别
1、数组是一段连续的内存空间,可以通过索引直接访问元素;链表是由一系列节点组成的数据结构,每个节点的存储空间可以独立分配。
2、数组查询(随机访问)效率高;链表插入和删除操作效率较高。
7、常见的线程安全的集合和线程不安全的集合有哪些
线程安全的集合类:Vector、Stack、HashTable
非线程安全的集合类:ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet、HashMap、LinkedHashMap、TreeMap
九、多线程:
1、线程的状态
1、NEW(新建):当线程对象被创建但还没有调用 start() 方法时,线程处于新建状态。
2、RUNNABLE(可运行):当线程被启动并且可以运行时,处于可运行状态。线程在这个状态下可能正在等待 CPU 时间片,也可能正在运行中。
3、BLOCKED(阻塞):当线程被阻塞等待获取锁时,处于阻塞状态。例如,当线程试图进入同步代码块但无法获取锁时,线程将进入阻塞状态。
4、WAITING(等待):当线程无限期等待另一个线程执行某个特定动作时,处于等待状态。线程在这个状态下将一直等待,直到其他线程调用相应的 notify() 或 notifyAll() 方法来唤醒它。
5、TIMED_WAITING(计时等待):当线程在等待另一个线程执行某个特定动作,但是带有一定超时时间时,处于计时等待状态。线程在这个状态下将等待一段时间,直到超时或者其他线程唤醒它。
6、TERMINATED(终止):当线程执行完成或者因异常而终止时,处于终止状态。
2、线程和进程的区别
进程是程序的执行实例,包含代码、数据和正在执行的指令,是操作系统资源分配和调度的基本单位。
线程是进程的一个实体,是程序执行流的最小单元,可以共享进程的资源,实现并发和并行执行。
进程之间相互独立,线程之间共享内存空间和系统资源。
线程比进程更轻量级,创建、销毁和切换开销更小,通常用于实现任务级别的并发。
3、什么是死锁,解决和预防死锁的方法
当两个或多个进程或线程相互等待对方释放资源而无法继续执行时,就会发生死锁。以下是几种通俗易懂的方法来解决和预防死锁:
1、避免循环等待:确保所有进程或线程获取资源的顺序一致,避免循环依赖。
2、资源有序分配:为资源分配一个唯一的全局序号,进程或线程按照序号递增的顺序获取资源。
3、加锁时限:在获取锁时设置一个超时限制,如果超过一定时间未能获取到锁,则放弃等待,释放已经获取的资源。
4、资源预申请:在执行操作前,先尝试获取所有需要的资源,如果获取失败则释放已经获取的资源并等待一段时间后重新尝试。
5、死锁检测和解除:定期检测系统中是否存在死锁,并采取相应的措施解除死锁,例如中断某些进程或线程,释放资源。
6、资源分级:为资源分配一个优先级,进程或线程只能获取低于自身优先级的资源,避免资源间的循环依赖。
这些方法可以帮助避免和解决死锁问题,提高系统的稳定性和可靠性。
4、列出创建线程的几个方法
1、继承Thread类
创建一个继承自Thread类的子类,并重写run()方法,在run()方法种定义线程的执行逻辑。
调用子类的start()方法来启动线程。
class MyThread extends Thread {
public void run() {
// 线程的执行逻辑
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
2、实现Runnable接口:
创建一个实现了Runnable接口的类,并实现run()方法,在run()方法种定义线程的执行逻辑。
创建一个Thread对象,将实现Runnable接口的对象作为参数传递给Thread构造函数。
调用Thread对象的start()方法来启动线程。
class MyRunnable implements Runnable {
public void run() {
// 线程的执行逻辑
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
3、实现Callable接口:
创建一个实现Callable接口的类,并实现call()方法,在call()方法种定义线程的执行逻辑。
创建一个FutureTask对象,将实现了Callable接口的对象作为参数传递给FutureTask构造函数。
创建一个Thread对象,将FutureTask对象作为参数传递给Thread构造函数。
调用Thread对象的start()方法来启动线程。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
// 线程的执行逻辑
return 42;
}
}
public class Main {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
}
}
5、synchronized 的作用
1、同步实例方法:使用 synchronized 关键字修饰实例方法,确保同一时间只有一个线程能够访问该方法所属对象的同步代码块或同步方法,避免多个线程同时访问导致的数据不一致或竞态条件问题。
public synchronized void synchronizedMethod() {
// 同步代码块
}
2、同步代码块:使用 synchronized 关键字修饰代码块,指定需要进行同步的对象或类,确保在同一时间只有一个线程能够访问代码块中的临界资源,从而保证数据的一致性和线程安全性。
synchronized (object) {
// 同步代码块
}
6、线程的生命周期
线程的生命周期通常可以分为以下几个状态:
1、新建(New):当创建了一个线程对象后,它就处于新建状态。此时线程对象已经在内存中,但尚未启动,还没有分配 CPU 时间。
2、就绪(Runnable):线程对象已经调用了 start() 方法,线程处于就绪状态。此时线程已经进入了线程队列,等待系统为其分配 CPU 时间片,一旦分配到 CPU 时间片,就可以开始执行。
3、运行(Running):线程获取到 CPU 时间片,开始执行任务,处于运行状态。此时线程正在执行具体的代码逻辑。
4、阻塞(Blocked):线程处于阻塞状态,通常由于某些原因导致线程无法继续执行,例如调用了 sleep() 方法、等待 I/O 操作完成、等待获取锁等情况。
5、等待/无限期等待(Waiting/Timed Waiting):线程处于等待状态,通常是因为调用了 Object.wait()、Thread.join() 或 LockSupport.park() 方法,线程进入等待队列等待被唤醒。等待状态和无限期等待状态的区别在于,等待状态没有设置等待时间,而无限期等待状态设置了等待时间。
6、终止(Terminated):线程执行完了任务或者发生了异常,线程处于终止状态。一旦线程处于终止状态,它就不能再次启动或者执行任务。
这些是线程的常见生命周期状态,线程在不同状态之间的转换是由 JVM 的线程调度器来控制的。
7、Runnable和Callable的区别
1、实现 Runnable 接口需要实现它的 run() 方法,而实现 Callable 接口需要实现它的 call() 方法。
2、在 run() 方法中不可以直接抛出异常,而在 call() 方法中可以抛出异常。
3、run() 方法没有返回值,而 call() 方法可以返回一个泛型值。
十、Kafka:
1、谈谈Kafka刷盘机制
Kafka的刷盘机制是指将数据从内存中写入磁盘的过程。通俗地说,就像把暂存的水一点点倒入水缸一样。这个过程是为了保证数据的可靠性和持久化存储,即使发生了系统故障也不会丢失数据。
Kafka的刷盘机制一般会包括以下几个方面:
1、日志文件写入:Kafka会将生产者产生的消息先写入内存缓冲区,然后定期或达到一定条件时,再批量地将这些消息写入到磁盘的日志文件中。
2、文件系统缓存:写入磁盘的过程通常会经过文件系统的缓存,提高写入效率。这些数据会暂时存储在文件系统的缓存中,待系统有空闲资源时再进行实际的磁盘写入。
3、异步刷盘:为了提高性能,Kafka通常会采用异步刷盘的方式,即在将数据写入内存缓冲区后,并不立即进行磁盘写入操作,而是等待一定时间或者达到一定条件后再进行批量刷盘,从而减少频繁的磁盘IO操作,提高系统的吞吐量。
4、持久化存储:一旦数据成功写入到磁盘中的日志文件,就意味着数据已经持久化存储了,即使系统发生故障也不会丢失。
总的来说,Kafka的刷盘机制是确保数据安全和持久化存储的重要手段,通过合理地控制刷盘策略和参数配置,可以在保证数据可靠性的同时提高系统的性能和吞吐量。
2、谈谈Kafka集群分片机制
Kafka集群分片机制是指将Kafka中的数据切分成多个小块,分散存储在集群的不同节点上的一种设计。这样做有几个好处:
1、提高吞吐量:将数据分成多个分片,可以让多个节点同时处理数据,提高整个系统的处理能力和吞吐量。
2、水平扩展:当系统的数据量增加时,可以通过增加节点来扩展集群的容量,而不是升级单个节点,从而降低了系统扩展的成本和风险。
4、负载均衡:Kafka会根据分片的情况和各个节点的负载情况,动态地调整数据分布和负载,保证集群中各个节点的负载均衡。
总的来说,Kafka集群分片机制是一种有效的分布式存储和处理数据的方式,通过将数据分散存储在多个节点上,实现了高性能、高可用和水平扩展的分布式数据处理系统。
3、Kafka如何防止消息丢失
生产者:
1、使用同步发送
2、把ack设成1或者all,并且设置同步的分区数>=2
消费者:
把手动提交改成自动提交
4、谈谈Kafka重复消费
在防止消息丢失的⽅案中,如果⽣产者发送完消息后,因为网络抖动,没有收到ack,但实际上broker已经收到了。
此时⽣产者会进行重试,于是broker就会收到多条相同的消息,而造成消费者的重复消费。
怎么解决:
⽣产者关闭重试:会造成丢消息(不建议)
消费者解决非幂等性消费问题:
所谓的幂等性:多次访问的结果是⼀样的。对于rest的请求(get(幂等)、post(非幂等)、put(幂等)、delete(幂等))
解决方案:
在数据库中创建联合主键,防止相同的主键,创建出多条记录
使⽤分布式锁,以业务id为锁。保证只有⼀条记录能够创建成功
十一、Nginx:
1、谈谈反向代理和负载均衡以及相关配置
Nginx是一个高性能的开源Web服务器,也可以用作负载均衡器和反向代理服务器。下面是Nginx负载均衡和反向代理的简介:
负载均衡:
在高流量的网络环境中,单个服务器可能无法处理所有的请求,因此需要将流量分发到多个服务器上,以提高系统的性能和可靠性。Nginx作为负载均衡器可以将来自客户端的请求分发到多个后端服务器上,实现负载均衡。
反向代理:
反向代理隐藏了后端服务器的实际地址,客户端请求会首先被发送到反向代理服务器,然后由反向代理服务器将请求转发给后端服务器。反向代理可以提高安全性,隐藏服务器的真实IP地址,并提供额外的功能,如SSL终端,缓存等。Nginx的负载均衡和反向代理功能使得它在构建高性能、高可用性的Web服务架构中非常有用。
要配置Nginx进行负载均衡和反向代理,你可以按照以下步骤进行:
1、安装Nginx:首先确保已经安装了Nginx。你可以通过包管理器(如apt、yum等)来安装。
2、编辑Nginx配置文件:通常Nginx的主配置文件位于/etc/nginx/nginx.conf,你也可以在该文件中包含其他配置文件。打开配置文件以进行编辑。
3、配置负载均衡:在配置文件中,使用 upstream 块定义一组后端服务器。例如:
upstream backend_servers {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
4、配置反向代理:在配置文件中,设置Nginx作为反向代理,将请求转发给定义的后端服务器组。例如:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
在这个示例中,所有收到的请求都将被转发到定义的 backend_servers 中的服务器组。
proxy_pass 指令指定了后端服务器的地址,而 proxy_set_header 则设置了代理请求头,以便将客户端的真实IP地址传递给后端服务器。
5、重新加载Nginx配置:完成配置后,保存文件并重新加载Nginx以使更改生效。
你可以使用命令 sudo nginx -t 检查配置文件的语法错误,然后使用 sudo systemctl reload nginx 命令重新加载Nginx。
6、测试配置:确保配置正确并且Nginx正常工作。你可以访问你的域名或IP地址,并检查请求是否正确地转发到后端服务器。
请根据你的实际需求和环境,进行适当的配置调整。
2、Nginx性能调优
1、调整工作进程数:根据服务器的CPU核心数和内存大小,适当调整Nginx的工作进程数,以充分利用服务器资源。
2、优化Keepalive连接:合理设置Keepalive连接的超时时间和数量,减少连接的建立和关闭次数,提高服务器的并发处理能力。
3、启用TCP连接重用:开启TCP连接重用功能,允许多个连接复用同一个TCP连接,减少连接的创建和关闭开销。
4、启用Gzip压缩:启用Gzip压缩功能,对响应数据进行压缩,减少数据传输量,提高网络传输速度。
5、调整缓冲区大小:根据服务器的硬件配置和网络环境,适当调整Nginx的缓冲区大小,以提高数据传输的效率。
6、使用缓存:使用Nginx的缓存功能,缓存静态文件或动态内容,减少服务器的负载,加快网站的访问速度。
7、限制请求频率:设置请求频率限制,防止恶意请求或DDoS攻击,保护服务器的稳定性和安全性。
8、监控和调优:定期监控Nginx服务器的性能和运行状态,及时发现和解决性能瓶颈,优化服务器配置和参数。
3、谈谈静态资源缓存以及配置
要在Nginx中配置静态资源服务器,可以按照以下步骤进行:
1、准备静态资源文件:将需要提供的静态资源文件(例如HTML、CSS、JavaScript、图像等)放置在服务器的某个目录下,例如/var/www/html。
2、编辑Nginx配置文件:打开Nginx的配置文件(通常是nginx.conf或/etc/nginx/nginx.conf),添加一个新的server块来配置静态资源服务器。
server {
listen 80;
server_name example.com; # 替换为您的域名
root /var/www/html; # 静态资源文件的根目录
location / {
try_files $uri $uri/ =404; # 尝试查找对应的静态文件,如果找不到返回404
}
}
这个配置告诉Nginx监听80端口,并将所有来自example.com域名的请求映射到/var/www/html目录下。try_files指令会尝试查找对应的静态文件,如果找不到则返回404。
3、重新加载Nginx配置:在完成配置后,使用以下命令重新加载Nginx配置以使更改生效:
sudo nginx -s reload
4、访问静态资源:现在,您可以通过浏览器或其他HTTP客户端访问您的静态资源,例如http://example.com/index.html。
通过以上步骤,您就可以在Nginx中配置一个简单的静态资源服务器了。根据实际需求,您还可以添加更多的配置选项,如缓存、压缩等,以优化静态资源服务器的性能和安全性。
十二、Zookeeper:
1、谈谈paxos算法
Paxos算法是一种用于在分布式系统中达成一致性的算法,它能够确保多个节点在面对消息丢失、延迟或者节点故障等情况下仍然能够达成一致的共识。通俗来说,就是让一群人在没有中央指挥的情况下,协商出一个大家都同意的结果。
为了达成共识,Paxos算法主要分为三个角色:提议者、接受者和学习者。
提议者:负责提出一个提案,包含提案的值和提案的序号。
接受者:负责接受提议,并判断是否接受。接受者会比较提案的序号,如果收到的提案序号比已接受的提案序号大,则接受提案。
学习者:负责学习已经达成共识的提案,并将其应用到系统中。
Paxos算法的核心思想是通过一系列阶段来达成共识,其中包括提议、接受和学习等阶段。提议者提出提案,接受者接受提案并将其广播给其他节点,一旦大多数节点都接受了提案,就达成共识并应用提案。
尽管Paxos算法可能在理论上有些复杂,但它为分布式系统中的一致性问题提供了一种可行的解决方案,被广泛应用于分布式数据库、分布式存储和分布式计算等场景中。
2、谈谈zab算法
ZooKeeper Atomic Broadcast(ZAB)算法是 ZooKeeper 分布式协调服务中使用的一种协议,用于实现分布式一致性和可靠性。ZooKeeper是一个高性能的分布式协调服务,常用于协调分布式系统中的各个节点。
ZAB算法的基本思想是将所有的事务请求按照顺序广播给所有的节点,并通过选举机制选出一个节点作为Leader来负责处理客户端的读写请求。ZAB协议主要包含两个阶段:
消息广播(Message Broadcast):在这个阶段,Leader节点负责向其他节点广播事务请求。当一个节点收到Leader节点的事务请求后,会将其记录到本地的日志中,并异步发送确认消息给Leader。
选举(Leader Election):如果Leader节点出现故障或者网络分区导致无法与其他节点通信,那么系统需要通过选举机制选出一个新的Leader节点来维护系统的一致性。ZAB算法使用了多数投票的原则来保证选出的Leader节点拥有大多数节点的支持,从而确保系统的一致性。
ZAB算法通过严格的消息顺序保证了分布式系统中的数据一致性,同时通过选举机制确保了系统的可用性和容错性。它是ZooKeeper实现高可用性和强一致性的核心算法之一。
3、谈谈CAP
CAP理论是分布式系统设计中的一个重要原则,用来描述在分布式系统中的三个关键属性之间的权衡关系。
1、一致性(Consistency):指的是分布式系统中的所有节点在同一时刻对相同数据的访问结果是一致的。换句话说,如果一个节点对数据进行了更新,那么其他节点在读取该数据时应该能够立即看到更新后的值。
2、可用性(Availability):指的是分布式系统在面对用户请求时能够保持可用的能力。即使系统中的某些节点出现故障或网络故障,仍然能够对外提供服务并返回正确的结果。
3、分区容错性(Partition Tolerance):指的是系统在遇到网络分区(即节点之间无法通信或通信延迟很大)时仍然能够保持正常运行的能力。即使系统中的部分节点无法通信,仍然能够保持一致性和可用性。
CAP理论指出,在分布式系统中无法同时保证一致性、可用性和分区容错性这三个属性。在面对网络分区时,必须在一致性和可用性之间做出选择。因此,CAP理论强调了在设计分布式系统时需要权衡这三个属性,根据具体的需求选择适当的策略。
举例来说,如果一个分布式系统更注重数据的一致性,那么在面对网络分区时可能会牺牲一部分可用性;相反,如果一个系统更注重可用性,可能会对数据的一致性放松要求,以保证在任何情况下都能对外提供服务。
十三、其它:
1、docker常用命令
1、docker version:查看 Docker 版本信息。
2、docker info:查看 Docker 系统信息。
3、docker images:列出本地镜像。
4、docker search [image_name]:搜索 Docker Hub 上的镜像。
5、docker pull [image_name:tag]:从 Docker Hub 下载镜像。
6、docker run [options] [image_name:tag]:运行容器。
例如:docker run -d -p 8080:80 nginx (以后台模式运行 Nginx 容器,并将容器的 80 端口映射到主机的 8080 端口)
7、docker ps:列出正在运行的容器。
8、docker ps -a:列出所有容器(包括已停止的)。
9、docker stop [container_id/container_name]:停止容器。
10、docker start [container_id/container_name]:启动已停止的容器。
11、docker restart [container_id/container_name]:重启容器。
12、docker rm [container_id/container_name]:删除容器。
13、docker rmi [image_id/image_name:tag]:删除镜像。
14、docker exec [options] [container_id/container_name] [command]:在运行的容器中执行命令。
例如:docker exec -it my_container bash (以交互模式进入名为 my_container 的容器,并启动一个 bash 终端)
15、docker logs [container_id/container_name]:查看容器日志。
16、docker inspect [container_id/container_name]:查看容器详细信息。
17、docker build [options] [path_to_dockerfile]:构建镜像。
18、docker-compose up:使用 Docker Compose 启动应用。
19、docker-compose down:使用 Docker Compose 停止应用并移除容器。
20、docker-compose ps:列出 Docker Compose 管理的容器。
2、cookie和session的区别与联系
联系:
Cookie 和 Session 是 Web 开发中常用的两种技术,它们之间的联系主要体现在身份验证和用户跟踪上。通过将 SessionId 存储在 Cookie 中,并将相关的会话数据保存在服务器端的 Session 中,可以实现跨页面或跨请求的用户会话跟踪,同时也可以实现持久的用户身份验证,从而提供个性化的用户体验。
区别:
1、存储位置不同:Cookie 存储在客户端,而 Session 存储在服务器端,因此服务器端存储相对更安全。
2、存储数据类型不同:Cookie 的值只能是字符串类型,而 Session 的值可以是任意对象类型。
3、存储数据大小不同:Cookie 的大小受浏览器限制,通常在4KB左右,而 Session 的大小受服务器内存限制。
4、生命周期区别:Cookie 的生命周期由浏览器控制,在浏览器关闭或超时后会过期;而 Session 的生命周期可以由服务器控制,可以设置为持续访问或特定时间后过期。
3、http和https区别
前言:
1、HTTP协议传输的数据是明文的,而HTTPS利用SSL和TLS协议对数据进行加密传输,因此HTTPS比HTTP更安全。
2、HTTPS是由SSL和TLS协议构建的,它通过在HTTP之上应用SSL和TLS协议来实现加密传输,确保数据的安全性。
3、通过SSL和TLS协议,HTTPS确保数据传输的安全性,同时通过证书验证,确保用户连接的是合法的网站。
区别:
1、HTTP确实不需要申请证书,而HTTPS需要申请CA证书,并且这会带来一定的费用成本。
2、HTTP传输的信息是明文的,而HTTPS通过SSL和TLS协议进行加密传输,可以防止信息被窃取或篡改,因此HTTPS比HTTP更安全。
3、HTTP和HTTPS确实使用不同的连接方式和端口,HTTP默认端口是80,而HTTPS默认端口是443。
4、如何实现跨域
1、在目标方法上添加 @CrossOrigin 注解
@RestController
public class MyController {
@GetMapping("/data")
@CrossOrigin(origins = "*", methods = {RequestMethod.GET})
public ResponseEntity<String> getData() {
// 处理请求并返回数据
}
}
2、添加 CORS 过滤器
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Origin", "*");
httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
3、实现 WebMvcConfigurer 接口,重写 addCorsMappings 方法
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("Content-Type", "Authorization")
.allowCredentials(true)
.maxAge(3600);
}
}
以上三种方法都可以实现跨域通信,选择其中一种适合自己的方式即可。
5、linux常用命令
1、ls:列出目录下的内容。
例如:ls, ls -l, ls -a
2、cd:切换当前目录。
例如:cd directory_name
3、pwd:显示当前工作目录。
例如:pwd
4、mkdir:创建新目录。
例如:mkdir directory_name
5、rm:删除文件或目录。
例如:rm file_name, rm -r directory_name
6、cp:复制文件或目录。
例如:cp source_file target_file, cp -r source_directory target_directory
7、mv:移动文件或目录。
例如:mv source_file target_file, mv source_directory target_directory
8、cat:查看文件内容。
例如:cat file_name
9、grep:在文件中查找匹配的字符串。
例如:grep pattern file_name
10、tail:查看文件末尾内容。
例如:tail file_name, tail -f file_name
11、chmod:修改文件权限。
例如:chmod permissions file_name
12、su:切换用户身份。
例如:su username
13、sudo:以超级用户身份执行命令。
例如:sudo command
14、ps:显示当前进程信息。
例如:ps, ps aux
15、kill:终止进程。
例如:kill process_id
16、wget:下载文件。
例如:wget url
17、find:在文件系统中查找文件。
例如:find directory -name filename
18、history:显示命令历史记录。
例如:history
6、谈谈阿里云,百度云,腾讯云,亚马逊等服务器
1、服务器性能影响因素:确实,服务器性能受多种因素影响,包括价格、硬件规格、网络质量和系统优化等。这些因素共同决定了服务器的性能表现。
2、阿里云提供丰富产品和服务:阿里云是国内领先的云服务提供商,拥有丰富的产品和服务,但价格相对较高。它的价格与提供的服务范围和品质相匹配。
3、百度云和腾讯云知名度和口碑:百度云和腾讯云在云服务领域的知名度逐渐提升,但相对于阿里云,它们的市场份额和口碑可能还有待提高。
4、亚马逊云服务局限于国内:亚马逊云是全球领先的云服务提供商,但其在中国市场的服务范围相对较小,局限于国内特定的业务场景。
总结:
服务器性能受价格、硬件规格、网络质量和系统优化等因素影响。
阿里云提供丰富产品和服务,但价格相对较高。
腾讯云在知名度和口碑方面尚未达到行业领先水平,有过一些丢失数据的事件。
百度云在知名度和口碑方面相对较低。
亚马逊云虽然在国内市场上服务有限,但在国际市场上具有较大的影响力和市场份额。
7、悲观锁和乐观锁的区别
悲观锁:很悲观,认为什么时候都会出现问题,无论做什么都会加锁
乐观锁:很乐观,认为什么时候都不会出现问题,所以不会上锁。更新数据的时候去判断一下,在此期间是否有人修改过这个数据。