1 数据库
1.1 Mysql使用innodb引擎,请简述mysql索引的最左前缀如何优化oder by子句
1.1.1 关键点
- 如果排序字段不在索引列上,filesort有两种算法:mysql就要启用双路排序或者单路排序。
- 无过滤不索引
- order by非最左filesort
- 顺序错filesort
- 方向反 filesort
- 熟练使用explain,必要时使用optimizer_trace.
1.1.2 答案
- 对sql进行分析,检查必要的查询字段、过滤字段和排序字段是否按照顺序创建好了索引。
- 如果查询字段不在索引中可能会产生回表操作导致filesort,降低性能。
- 一定要有过滤字段,否则不能使用索引。
- 排序字段和索引顺序不一致会导致filesort
- 多个字段排序时,如果方向不一致也会导致filesort
- 使用explain观察查询类型和索引利用情况。
- 尽可能减少不必要的filesort
1.1.3 最左前缀法则示例
假设index(a, b, c)
Where子句 | 索引是否被使用 |
---|---|
where a = 3 | Y,使用到a |
where a = 3 and b = 5 | Y,使用到a,b |
where a = 3 and b = 5 and c = 4 | Y,使用到a,b,c |
where b = 3 或者 where b = 3 and c = 4 或者where c = 4 | N |
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 null | is null支持索引,但是is not null不支持,所以a可以使用索引,但是b不一定能使用上索引 (8.0上的情况) |
where a <> 3 | 不能使用索引 |
where abs(a) == 3 | 不能使用索引 |
where a = 3 and b like ‘kk%’ and c = 4 | Y,使用到a,b,c |
where a = 3 and b like ‘%kk’ and c = 4 | Y,只用到a |
where a = 3 and b like ‘%kk%’ and c = 4 | Y,只用到a |
where a = 3 and b like ‘k%kk%’ and c = 4 | Y,使用到a,b,c |
1.1.4 使用索引的一般性建议
- 对于单键索引,尽量选择过滤性更好的索引(例如:手机号,邮件,身份证)
- 在选择组合索引的时候,过滤性最好的字段在字段索引顺序中,位置越靠前越好
- 选择组合索引时,尽量包含where中更多字段的索引
- 组合索引出现范围查询时,尽量把这个字段放在索引次序的最后面。
- 尽量避免造成索引失效的情况。
1.1.5 索引失效的情况
- 计算和函数会导致索引失效
// 索引失效
select * from emp where left(emp.name, 3) = 'abc';
- like以%开头,索引失效
// 索引失效
select * from emp where name like '%ab%';
- 不等于(!=或者<>)索引失效
select sql_no_cache * from emp where emp.name <> 'abc';
- is not null 和 is null
看情况,mysql 8.0之前is not null是不能用索引的。 - 类型转换导致索引失效
// name是字符串类型的
select * from emp where name = 123;
- 数据库优化器觉得不用索引更快的时候索引会失效。
1.2 数据库索引
1.2.1 原理
- InnoDB使用了B+树实现索引,在查找时可以达到log(n)的时间复杂度
- 聚簇索引记录了主键ID等完整数据;非聚簇索引的索引树中记录索引字段和主键,如果需要完整的数据的话需要回表操作(即使用主键去聚簇索引中再次查找完整数据)
- 索引的叶子节点以链表的形式存储,方便顺序查找和排序。
1.2.2 索引的缺点
- 占用空间
- 更新时会级联更新索引
- 高并发写影响性能。
1.2.3 优化数据库的方法
- 硬件,主要是存储层优化(使用企业级SSD)
- 网络,或者操作系统的tcp等的配置
- 表结构优化
- sql优化, where条件要符合索引等等。
- 减少函数的使用
- 索引优化
- 大字段和全文检索优化
- 连接池的优化(参数调优)
- 事务优先(行锁和表锁)
- 数据库集群化
- 加入缓存
- 冷热分离
- 分库分表
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区别
- Spring 框架就是由框架来帮你管理这些对象,包括它的创建,销毁等。 Spring框架还是体现高内举,低耦合的。Spring Framework 本身并未提供太多具体的功能,它主要专注于让你的项目代码组织更加优雅,使其具有极好的灵活性和扩展性,同时又能通过Spring集成业界优秀的解决方案
- SpringMVC主要用于开发WEB应用和网络接口,它是Spring的一个模块,通过Dispatcher Servlet, ModelAndView 和 View Resolver,让应用开发变得很容易。
- SpringBoot目的在于实现自动配置,降低项目搭建的复杂度。
- 最后一句话总结:Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的一个 MVC 框架,而Spring Boot 是基于Spring的一套快速开发整合包
2.1.2 SpringBoot如何管理版本依赖。什么是自动配置,起步依赖
2.1.2.1 SpringBoot如何管理版本依赖
- 使用maven进行版本管理,通过控制pom.xml父子关系来完成细节配置,在父pom中定义具体框架版本号
- 提供了很多的spring-boot-starter的pom.xml来标准化的引入依赖避免冲突
2.1.2.2 自动配置
- Spring提供了默认配置,不需要把框架的每一个属性都配置一遍。可以简化开发。
- SpringBoot通过在启动类上面添加@SpringBootApplication注解来完成自动配置。
- 内部完成了读取每一个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
}
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;
}
}
- 利用静态构造函数
/**
* 利用静态构造函数
*/
public class Singeton
{
private Singeton()
{
}
private static Singeton singeton = new Singeton();
public Singeton getSingeton()
{
return singeton;
}
}
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区?
- 主流垃圾回收器都使用了分代收集算法。
- 大量的临时对象会被放置在新生代。新生代满了之后会把有效对象复制到老年代。
- 为了更好的区分哪些对象应该被复制到老年代,新生代再次划分eden区和两个survivor区。
- 细化后的新生代内部被再次划分以便保证高速读写同时内存连续。
(1) 新对象会被保存到eden区(开始是连续空内存),eden区满了之后会把对象保存到s0(from区在开始的时候也是连续空内存)
(2)清空eden区
(3)s0和s1在命名上互换,原来的s1等待写入。
(4)eden区再次满了,重复上面的步骤。 - eden区通过复制-清除算法保证了内存的连续性。
5 中间件
5.1 Redis
5.1.1 基本使用
【查看版本】
redis-server -v
进入到redis里面可以使用info
命令
【大小写】
redis命令是不区分大小写的,但是key是区分大小写的。
【查询使用】
help@类型名
5.1.2 基本的五种数据类型
【String】
- 最常用
set key value
,get key
- 同时设置或者获取多个键值对
mset key value key value ...
meget key key ...
-
数值增减
-
获取字符串长度
-
分布式锁
-
应用场景
商品编号、订单号采用incr命令生成。
点赞、喜欢等功能。
【hash】
此种数据结构类似于java中的Map<String, Map<Object, Object>>
简单的购物车可以用这个搞定。
【list】
一个人订阅的微信公众号的列表
【set】
抽奖程序
微信朋友圈点赞
微博好友关注的社交关系
QQ推送可能认识的人
【zset】
对商品的销售情况进行排序显示
抖音热搜
5.1.3 分布式锁
- 考虑即使是单机版的情况下,多线程的线程安全有保证么?
JUC的内容
sychronized:一直要等到锁,容易导致线程的积压。
Lock:有tryLock的功能,更加灵活。
- 分布式锁
setnx - 所有的锁的释放都要放在finally代码块里面
- 如果部署了微服务jar包的机器挂了,代码层面根本没有走到finally代码块里面,没有办法保证分布式锁的释放,使用的key没有被删除。那么我们需要加入一个过期时间限定的key。
- 设置过期时间的语句必须要具有原子性,防止上锁了,但是时间还没有设置。
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);
- 必须确保释放的是自己的锁
finally {
if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)) {
stringRedisTemplate.delete(REDIS_LOCK);
}
}
- 保证释放锁的原子性
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();
}
}
可以使用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;
}
-
如何确保lock的存续时间确实大于业务逻辑的执行时间
Redis锁如何续期的问题。
Redis集群是一个AP类型的集群,一致性得不到保证。 -
最终可以使用redlock的RedisSon
但是解锁的时候需要注意,再度做一些判断。
if (redissonLock.isLocked()) {
if (redissonLock.isHeldByCurrentThread()) {
redissonLock.unlock();
}
}
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 常见的远程过程调用
- 自定义协议实现C/S RPC调用(实现复杂,需解决很多问题)
- UDP广播(常用语即时通讯,速度快,但是需要二次确认)
- 基于http协议调用(RESTful风格api,SpringCloud微服务调用使用的就是http)
- dubbo协议(阿里出品,长链接,封装二进制数据,性能较高)
- soap协议(古老的webservice系列框架,基于xml实现数据封装,http协议传输)
- 异步响应式(WebFlux + Spring Data Reactive)
- 服务器推(不主动调用,常见系统通知类)
5.2 类似于远程过程调用的这种有外部衔接的方法需要注意哪些问题?请写出注意问题和伪代码
- 写好文档,方便后续维护
- 统一的报文结构
- 标准的服务状态
status_code 200; // 请求成功
- 统一的请求日志记录和异常记录
- 当请求延迟过高,快速失败
- 重试机制
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();
}
}
- 事务问题,事务回滚;包括分布式事务
- 数据一致性问题;分布式锁
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());
}
}
6.2.2 源码分析
- 去看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() {
}
}
这个是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() {
}
}
- 去查看System中的初始化函数
JDK 1.8:
...
sun.misc.Version.init();
...
这个是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
...
}
- 去看Version类
JDK 1.8:
public class Version {
private static final String launcher_name =
"java";
}
这个是openjdk 17:
class VersionProps {
private static final String launcher_name =
"openjdk";
由此观之,在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());
数据库中的并发一致性问题
- 丢失修改
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中的#{}和${}区别
-
#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如: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.
-
#方式能够很大程度防止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是怎么进行垃圾回收的
- 针对方法区和堆,这两个地方是线程间共享的。程序计数器,虚拟机栈,本地方法栈都是线程内私有的,线程消失之后,也随之消失。
- 如何判断一个对象可不可以被回收。引用计数法和可达性分析法。
- 垃圾收集算法:标记-清除,标记-整理,复制,分代收集。
- 有七种垃圾收集器,现在都是多种垃圾收集器组合使用。
冒泡排序
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;
}
}
}
}
}