当前位置:   article > 正文

Java后台常见面试题_java 后端面试题

java 后端面试题

文章目录

1 数据库

1.1 Mysql使用innodb引擎,请简述mysql索引的最左前缀如何优化oder by子句

1.1.1 关键点

  1. 如果排序字段不在索引列上,filesort有两种算法:mysql就要启用双路排序或者单路排序。
  2. 无过滤不索引
  3. order by非最左filesort
  4. 顺序错filesort
  5. 方向反 filesort
  6. 熟练使用explain,必要时使用optimizer_trace.

1.1.2 答案

  1. 对sql进行分析,检查必要的查询字段、过滤字段和排序字段是否按照顺序创建好了索引。
  2. 如果查询字段不在索引中可能会产生回表操作导致filesort,降低性能。
  3. 一定要有过滤字段,否则不能使用索引。
  4. 排序字段和索引顺序不一致会导致filesort
  5. 多个字段排序时,如果方向不一致也会导致filesort
  6. 使用explain观察查询类型和索引利用情况。
  7. 尽可能减少不必要的filesort

1.1.3 最左前缀法则示例

假设index(a, b, c)

Where子句索引是否被使用
where a = 3Y,使用到a
where a = 3 and b = 5Y,使用到a,b
where a = 3 and b = 5 and c = 4Y,使用到a,b,c
where b = 3 或者 where b = 3 and c = 4 或者where c = 4N
where a = 3 and c = 5使用到a,但是c不可以,b的地方断了
where a = 3 and b > 4 and c = 5使用到a和b,c不能用在范围之后,在b之后就已经断了
where a is null and b is not nullis null支持索引,但是is not null不支持,所以a可以使用索引,但是b不一定能使用上索引 (8.0上的情况)
where a <> 3不能使用索引
where abs(a) == 3不能使用索引
where a = 3 and b like ‘kk%’ and c = 4Y,使用到a,b,c
where a = 3 and b like ‘%kk’ and c = 4Y,只用到a
where a = 3 and b like ‘%kk%’ and c = 4Y,只用到a
where a = 3 and b like ‘k%kk%’ and c = 4Y,使用到a,b,c

1.1.4 使用索引的一般性建议

  1. 对于单键索引,尽量选择过滤性更好的索引(例如:手机号,邮件,身份证)
  2. 在选择组合索引的时候,过滤性最好的字段在字段索引顺序中,位置越靠前越好
  3. 选择组合索引时,尽量包含where中更多字段的索引
  4. 组合索引出现范围查询时,尽量把这个字段放在索引次序的最后面。
  5. 尽量避免造成索引失效的情况。

1.1.5 索引失效的情况

  1. 计算和函数会导致索引失效
// 索引失效
select * from emp where left(emp.name, 3) = 'abc';
  • 1
  • 2
  1. like以%开头,索引失效
// 索引失效
select * from emp where name like '%ab%';
  • 1
  • 2
  1. 不等于(!=或者<>)索引失效
select sql_no_cache * from emp where emp.name <> 'abc';
  • 1
  1. is not null 和 is null
    看情况,mysql 8.0之前is not null是不能用索引的。
  2. 类型转换导致索引失效
// name是字符串类型的
select * from emp where name = 123
  • 1
  • 2
  1. 数据库优化器觉得不用索引更快的时候索引会失效。

1.2 数据库索引

1.2.1 原理

  1. InnoDB使用了B+树实现索引,在查找时可以达到log(n)的时间复杂度
  2. 聚簇索引记录了主键ID等完整数据;非聚簇索引的索引树中记录索引字段和主键,如果需要完整的数据的话需要回表操作(即使用主键去聚簇索引中再次查找完整数据)
  3. 索引的叶子节点以链表的形式存储,方便顺序查找和排序。

1.2.2 索引的缺点

  1. 占用空间
  2. 更新时会级联更新索引
  3. 高并发写影响性能。

1.2.3 优化数据库的方法

  1. 硬件,主要是存储层优化(使用企业级SSD)
  2. 网络,或者操作系统的tcp等的配置
  3. 表结构优化
  4. sql优化, where条件要符合索引等等。
  5. 减少函数的使用
  6. 索引优化
  7. 大字段和全文检索优化
  8. 连接池的优化(参数调优)
  9. 事务优先(行锁和表锁)
  10. 数据库集群化
  11. 加入缓存
  12. 冷热分离
  13. 分库分表

1.2.4 MySQL一张1000w条记录的表,索引的高度是多少

Mysql页默认是16KB,假设主键加上页内链表指针一共是16字节(二者分别都是8字节),则一页可以保存1000条数据,三层就是1000 * 1000 * 1000 = 10亿 条数据。

第三层为叶子页,假设每页保存不低于10条,三层就可以容纳1000万数据。

12.5 B树和B+树的区别

https://www.cnblogs.com/JCpeng/p/15231338.html

1.3 事务

1.3.1 MVCC

https://juejin.cn/book/6844733769996304392/section/6844733770071801870

1.4 数据库范式

1NF:字段拆分到不可拆分为止
2NF:每一行数据的唯一性
3NF:表之间关联主键依赖

降低耦合度,节省存储空间

2 开源框架

2.1 Spring框架

2.1.1 SpringBoot与Spring区别

  1. Spring 框架就是由框架来帮你管理这些对象,包括它的创建,销毁等。 Spring框架还是体现高内举,低耦合的。Spring Framework 本身并未提供太多具体的功能,它主要专注于让你的项目代码组织更加优雅,使其具有极好的灵活性和扩展性,同时又能通过Spring集成业界优秀的解决方案
  2. SpringMVC主要用于开发WEB应用和网络接口,它是Spring的一个模块,通过Dispatcher Servlet, ModelAndView 和 View Resolver,让应用开发变得很容易。
  3. SpringBoot目的在于实现自动配置,降低项目搭建的复杂度。
  4. 最后一句话总结:Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的一个 MVC 框架,而Spring Boot 是基于Spring的一套快速开发整合包
    在这里插入图片描述

2.1.2 SpringBoot如何管理版本依赖。什么是自动配置,起步依赖

2.1.2.1 SpringBoot如何管理版本依赖
  1. 使用maven进行版本管理,通过控制pom.xml父子关系来完成细节配置,在父pom中定义具体框架版本号
  2. 提供了很多的spring-boot-starter的pom.xml来标准化的引入依赖避免冲突
2.1.2.2 自动配置
  1. Spring提供了默认配置,不需要把框架的每一个属性都配置一遍。可以简化开发。
  2. SpringBoot通过在启动类上面添加@SpringBootApplication注解来完成自动配置。
  3. 内部完成了读取每一个jar包下的META-INF/spring.factories和spring-boot-autoconfigure-2.6.7.jar中的默认配置。
2.1.2.3 起步依赖

指的是各种starter,重点是pom.xml,其中包含了框架所需要的其他依赖和默认配置文件,不需要使用方手动配置了。

2.1.3 Spring三级缓存

构造注入的方式下会产生循环依赖的问题。 AB循环依赖问题只要A的注入方式是Set注入,就不会有循环依赖问题。

Spring容器循环依赖报错是BeanCurrentlyInCreationException。

默认的单例场景是支持循环依赖的解决的,不会报错。 原型场景不支持循环依赖,会报错。

三级缓存就是DefaultSingletonBeanRegistry类中的三个map。一级缓存(也叫单例池)singletonObjects:存放已经经历了完整生命周期的bean对象。二级缓存earlySingletonObjects,存放早期暴露出来的bean对象,Bean的生命周期未结束(属性还未填充完毕)。第三级缓存singletonFactories,存放可以生成bean的工厂。

2.1.3.1 A/B两对象在三级缓存中的迁移说明

1 A创建的过程中需要B,于是将A放到三级缓存中,去实例化B
2 B实例化的时候发现需要A,于是B先去查一级缓存,没有再查二级缓存,还没有,查询三级缓存,在三级缓存中找到了A,然后把三级缓存中的A挪到了二级缓存中。
3 B初始化完毕,将自己放到一级缓存中(此时B里面的A仍然是创建中的状态)。然后回来接着创建A,此时B已经创建结束,直接可以从单例池中获取到B,然后完成创建。并将A挪到一级缓存里面。

关键的四个方法:
getSingleton
doCreateBean
populateBean
addSingleton

详细过程的文字描述:
1 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
2 在getSingleton()方法中,从一级缓存中查找,没有就返回null
3 doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
4 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的createBean方法。
5 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断:是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否在第4步的集合中)。判断为true则将beanA添加到【三级缓存】中(缓存中放的是FactoryBean)
6 对beanA进行属性填充,此时检测到beanA依赖beanB,于是开始查找beanB
7 调用doGetBean()方法,和beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性。
8 此时beanB依赖beanA,调用getSingleton()获取beanA,依次从一级、二级和三级缓存中查找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面流程中doCreateBean()方法中实例化的beanA
9 这样beanB就可以依赖beanA了,beanB顺利完成了初始化,并将beanA从三级缓存中升级到了二级缓存。
10 beanA继续完成它的属性填充工作,此时也获取到了beanB,beanA完成了创建,回到getSingleton方法中继续执行,将beanA从二级缓存中移动到一级缓存中。

2.1.4 Spring各种通知的顺序

【Spring4版本,对应SpringBoot 1.x,正常业务逻辑执行】
环绕通知(前)
Befor通知
业务逻辑
环绕通知(后)
Atfter通知
AtfterReturning通知

【Spring4版本,对应SpringBoot 1.x,出现异常的情况】
环绕通知
Before
After
AfterThrowing

【Spring5版本,对应SpringBoot 2.x,正常业务逻辑执行】
环绕通知
before
业务逻辑
AtfterReturning通知
After
环绕通知

【Spring5版本,对应SpringBoot 2.x,异常业务逻辑执行】
环绕通知
Before
AfterThrowing
After

try {
	@Before
	method.invoke(obj, args);
	@AfterReturning
} catch () {
	@AfterThrowing
} finaly {
	@After
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.2 Mybatis框架

3 JUC

3.1 concurrentHashMap的key能不能为空?HashMap的key呢?如果HashMap的key为null,是怎么存储的呢?

ConcurrentHashMap不允许插入为null的key\value,get也不可以,会包nullpointerException。

hashtable,concurrenthashmap它们是用于多线程的,并发的 ,如果map.get(key)得到了null,不能判断到底是映射的value是null,还是因为没有找到对应的key而为空,而用于单线程状态的hashmap却可以用containKey(key) 去判断到底是否包含了这个null。

hashtable为什么就不能containKey(key) ??因为一个线程先get(key)再containKey(key),这两个方法的中间时刻,其他线程怎么操作这个key都会可能发生,例如删掉这个key

3.2 单例模式和线程安全的单例模式

  • 仅用于单线程下的
public class Singeton
{
    private Singeton()
    {

    }

    private static Singeton singeton = null;

    public static Singeton getInstance()
    {
        if (singeton == null)
            return new Singeton();
        else
            return singeton;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 利用静态构造函数
/**
 * 利用静态构造函数
 */
public class Singeton
{
    private Singeton()
    {

    }

    private static Singeton singeton = new Singeton();

    public Singeton getSingeton()
    {
        return singeton;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.3 wait和notify的注意事项

wait和notify方法必须要在同步块或者方法里面使用,并且要成对出现。 否则会爆出IllegalMonitorStateException
先wait后notify才ok。

await和signal方法也是有着同样的问题。

线程必须要先获得并持有锁,必须在锁块(synchronized或者lock)中;
必须要先等待后唤醒。

3.4 LockSupport

3.4.1 LockSupport是什么

通过park和unpark方法实现阻塞和唤醒线程的操作。 所有的方法都是静态方法。调用的都是Unsafe类里面的native方法。

LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于是1,0的开关,默认是0;调用一次unpark就会++变成1;调用一次park就会消费permit,permit–,同时park方法返回;如果调用park的时候,permit已经是0了,会阻塞。

每个线程都有一个相关的permit,permit最大值为1,重复调用unpark也不会累积凭证。

3.4.2 为什么可以先唤醒线程然后阻塞线程

因为unpark获得了凭证,之后再调用park方法,park方法可以名正言顺地凭证消费,故不会阻塞。

3.4.2 为什么唤醒两次后阻塞两次,但是最终结果还是会阻塞线程

因为凭证数量最多为1,连续调用两次unpark和调用一次unpark的效果是一样的,只会增加一个凭证;但是调用两次park需要消费两个凭证,证不够,不能放行。

4 JVM

4.1 JVM内存布局中,为什么会区分新生代和老年代,对于新生代,为什么又要区分Eden区和Suvivor区?

  1. 主流垃圾回收器都使用了分代收集算法。
  2. 大量的临时对象会被放置在新生代。新生代满了之后会把有效对象复制到老年代。
  3. 为了更好的区分哪些对象应该被复制到老年代,新生代再次划分eden区和两个survivor区。
  4. 细化后的新生代内部被再次划分以便保证高速读写同时内存连续。
    (1) 新对象会被保存到eden区(开始是连续空内存),eden区满了之后会把对象保存到s0(from区在开始的时候也是连续空内存)
    (2)清空eden区
    (3)s0和s1在命名上互换,原来的s1等待写入。
    (4)eden区再次满了,重复上面的步骤。
  5. eden区通过复制-清除算法保证了内存的连续性。

5 中间件

5.1 Redis

5.1.1 基本使用

【查看版本】
redis-server -v
进入到redis里面可以使用info命令
【大小写】
redis命令是不区分大小写的,但是key是区分大小写的。
【查询使用】
help@类型名

5.1.2 基本的五种数据类型

【String】

  1. 最常用 set key value, get key
  2. 同时设置或者获取多个键值对
mset key value key value ...
meget key key ...
  • 1
  • 2
  1. 数值增减

  2. 获取字符串长度

  3. 分布式锁

  4. 应用场景
    商品编号、订单号采用incr命令生成。
    点赞、喜欢等功能。

【hash】
此种数据结构类似于java中的Map<String, Map<Object, Object>>
简单的购物车可以用这个搞定。

【list】
一个人订阅的微信公众号的列表
【set】
抽奖程序
微信朋友圈点赞
微博好友关注的社交关系
QQ推送可能认识的人
【zset】
对商品的销售情况进行排序显示
抖音热搜

5.1.3 分布式锁

  1. 考虑即使是单机版的情况下,多线程的线程安全有保证么?
    JUC的内容

sychronized:一直要等到锁,容易导致线程的积压。
Lock:有tryLock的功能,更加灵活。

  1. 分布式锁
    setnx
  2. 所有的锁的释放都要放在finally代码块里面
  3. 如果部署了微服务jar包的机器挂了,代码层面根本没有走到finally代码块里面,没有办法保证分布式锁的释放,使用的key没有被删除。那么我们需要加入一个过期时间限定的key。
  4. 设置过期时间的语句必须要具有原子性,防止上锁了,但是时间还没有设置。
private static String REDIS_LOCK = "xxx";
String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
// 此语句本身就具有原子性。
Boolean lockSucess = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
  • 1
  • 2
  • 3
  • 4
  1. 必须确保释放的是自己的锁
finally {
	if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)) {
		stringRedisTemplate.delete(REDIS_LOCK);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 保证释放锁的原子性
    Redis需要使用Lua脚本
 Jedis jedis = RedisUtils.getJedis();
 String script = "xxx, 这个脚本要从Redis的官网上面去找";
 try {
 	Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
 	if ("1".equals(o.toString())) {
 		System.out.println("----------delete redis lock ok.");
 	} else {
 		System.out.println("----------delete redis lock error.");
 	}
 } finally {
	 if (jedis != null) {
	 	jedis.close();
	 } 
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

可以使用Redis自身的事务

while (true) {
	// 设置哨兵
	stringRedisTemplate.watch(REDIS_LOCK);
	if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)) {
		// 启用事务
		stringRedisTemplate.setEnableTransactionSupport(true);
		// 开始事务
		stringRedisTemplate.multi();
		stringRedisTemplate.delete(REDIS_LOCK);
		// 执行事务,返回事务的执行结果
		List<Object> list = stringReidsTemplate.exec();
		if (list = null) {
			continue;
		}
	}
	stringRedisTemplate.unWatch(REDIS_LOCK);	
	break;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  1. 如何确保lock的存续时间确实大于业务逻辑的执行时间
    Redis锁如何续期的问题。
    Redis集群是一个AP类型的集群,一致性得不到保证。

  2. 最终可以使用redlock的RedisSon
    但是解锁的时候需要注意,再度做一些判断。

if (redissonLock.isLocked()) {
	if (redissonLock.isHeldByCurrentThread()) {
		redissonLock.unlock();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5

5.1.4 Redis缓存过期淘汰策略

【查看和设置Redis的最大占用内存】

查看配置文件redis.conf;也可以通过info memory命令进行查看

如果要设置最大内存,可以修改配置文件maxmemory 104857600; 也可以通过命令进行设置config set maxmemory xxx.

如果没有配置最大内存,或者最大内存设置的是0,那么在64为操作系统上不限制内存大小,如果在32位机上最多使用3GB内存。

一般推荐设置为物理内存的四分之三大小。

【Redis内存用满了】
会出现OOM的报错。

【删除策略】
定时删除:对CPU不友好,用时间换取空间。
惰性删除:在访问数据的时候进行数据的删除,如果访问的时候数据已经过期了,就删除掉。 对内存不友好,用空间换取时间。
定期删除:

【内存淘汰策略】
noeviction: 不会驱逐任何key
allkeys-lru:对所有的key通过lru算法进行删除
volatile-lru:对所有设置了过期时间的key使用lru算法进行删除。
allkeys-random:对所有的key随机删除
volatile-random:对所有设置了过期时间的key随机删除
volatile-ttl:删除马上要过期的key
allkeys-lfu:对所有的key使用lfu算法进行删除。
volatile-lfu:对所有设置了过期时间的key使用lfu算法进行删除。

两个维度:过期键中筛选;所有key中筛选
四个方面:LRU、LFU、random和ttl

命令修改
配置文件修改

【LRU算法】
leetcode lru-cache
现有的数据结构:LinkedHashMap

5.2 消息队列

6 分布式

5.1 常见的远程过程调用

  1. 自定义协议实现C/S RPC调用(实现复杂,需解决很多问题)
  2. UDP广播(常用语即时通讯,速度快,但是需要二次确认)
  3. 基于http协议调用(RESTful风格api,SpringCloud微服务调用使用的就是http)
  4. dubbo协议(阿里出品,长链接,封装二进制数据,性能较高)
  5. soap协议(古老的webservice系列框架,基于xml实现数据封装,http协议传输)
  6. 异步响应式(WebFlux + Spring Data Reactive)
  7. 服务器推(不主动调用,常见系统通知类)

5.2 类似于远程过程调用的这种有外部衔接的方法需要注意哪些问题?请写出注意问题和伪代码

  1. 写好文档,方便后续维护
  2. 统一的报文结构
  3. 标准的服务状态
status_code 200; // 请求成功
  • 1
  1. 统一的请求日志记录和异常记录
  2. 当请求延迟过高,快速失败
  3. 重试机制
serverList = {server1, server2, server3};
retryCount = 0;
retryMax = 10;
maxRequestTime = 200ms;

public Object getXXOO() {
	try {
		// 尝试请求数据
		callData(serverList);
	} catch (Exception ex) {
		// 拦截器记录日志
		serverList.remove(请求过的服务器);
		if (retryCount == retryMax) {
			return error;
		}
		retryCount++;
		// 递归调用本方法
		getXXOO();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  1. 事务问题,事务回滚;包括分布式事务
  2. 数据一致性问题;分布式锁

6 Java语言

6.1 布尔类型占几个字节?为什么占这么多字节?

“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。

显然第三条是更准确的说法,那虚拟机为什么要用int来代替boolean呢?为什么不用byte或short,这样不是更节省内存空间吗。大多数人都会很自然的这样去想,我同样也有这个疑问,经过查阅资料发现,使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。

6.2 java字符串常量池

6.2.1 说出程序的输出

public class StringPoolDemo {
    public static void main(String[] args) {
        String str1 = new StringBuilder("58").append("tongcheng").toString();
        System.out.println(str1);
        System.out.println(str1.intern());
        System.out.println(str1 == str1.intern());

        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2);
        System.out.println(str2.intern());
        // java 1.8输出false,open jdk 17 输出为true
        System.out.println(str2 == str2.intern());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

6.2.2 源码分析

  1. 去看System类的开头的初始化的地方

jdk 1.8:

public final class System {
    /* Register the natives via the static initializer.
     *
     * VM will invoke the initializeSystemClass method to complete
     *  the initialization for this class seperated from clinit.
     * Note that to use properties set by the VM, see the constriants
     * described in the initializeSystemClass method.
     */
    private static native void registerNatives();
    static {
        registerNatives();
    }

    /** Don't let anyone instantiate this class */
    private System() {
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这个是openjdk 17:

public final class System {
    /* Register the natives via the static initializer.
     *
     * The VM will invoke the initPhase1 method to complete the initialization
     * of this class separate from <clinit>.
     */
    private static native void registerNatives();
    static {
        registerNatives();
    }

    /** Don't let anyone instantiate this class */
    private System() {
    }
   }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  1. 去查看System中的初始化函数

JDK 1.8:

...
sun.misc.Version.init();
...
  • 1
  • 2
  • 3

这个是openjdk 17:

    /**
     * Initialize the system class.  Called after thread initialization.
     */
    private static void initPhase1() {

        // register the shared secrets - do this first, since SystemProps.initProperties
        // might initialize CharsetDecoders that rely on it
        setJavaLangAccess();

        // VM might invoke JNU_NewStringPlatform() to set those encoding
        // sensitive properties (user.home, user.name, boot.class.path, etc.)
        // during "props" initialization.
        // The charset is initialized in System.c and does not depend on the Properties.
        Map<String, String> tempProps = SystemProps.initProperties();
        VersionProps.init(tempProps);

		// others
		...
   }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  1. 去看Version类
    JDK 1.8:
public class Version {
	    private static final String launcher_name =
        "java";
}
  • 1
  • 2
  • 3
  • 4

这个是openjdk 17:

class VersionProps {

    private static final String launcher_name =
        "openjdk";
  • 1
  • 2
  • 3
  • 4

由此观之,在17上,会有如下效果:

String str3 = new StringBuilder("open").append("jdk").toString();
System.out.println(str3);
System.out.println(str3.intern());
// 这个的输出是false,VersionProps 加载的时候,openjdk就已经加载到常量池里面了。
System.out.println(str3 == str3.intern());
  • 1
  • 2
  • 3
  • 4
  • 5

数据库中的并发一致性问题

  • 丢失修改
    T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EUA4uXML-1687163385945)(null)]
  • 读脏数据
    T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eZjEYIvi-1687163385924)(null)]
  • 不可重复读
    T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o7LDRhqR-1687163385911)(null)]
  • 幻影读

T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oAdgMfmp-1687163385995)(null)]
产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。

数据库中的事物隔离级别

  • 未提交读(read uncommitted)
    事务中的修改,即使没有提交,对其它事务也是可见的
  • 提交读(read committed)
    一个事务智能读取已经提交的事务所做的修改。即,一个事务所做的修改在提交之前对于其它事务来说是不可见的。
  • 可重复读(repeatable read)
    保证同一个事务中多次读取同样的数据的结果是一样的。
  • 可串行化
    强制事务串行执行
隔离级别脏读不可重复读幻影读加锁读
未提交读
提交读
可重复读
可串行化

mybatis中的#{}和${}区别

  1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by “111”, 如果传入的值是id,则解析成的sql为order by “id”.

    $将传入的数据直接显示生成在sql中。如:order by u s e r i d user_id userid,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.

  2. #方式能够很大程度防止sql注入。$方式无法防止Sql注入。

5.KaTeX parse error: Expected 'EOF', got '#' at position 32: …传入表名.    6.一般能用#̲的就别用.

MyBatis排序时使用order by 动态参数时需要注意,用$而不是#

Http的错误码

500 内部错误 — 因为意外情况,服务器不能完成请求。
304 未修改 — 未按预期修改文档。
302 已找到 — 请求的数据临时具有不同 URI。

如果我们自己写一个包名和类名跟jdk中的类完全一样,那么类加载器会加装这个类吗?

不能自己写以"java."开头的类,其要么不能加载进内存,要么即使你用自定义的类加载器去强行加载,也会收到一个SecurityException。

JVM是怎么进行垃圾回收的

  1. 针对方法区和堆,这两个地方是线程间共享的。程序计数器,虚拟机栈,本地方法栈都是线程内私有的,线程消失之后,也随之消失。
  2. 如何判断一个对象可不可以被回收。引用计数法和可达性分析法。
  3. 垃圾收集算法:标记-清除,标记-整理,复制,分代收集。
  4. 有七种垃圾收集器,现在都是多种垃圾收集器组合使用。

冒泡排序

public class BubbleSort
{
    public static void main(String[] args)
    {
        int[] arr = {1,1,2,0,9,3,12,7,8,3,4,65,22};
        bubbleSort(arr,arr.length);
        printArr(arr);
    }

    public static void printArr(int[] a)
    {
        for (int element : a)
        {
            System.out.print( element + " ");
        }
        System.out.println();
    }

    public static void bubbleSort(int[] a,int n)
    {
        for(int i=0;i<n;i++)
        {
            for (int j=1;j<n-i;j++)
            {
                if(a[j-1] > a[j])
                {
                    int temp = a[j-1];
                    a[j-1] = a[j];
                    a[j] = temp;
                }
            }
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

如何实现nginx动静态分离

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/902488
推荐阅读
相关标签
  

闽ICP备14008679号