java面试题

gpt4.0链接 https://api.fukaluosi.online/

索引失效情况

参考这个https://cloud.tencent.com/developer/article/1992920

 1. 联合索引不满足最左匹配原则
 2. 使用了select *
 3. 索引列参与运算或使用了函数
 4. 模糊查询时(like语句),模糊匹配的占位符位于条件的首部。
 5. 参数类型与字段类型不匹配,导致类型发生了隐式转换,索引失效。
 6. 错误使用OR 
 			  1:查询条件使用or关键字,其中一个字段没有创建索引,则会导致整个查询语句索引失效;
              2:or两边为“>”和“<”范围查询时,索引失效。
 8. 两列数据做比较,即便两列都创建了索引,索引也会失效。
 9. 查询条件使用不等进行比较时,需要慎重,普通索引会查询,结果集占比较大时索引会失效。
 10. 查询条件使用is null时正常走索引,使用is not null时,不走索引。
 11. 查询条件使用not in时,如果是主键则走索引,如果是普通索引,则索引失效。
 12. 当查询条件涉及到order by、limit等条件时,是否走索引情况比较复杂,而且与Mysql版本有关,通常普通索引,如果未使用limit,则不会走索引。order by多个索引字段时,可能不会走索引。其他情况,建议在使用时进行expain验证。
 13. 当查询条件为大于等于、in等范围查询时,根据查询结果占全表数据比例的不同,优化器有可能会放弃索引,进行全表扫描。

如何查看语句是否走了索引?

查看SQL语句执行计划,一般我们都采用explain关键字,通过执行结果来判断索引使用情况。

链接中此部分内容在数据库版本及执行计划标题下面(或者直接ctrl+ f “一般我们都采用explain关键字”)

参考这个https://cloud.tencent.com/developer/article/1992920

覆盖索引了解嘛

覆盖索引是select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖。

参考链接:https://blog.csdn.net/qq_36551991/article/details/106594768

Redis击穿 穿透 雪崩 以及如何解决

缓存击穿
定义:缓存中某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易被高并发的请求冲垮,这就是缓存击穿问题。
方案:
	1.设置逻辑过期,获取数据时判断数据是否过期,未过期直接返回数据,如果过期返回脏数据且更新缓存。
	2.互斥锁,保证同意书见只有一个线程更新缓存,未获得锁的线程返回空或者默认值。
缓存雪崩
定义:当大量缓存数据在同一时间过期(失效)或者Redis故障宕机时,如果此时有大量的用户请求,都无法在Redis中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。
方案:
	1.大量的数据设置不同的过期时间,避免同时过期。
	2.给缓存业务添加降级限流策略。
	3.搭建高可用redis集群。
缓存穿透
定义:当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据。那么此时大量的请求都落在数据库中,导致数据库压力骤增,这就是缓存穿透问题。
处理:
	1.进行参数校验,对于不合法的参数请求直接抛出异常返回给客户端。
	2.缓存空对象。
	3.使用布隆过滤器
数据库的事务特性?

简称为:ACID

原子性、一致性、隔离性、持久性
常见的事务失效的场景

参考链接 https://blog.csdn.net/hanjiaqian/article/details/120501741

  • 访问权限问题(spring事务默认生效的方法权限都必须为public)
  • 方法用 final 修饰
  • 方法内部调用(在某个 Service 类的某个方法中,调用另外一个事务方法)
  • 添加@Transactional注解的方法未被spring管理
  • 多线程调用(在事务的方法内用另外一个线程执行操作,如果单开的线程中出现错误,原方法也不能回滚)
  • 表不支持事务
  • 未开启事务
  • 未抛出异常或者抛出的异常不正确(spring 事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误)

哪些场景不适合添加索引

 - 经常增删改的表的字段不适合建立索引
 - 表记录太少 
 - where条件中用不到的字段不适合建立索引 
 - 表数据可以确定比较少的不需要建索引
 - 数据重复且发布比较均匀的的字段不适合建索引(唯一性太差的字段不适合建立索引)
 - 参与列计算的列不适合建索引

添加索引是如何提高查询速度的

gpt4.0回答

数据库通过添加索引来提高查询速度,主要是因为索引可以大大减少数据库搜索的数据量,从而减少了访问磁盘的次数,加快了查询速度。这个过程可以通过以下几个方面来理解:

  1. 索引的本质: 索引是对数据库表中一列或多列的值进行排序的一种结构,可以被数据库搜索算法高效利用。常见的索引结构包括B树(及其变种B+树)、哈希表等。这些结构使得数据的查找效率可以从线性时间降低到对数时间或接近常数时间。

  2. 减少检索数据量: 当没有索引时,数据库执行查询操作(尤其是查找、排序操作)需要进行全表扫描,即逐行检查表中的数据,直到找到所需的信息。而如果表中数据量非常大时,这将是一个时间复杂度为O(N)的操作。有了索引后,数据库可以利用索引结构直接定位到需要的数据,而无需扫描整个表,这样就大大减少了需要检索的数据量。

  3. 提高数据访问速度: 索引结构(如B+树)通常设计为多层次的树形结构,根节点在内存中,这样就可以快速定位到需要查询的数据所在的磁盘位置。与磁盘I/O操作相比,内存操作的速度要快得多。因此,使用索引可以显著减少数据访问的时间。

  4. 优化排序与连接操作: 索引不仅可以提高查询速度,还可以优化数据库的排序操作和连接操作(JOIN)。对于排序操作,有序的索引可以直接用于输出排序结果,避免了昂贵的排序操作。对于连接操作,索引可以快速匹配参与连接的表中的相关列,减少了连接操作的复杂度。

然而,索引也并非没有代价。索引需要占用额外的存储空间,同时也会增加插入、删除和更新数据时的开销,因为这些操作不仅要修改数据本身,还要更新索引结构。因此,在实际应用中,需要根据具体情况权衡是否添加索引以及如何设计索引策略。

其他回答

参考链接 https://blog.csdn.net/tongdanping/article/details/79878302

回表了解吗?/是什么是回表

gpt4.0回答

gpt4.0链接 https://api.fukaluosi.online/

MySQL中的"回表"是一个和索引相关的术语,主要出现在InnoDB存储引擎中对查询进行优化的过程里。要理解回表,我们首先需要了解MySQL中有两大类索引:聚集索引(Clustered Index)和非聚集索引(Secondary Index,也叫辅助索引)。聚集索引决定了表中行的物理存储顺序,而非聚集索引则是指向聚集索引的指针。

聚集索引
在InnoDB中,表是按照聚集索引的顺序存储的,也就是说,表中的数据行是按照聚集索引键的顺序物理存储的。如果一张表有主键,那么这个主键就是聚集索引;如果没有显式定义主键,MySQL会自动选择一个唯一索引作为聚集索引;如果这样的唯一索引也不存在,InnoDB会自动生成一个隐藏的行ID作为聚集索引。

非聚集索引
非聚集索引,或称为辅助索引,是基于非主键列建立的索引。它们不影响数据的物理存储顺序,但可以提高访问这些非主键列的速度。在非聚集索引中,每个索引项包含索引列的值和该行数据在聚集索引中的位置信息。

回表操作
当使用非聚集索引进行查询时,MySQL可以快速定位到聚集索引的位置,但如果查询的列不完全包含在非聚集索引中,MySQL需要进行额外的操作来获取这些数据,这个过程就叫做"回表"。

例如,考虑一个包含ID(主键,即聚集索引)、姓名、年龄和地址的表。如果我们创建了一个仅包含姓名的非聚集索引,当我们执行一个同时查询姓名和地址的查询时,MySQL会首先使用非聚集索引快速找到对应的姓名,但因为地址信息不包含在这个索引中,它需要通过非聚集索引找到行的聚集索引位置,然后再回到数据文件中获取完整的行数据包括地址,这个过程即为"回表"。

回表操作是一个相对开销较大的过程,因为它可能导致额外的磁盘I/O,特别是在大表上。因此,在设计索引时,一个常用的优化技巧是尽量使用覆盖索引(Covering Index),即查询所需的所有列都包含在非聚集索引中,这样可以避免回表操作,提高查询效率。
回表(英文名:"Lookup" 或 "Ref")是数据库中一种查询优化方式,通常用于处理使用非聚集索引的查询。
当使用非聚集索引查询时,数据库引擎首先通过该索引找到匹配的行,然后使用行指针(Row Pointer)到表中查找相应的行数据。这个过程中就需要进行一次额外的访问(即“回表”),以获取完整的行数据。

如何实现多线程

参考链接: https://blog.csdn.net/PassionAnytime/article/details/126143524

1.继承 Thread 类

创建 MyThread 类,让其继承 Thread 类并重写 run() 方法。
创建 MyThread 类的实例对象,即创建一个新线程。
调用 start() 方法,启动线程。

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("我是通过继承 Thread 类创建的多线程,我叫" + Thread.currentThread().getName());
    }
}

class TestMyThread {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.setName("Thread-1");
        MyThread myThread2 = new MyThread();
        myThread2.setName("Thread-2");
        MyThread myThread3 = new MyThread();
        myThread3.setName("Thread-3");

        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}
二、实现 Runnable 接口

创建 MyRunnable 类实现 Runnable 接口。
创建 MyRunnable 类的实例对象 myRunnable 。
把实例对象 myRunnable 作为参数来创建 Thread 类的实例对象 thread,实例>对象 thread 就是一个新线程。
调用 start() 方法,启动线程。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我是通过实现 Runnable 接口创建的多线程,我叫" + Thread.currentThread().getName());
    }
}

class TestMyRunnable {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}
三、实现 Callable 接口

创建 MyCallable 类实现 Callable 接口。
创建 MyCallable 类的实例对象 myCallable。
把实例对象 myCallable 作为参数来创建 FutureTask 类的实例对象 futureTask。
把实例对象 futureTask 作为参数来创建 Thread 类的实例对象 thread,实例对象 thread 就是一个新线程。
调用 start() 方法,启动线程。

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int a = 6;
        int b = 9;
        System.out.println("我是通过实现 Callable 接口创建的多线程,我叫" + Thread.currentThread().getName());
        return a + b;
    }
}

class TestMyCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("返回值为:" + futureTask.get());
    }
}


四、线程池

1,Executors.newCachedThreadPool()
2.Executors.newFixedThreadPool(3)
3,Executors.newScheduledThreadPool(3)
4,newSingleThreadExecutor()
5.Executors.newWorkStealingPool()

参考链接: https://blog.csdn.net/PassionAnytime/article/details/126143524

线程池了解吗

参考链接 https://developer.baidu.com/article/detail.html?id=3038817

并发和并行的区别

gpt 4.0回答
并发(Concurrency)和并行(Parallelism)是计算机科学中常用来描述多任务处理的概念,它们在字面上很容易混淆,但实际上有着明显的不同。

并发(Concurrency)
并发是指系统具有处理多个任务的能力,但这并不意味着这些任务同时执行。在单核CPU的环境下,CPU通过在任务之间迅速切换(上下文切换),给人一种似乎是同时处理多个任务的错觉。因此,并发关注的是多个任务的启动、执行和完成的顺序,使得多个任务看似在“同时”执行,实际上从宏观角度是的,但从微观角度看,这些任务可能并没有在同一时刻被处理。

并发的关键概念包括但不限于线程、进程、协程、任务切换和响应时间优化。

并行(Parallelism)
并行处理是指多个处理器或多核处理器同时处理多个任务。在这种情况下,任务确实是在同一时刻被执行。并行执行的目的是通过同时使用多个计算资源来减少程序的执行时间。它适用于那些可以被分解为可以独立执行的多个子任务的问题。

并行计算的关键要素包括数据分割、任务分配和同步。并行处理要求硬件支持,如多核处理器、多处理器系统或分布式计算系统。

并发与并行的区别
资源利用:并发关注如何有效地使用单个处理资源(比如一个CPU核),以实现多任务管理。并行关注的是如何协调多个处理资源,以同时处理多个任务。
执行时间:并发执行可能涉及到任务之间的切换,这可能会引入一些开销,但它使得每个任务都能获得进展。并行执行旨在通过同时执行任务来减少总的执行时间。
硬件要求:并发不一定需要多核或多处理器系统;它可以在单核处理器上实现。而并行执行通常需要多核/多处理器系统或分布式系统。
应用场景:并发适用于需要快速响应多个任务的场景,而并行适用于对大量数据进行处理或解决大规模问题的场景,其中任务可以被拆分并且独立处理。
简而言之,并发是关于在单个处理器上有效管理多个任务,而并行是关于利用多个处理资源来同时执行多个任务。

其他答案

并发
并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。

并行
并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

参考链接 https://cloud.tencent.com/developer/article/1424249

CAS/AQS

参考下面两篇博客
https://zhuanlan.zhihu.com/p/141715040

https://blog.csdn.net/u010325193/article/details/86590169

谈一下Spring的AOP IOC的理解

在Spring框架中,AOP(面向切面编程)和IOC(控制反转)是两个非常核心的概念,它们大大提高了Java开发的效率和代码的质量。

gpt4.0回答

IOC(控制反转)

IOC是一种设计原则,可以用来减少代码之间的耦合度。在没有控制反转的情况下,我们自行在对象内部创建依赖对象。这会导致依赖关系僵硬,不便于管理和扩展。而控制反转的思想是由容器控制程序之间的依赖关系,而非传统的在类内部直接控制。

为了实现控制反转,Spring提供了一种称为依赖注入(DI)的技术。依赖注入意味着对象的依赖通过外部方式(比如配置文件或注解)提供,而不是在对象内部直接创建。这样做的好处是,可以在不修改代码的情况下,更换依赖的实现。

AOP(面向切面编程)

AOP是一种编程范式,它允许开发者将横切关注点(诸如日志、事务管理等)从业务逻辑代码中分离出来,通过预定义的方式动态地添加到程序中。简而言之,AOP可以让我们在不修改源代码的情况下,增加额外的功能。

在Spring框架中,AOP是通过动态代理实现的。根据对象的类型(接口还是类),Spring分别使用了JDK动态代理和CGLIB代理。当一个Bean被代理后,每当调用目标方法时,实际上是先通过代理对象,然后代理对象根据配置决定是否要织入增强代码(比如开启事务、记录日志等),最后再调用实际的业务方法。

参考回答 https://blog.csdn.net/gg_996633543/article/details/137228975
  • 10
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值