面试题
- 自我介绍
- 项目用到的技术栈、项目问的比较多,一定要多看
- 三次握手四次挥手
- 缓存穿透和雪崩的原因和解决方法
- 布隆过滤器你了解吗
- mysql中sql执行流程
- sqlserver你了解吗
- 二进制文件在mysql中的作用
- undolog和redolog的作用
- java的基本数据类型
- spring中ioc和aop你了解吗
- spring怎么样解决循环依赖
- mysql中有哪些锁
- 锁的是什么东西
- 线程和进程的区别
- 线程的几种状态
- 怎么样能使线程进入阻塞状态
- wait()和sleep()的区别
- 类的加载的过程
- 说一说双亲委派机制
- int占多少字节,最大为多少
- 单例模式知道吗?单例模式里的双重检查?
- hashmap如何解决哈希冲突问题?
- https你了解吗?
- 索引失效的原因?
- 索引的数据结构为什么要用b 树?
- coding:二分查找,分析边界问题
1. 自我介绍
自我介绍主要包括三个部分:
- 个人基本信息、在校期间获得过什么编程之类的讲,奖学金之类的,在职期间做过哪些牛逼的项目,任职过项目经历、技术经理等。
- 项目中遇到过什么比较难的问题,自己是怎么解决,总之就是说一些自己有亮点的东西,有数据说数据。说白了都是自己简历中精华的提炼。
- 一定要结尾,不然面试官不知道你的自我介绍是否结束。"以上是个人的基本情况,谢谢!"
2. 项目用到的技术栈、项目问的比较多,一定要多看
项目中的技术栈一定要搞清楚,用到了xx技术,要知道为什么要用它,同时还要结合你的业务场景来说。很多人就是把之前的项目忘了,更不用说xx技术在项目中是用来干什么了。
所以,面试之前一定多看看简历中项目,不要到了面试官面前一问三不知。
3. 三次握手四次挥手
网络通信的三次握手是指在建立TCP连接时,客户端和服务器之间进行的一系列握手动作。具体步骤如下:
- 客户端向服务器发送一个SYN(同步)请求,请求建立连接。
- 服务器收到请求后,向客户端发送一个SYN-ACK(同步-确认)响应,表示接收到请求,并同意建立连接。
- 客户端收到服务器的响应后,再发送一个ACK(确认)响应,表示已经收到服务器的确认,连接建立完成。
四次挥手是指在断开TCP连接时,客户端和服务器之间进行的一系列挥手动作。具体步骤如下:
- 客户端向服务器发送一个FIN(结束)请求,表示不再发送数据。
- 服务器收到请求后,向客户端发送一个ACK响应,表示已经收到客户端的请求。
- 服务器再发送一个FIN请求,表示服务器也不再发送数据。
- 客户端收到服务器的请求后,向服务器发送一个ACK响应,表示已经收到服务器的请求,连接断开完成。
通过三次握手和四次挥手,客户端和服务器可以建立和断开连接,并保证数据的可靠传输。
4. 缓存穿透和雪崩的原因和解决方法
缓存穿透和雪崩是常见的缓存问题,它们的原因和解决方法如下:
- 缓存穿透:原因:当一个请求查询一个不存在的数据时,由于缓存中没有该数据,每次请求都会穿透缓存直接查询数据库,导致数据库压力过大。解决方法:
- 布隆过滤器:使用布隆过滤器在缓存层面进行过滤,将所有可能存在的数据哈希到一个足够大的bitmap中,不存在的数据会被快速过滤掉。
- 空值缓存:对于查询结果为空的数据,也将其缓存起来,可以设置一个较短的过期时间,避免频繁查询数据库。
- 缓存雪崩:原因:缓存中的大量数据同时过期或失效,导致大量请求直接访问数据库,造成数据库压力过大,甚至导致数据库崩溃。解决方法:
- 缓存失效时间错开:对于相同的缓存失效时间,可以在其基础上添加一个随机值,使缓存失效时间错开,避免大量缓存同时过期。
- 热点数据永不过期:对于一些热点数据,可以设置永不过期,确保这些数据一直在缓存中可用。
- 多级缓存:使用多级缓存架构,将缓存分为多个层级,不同层级的缓存设置不同的失效时间,避免缓存同时失效。
- 预热:在系统低峰期,提前加载缓存,避免在高峰期突然大量请求导致缓存失效。
- 数据更新加锁:在更新缓存时,使用分布式锁来避免多个请求同时更新缓存,保证只有一个请求更新成功。
- 限流降级:在缓存失效时,限制对数据库的直接访问量,可以通过限流或者降级策略来减少对数据库的压力。
5. 布隆过滤器你了解吗
布隆过滤器是一种用于判断一个元素是否存在于集合中的数据结构,它通过使用多个哈希函数和位数组来实现。具体来说,布隆过滤器会将每个元素通过多个哈希函数映射到位数组中的多个位置,将对应位置的位设置为1。当要判断一个元素是否存在时,布隆过滤器会对该元素进行相同的哈希映射,然后查看对应位置的位是否都为1,若都为1,则认为元素存在于集合中,若存在任一位为0,则认为元素不存在于集合中。
布隆过滤器的主要作用是在大规模数据集中快速判断一个元素是否存在,常用于缓存系统、网络爬虫、垃圾邮件过滤等场景,可以有效地减少对底层存储系统的查询压力。
布隆过滤器的优点包括:
- 空间效率高:布隆过滤器只需要使用一个位数组和多个哈希函数来表示集合,相比使用传统的哈希表或者树等数据结构,布隆过滤器的空间占用更小。
- 查询效率高:布隆过滤器通过多个哈希函数将元素映射到多个位置,所以查询一个元素只需要进行几次位操作,时间复杂度较低。
- 可扩展性好:布隆过滤器支持动态添加元素,可以根据需要进行扩展。
布隆过滤器的缺点包括:
- 存在一定的误判率:由于多个元素可能映射到同一个位,所以当判断一个元素是否存在时,存在一定的误判率,即可能将不存在的元素误判为存在。
- 不支持元素的删除:由于多个元素可能映射到同一个位,所以无法准确地删除一个元素,只能通过重新构建布隆过滤器来实现。
总的来说,布隆过滤器是一种高效的数据结构,适用于对查询效率要求较高、对误判率能够容忍的场景。
6. mysql中sql执行流程
在MySQL中,SQL执行流程通常包括以下几个步骤:
- 语法解析:MySQL首先对输入的SQL语句进行语法解析,检查其是否符合MySQL的语法规范。
- 语义分析:在语义分析阶段,MySQL会验证SQL语句中的表、列等对象是否存在,以及用户是否具有相应的权限。
- 查询优化:如果是查询语句,MySQL会对其进行优化,选择合适的执行计划来提高查询性能。这个阶段包括了索引选择、连接顺序优化、子查询优化等。
- 执行计划生成:在查询优化阶段完成后,MySQL会生成执行计划,决定如何执行SQL语句。执行计划通常是一个树形结构,包括了各个操作的顺序、方式等。
- 执行SQL语句:MySQL根据生成的执行计划开始执行SQL语句,包括从磁盘读取数据、进行过滤、排序、连接等操作。
- 返回结果:执行完SQL语句后,MySQL将结果返回给客户端。结果可以是查询结果集、影响行数等。
需要注意的是,MySQL还有一些其他的优化技术和功能,比如缓存、锁机制等,这些也会对SQL执行流程产生影响。此外,MySQL还支持批量执行SQL语句、并行执行等特性,以进一步提高执行效率。
7. sqlserver你了解吗,
SQL Server是由Microsoft开发的关系型数据库管理系统(RDBMS),用于存储和检索数据。它支持广泛的企业级应用和数据分析任务。
SQL Server和MySQL是两种不同的数据库管理系统,它们有以下几点区别:
- 开发公司和许可:SQL Server是由Microsoft开发并拥有商业许可,而MySQL是由Oracle公司拥有商业许可的开源数据库管理系统。
- 数据库规模和性能:SQL Server通常被用于大型企业级应用,它具有较大的数据处理能力和高性能特性。MySQL通常在中小型应用中使用,但也可以应对一些大型应用需求。
- 数据库功能:SQL Server提供了许多高级功能,如支持分布式事务处理、复制、数据仓库和分析服务等。MySQL提供了基本的数据库功能,但在某些高级功能上可能不如SQL Server。
- 数据库语法和工具:SQL Server使用Transact-SQL(T-SQL)作为其查询语言,而MySQL使用标准的SQL语言。此外,SQL Server提供了一套完整的集成开发环境(IDE)和管理工具,而MySQL提供了较为简单的管理工具。
需要根据具体的需求和预算来选择适合的数据库管理系统。对于大型企业级应用和需要高级功能的场景,SQL Server可能更适合;而对于中小型应用和较为简单的需求,MySQL可能更合适。
8. 二进制文件在mysql中的作用
在MySQL中,二进制文件具有以下作用:
- 存储数据:MySQL可以将二进制文件作为BLOB(Binary Large Object)类型的列存储在数据库中。这种类型的列可以用来存储任意二进制数据,如图像、音频、视频等文件。
- 备份和恢复:二进制文件可以用于备份和恢复数据库。通过将数据库的二进制日志文件备份,可以在需要时将数据库恢复到特定的时间点。
- 数据复制:MySQL的二进制日志文件(binary log)记录了数据库的所有更改操作,包括插入、更新和删除等。这些日志文件可以用于数据复制,将数据库的更改操作同步到其他MySQL实例,实现数据的复制和高可用性。
- 数据恢复:当数据库发生故障或数据丢失时,可以使用二进制日志文件进行数据恢复。通过将二进制日志文件应用到数据库中,可以将数据库恢复到故障发生之前的状态。
需要注意的是,二进制文件在MySQL中是以二进制形式存储的,无法直接查看和编辑。如果需要对二进制文件进行操作,可以通过应用程序或工具来实现。
9. undolog和redolog的作用
在MySQL中,undolog和redolog是两个重要的日志文件,用于确保数据库的事务的持久性和一致性。
- undolog(回滚日志):
- 作用:记录事务的修改操作,用于回滚(撤销)事务。
- 存储位置:在InnoDB存储引擎中,undolog存储在磁盘上的undolog文件中。
- 内容:undolog记录了事务执行过程中对数据的修改操作,包括插入、更新和删除操作。
- 使用方式:当事务需要回滚时,MySQL会根据undolog中的记录,将数据恢复到事务开始之前的状态。
- redolog(重做日志):
- 作用:记录事务的修改操作,用于保证事务的持久性。
- 存储位置:在InnoDB存储引擎中,redolog存储在磁盘上的redolog文件中。
- 内容:redolog记录了事务执行过程中对数据的修改操作,包括插入、更新和删除操作。
- 使用方式:当事务提交时,MySQL会将redolog中的记录刷新到磁盘上的数据文件中,以保证事务的持久性。
总结:
- undolog用于回滚事务,记录了事务执行过程中的修改操作;
- redolog用于保证事务的持久性,记录了事务执行过程中的修改操作;
- 两者都是用于恢复和保护数据库的重要日志文件。
10. java的基本数据类型
Java的基本数据类型包括以下几种:
- 整数类型:byte、short、int、long
- 浮点数类型:float、double
- 字符类型:char
- 布尔类型:boolean
这些基本数据类型在Java中用于存储不同类型的数据,并具有不同的取值范围和内存占用大小。在使用这些数据类型时,需要根据具体的需求选择合适的类型来存储数据。
11. spring中ioc和aop你了解吗
IOC(Inversion of Control)的原理和作用
IOC是一种设计模式,也是Spring框架的核心思想之一。它的原理是将对象的创建、依赖关系的管理交给容器来完成,而不是由程序员手动管理。通过IOC容器,我们可以将对象的创建和依赖注入的过程解耦,使得代码更加灵活、可维护和可测试。
IOC
- 解耦:通过IOC容器管理对象的创建和依赖注入,将对象之间的耦合关系转移到容器中,使得代码更加灵活、可维护和可测试。
- 依赖注入:IOC容器负责将对象之间的依赖关系注入到对象中,使得对象之间可以松耦合地协作。
- 配置集中化:通过配置文件或注解的方式,将对象的创建和依赖关系的管理集中在一个地方,方便管理和维护。
- AOP的基础:IOC容器是实现AOP(Aspect-Oriented Programming)的基础,可以通过IOC容器来管理切面和切点,实现横切关注点的统一处理。
AOP(Aspect-Oriented Programming)的原理和作用
AOP是一种编程范式,它的原理是通过在不修改原有代码的情况下,将横切关注点(如日志、事务、安全等)与业务逻辑进行解耦,从而提高代码的可维护性和可重用性。
AOP
- 解耦:AOP将横切关注点与业务逻辑进行解耦,使得代码更加清晰、可维护和可重用。
- 集中处理横切关注点:通过AOP,我们可以将横切关注点(如日志、事务、安全等)集中处理,避免代码的重复编写。
- 提高代码的可维护性和可重用性:通过AOP,我们可以将横切关注点的代码抽取出来,使得代码更加清晰、可维护和可重用。
- 动态代理:AOP的实现方式之一是通过动态代理,在运行时动态地生成代理对象,从而实现横切关注点的统一处理。
总结来说,IOC和AOP是Spring框架的两个核心特性。IOC通过将对象的创建和依赖注入交给容器来管理,实现了对象之间的解耦和依赖注入;AOP通过将横切关注点与业务逻辑进行解耦,提高了代码的可维护性和可重用性。两者结合使用,可以更好地实现面向对象编程的原则。
12. spring怎么样解决循环依赖
在Spring中,当两个或多个bean之间存在循环依赖时,可以通过以下几种方式解决:
- 构造函数注入:使用构造函数注入可以解决循环依赖的问题。通过将依赖作为构造函数的参数传递,可以确保在创建bean时,所有依赖的bean都已经被实例化。
- Setter方法注入:使用Setter方法注入也可以解决循环依赖的问题。通过在bean的Setter方法中注入依赖,可以确保在创建bean时,所有依赖的bean都已经被实例化。
- 使用@Lazy注解:使用@Lazy注解可以延迟加载bean的实例化过程,从而解决循环依赖的问题。通过将@Lazy注解添加到bean的定义上,可以延迟实例化bean,直到第一次使用时才进行实例化。
- 使用@DependsOn注解:使用@DependsOn注解可以指定bean的依赖关系,从而解决循环依赖的问题。通过在bean的定义上添加@DependsOn注解,可以确保在创建bean时,所有依赖的bean都已经被实例化。
需要注意的是,循环依赖可能会导致死锁或无限递归的问题,因此在设计应用程序时应尽量避免循环依赖的出现。如果无法避免循环依赖,可以通过上述方法解决。
在Spring中,解决循环依赖是通过使用三级缓存(三个map)来实现的。具体原理如下:
- 创建对象A时,首先会检查A是否在一级缓存中,如果在则直接返回A的实例。
- 如果A不在一级缓存中,则会创建一个A的早期引用,并将其放入二级缓存中。
- 接着,Spring会开始创建A对象的实例,并将其放入一级缓存中。
- 在创建A对象的过程中,如果发现A依赖于B,那么Spring会先去创建B对象。
- 创建B对象时,同样会检查B是否在一级缓存中,如果在则直接返回B的实例。
- 如果B不在一级缓存中,则会创建一个B的早期引用,并将其放入二级缓存中。
- 接着,Spring会开始创建B对象的实例,并将其放入一级缓存中。
- 在创建B对象的过程中,如果发现B又依赖于A,那么Spring会从二级缓存中获取A的早期引用,并注入到B中。
- 当A和B的创建过程都完成后,Spring会将A和B的实例放入一级缓存中,并将二级缓存中的早期引用清除。
- 最后,Spring会将A和B的实例注入到彼此的属性中,完成循环依赖的解决。
通过使用三级缓存,Spring能够在对象创建过程中解决循环依赖的问题,保证对象的正确创建和注入。这种机制使得Spring能够处理复杂的依赖关系,提高了应用程序的灵活性和可维护性。
13. mysql中有哪些锁
在MySQL中,常见的锁包括以下几种:
- 共享锁(Shared Lock):也称为读锁,多个事务可以同时持有共享锁,用于保证读操作的一致性。共享锁之间不会互相阻塞,但会与排它锁互斥。
- 排它锁(Exclusive Lock):也称为写锁,只有一个事务可以持有排它锁,用于保证写操作的原子性。排它锁与其他任何锁都互斥,包括共享锁和排它锁。
- 记录锁(Record Lock):用于保护单个记录的锁,可以是共享锁或排它锁。记录锁是在存储引擎层实现的,不同的存储引擎可能有不同的实现方式。
- 表锁(Table Lock):锁定整个表,可以是共享锁或排它锁。表锁是在MySQL服务器层实现的,对整个表进行锁定,会对其他事务的读写操作产生阻塞。
- 行锁(Row Lock):也称为行级锁,用于保护表中的行数据。行锁可以是共享锁或排它锁,不同的事务可以同时持有不同行的共享锁,但只能有一个事务持有某一行的排它锁。
需要注意的是,MySQL的锁机制是基于存储引擎实现的,不同的存储引擎可能有不同的锁实现方式。常见的存储引擎包括InnoDB、MyISAM等,它们在锁的粒度、并发性能等方面有所不同。
14. 锁的是什么东西
在MySQL中,锁是用于控制并发访问的机制,用于保证数据的一致性和完整性。锁可以应用在不同的粒度上,包括表级锁和行级锁。
表级锁是对整个表进行加锁,当一个事务获取了表级锁后,其他事务无法对该表进行修改操作,只能进行读取操作。表级锁的优点是简单、粗粒度,但是并发性较差。
行级锁是对表中的行进行加锁,当一个事务获取了某一行的锁后,其他事务可以继续对其他行进行操作,只有对同一行的操作会被阻塞。行级锁的优点是并发性好,但是实现相对复杂。
MySQL中的锁可以分为共享锁(读锁)和排他锁(写锁)。共享锁可以被多个事务同时获取,用于读取操作,不会阻塞其他事务的读取操作。排他锁只能被一个事务获取,用于写入操作,会阻塞其他事务的读取和写入操作。
锁的使用需要根据具体的业务场景和并发访问需求进行合理的设计和配置,以保证数据的一致性和并发性。
15. 线程和进程的区别
线程和进程是操作系统中的两个重要概念,它们有以下区别:
- 定义:进程是程序的执行实例,是操作系统进行资源分配和调度的基本单位;线程是进程中的一个执行单元,是操作系统进行调度和执行的基本单位。
- 资源占用:进程拥有独立的地址空间、文件描述符、堆栈等资源,相互之间不共享;线程与所属进程共享地址空间和其他资源,线程之间可以直接访问同一进程的数据。
- 切换开销:进程切换需要保存和恢复整个进程的上下文,开销较大;线程切换只需要保存和恢复线程的上下文,开销较小。
- 通信和同步:进程之间通信需要使用进程间通信(IPC)机制,如管道、消息队列、共享内存等;线程之间通信可以直接读写共享变量,同步可以使用互斥锁、条件变量等机制。
- 独立性:进程是独立的执行实体,拥有自己的执行状态和控制流;线程是进程的一部分,共享进程的资源,执行状态和控制流与所属进程相互依赖。
总结来说,进程是资源分配的基本单位,线程是执行调度的基本单位。进程之间相互独立,线程之间共享资源。进程切换开销大,线程切换开销小。进程间通信需要使用IPC机制,线程间通信可以直接读写共享变量。
16. 线程的几种状态
在Java中,线程有以下几种状态:
- 新建(New):当线程对象被创建时,它处于新建状态。此时线程还没有开始执行。
- 运行(Runnable):当调用线程的start()方法后,线程进入运行状态。此时线程正在执行任务。
- 阻塞(Blocked):当线程被阻塞时,它暂时停止执行。线程可能会进入阻塞状态的原因有多种,比如等待某个资源、等待输入/输出等。
- 等待(Waiting):线程进入等待状态是因为调用了Object类的wait()方法、Thread类的join()方法或LockSupport类的park()方法。在等待状态下,线程会释放持有的锁。
- 超时等待(Timed Waiting):线程进入超时等待状态是因为调用了Thread类的sleep()方法、Object类的wait(long timeout)方法、Thread类的join(long millis)方法或LockSupport类的parkNanos()方法。
- 终止(Terminated):线程执行完任务后,或者因异常而结束,线程进入终止状态。
以上就是Java中线程的几种状态。
17. 怎么样能使线程进入阻塞状态
可以使用以下方法使线程进入阻塞状态:
- 使用Thread类的
sleep()
方法:调用Thread.sleep()
方法可以使当前线程进入阻塞状态,指定的时间内不会执行任何操作。例如,Thread.sleep(1000)
将使当前线程阻塞1秒钟。 - 使用Object类的
wait()
方法:调用wait()
方法可以使当前线程进入阻塞状态,并释放对象的锁。其他线程可以通过调用相同对象的notify()
或notifyAll()
方法来唤醒被阻塞的线程。 - 使用Thread类的
join()
方法:调用join()
方法可以使当前线程等待另一个线程执行完毕后再继续执行。例如,thread.join()
将使当前线程阻塞,直到thread
线程执行完毕。 - 使用Lock类的
lock()
方法:通过使用Lock类的lock()
方法获取锁,可以使线程进入阻塞状态,直到获取到锁为止。例如,lock.lock()
将使当前线程阻塞,直到获取到锁。
以上是几种常见的使线程进入阻塞状态的方法。根据具体的需求和场景,选择适合的方法来实现线程的阻塞。
18. wait()和sleep()的区别
wait()
和sleep()
是两个不同的方法,wait()
方法是Object中的,sleep()
是Thread中的。都是用于控制程序的执行。
wait()
方法是用于线程间通信的,它会使当前线程进入等待状态,直到子线程结束或收到指定的信号。在等待期间,当前线程会被挂起,不会占用CPU资源。
sleep()
函数是用于线程间的延时操作,它会使当前线程进入睡眠状态,暂停执行一段指定的时间。在睡眠期间,当前线程会被挂起,不会占用CPU资源。
总结来说,wait()
是用于线程间通信,而sleep()
是用于线程间的延时操作。
20. 类的加载的过程
JVM中类的加载过程可以分为以下几个步骤:
- 加载(Loading):将类的字节码文件加载到内存中。这个过程可以通过类加载器(ClassLoader)来完成。类加载器会根据类的全限定名(包括包名和类名)来查找并加载对应的字节码文件。
- 验证(Verification):验证字节码文件的正确性和安全性。这个过程会检查字节码文件的结构是否符合规范,并且会进行一些静态分析,以确保字节码文件不会引发安全问题。
- 准备(Preparation):为类的静态变量分配内存,并设置默认初始值。在这个阶段,JVM会为类的静态变量分配内存空间,并根据变量的类型设置默认的初始值(例如,int类型的变量默认为0)。
- 解析(Resolution):将符号引用解析为直接引用。在Java中,类之间的引用是通过符号引用来表示的,而在解析阶段,JVM会将这些符号引用解析为直接引用,以便后续的访问和调用。
- 初始化(Initialization):执行类的初始化代码。在这个阶段,JVM会执行类的静态初始化代码,包括静态变量的赋值和静态代码块的执行。类的初始化是在类第一次被使用时触发的,例如创建类的实例、访问类的静态变量或调用类的静态方法。
- 使用(Usage):使用类的实例或调用类的方法。在类被初始化之后,就可以使用类的实例或调用类的方法了。
- 卸载(Unloading):卸载不再使用的类。当一个类不再被引用,并且没有任何活动的实例时,JVM会卸载该类,释放相关的内存空间。
以上就是JVM中类的加载过程的主要步骤。
21. 说一说双亲委派机制
JVM中的双亲委派机制是一种类加载机制,它的目的是保证Java类的安全性和一致性。在双亲委派机制下,当一个类加载器收到加载类的请求时,它首先会将这个请求委派给它的父类加载器去完成,只有当父类加载器无法完成加载时,才会由当前类加载器自己去加载。
这种机制的好处在于可以避免类的重复加载,保证类的唯一性。当一个类加载器收到加载类的请求时,它会首先向上委派给父类加载器,如果父类加载器能够找到并加载这个类,那么就直接返回这个类的Class对象;如果父类加载器无法找到这个类,那么子类加载器才会尝试自己去加载。
双亲委派机制的具体实现是通过ClassLoader类的loadClass()方法来实现的。在loadClass()方法中,首先会检查是否已经加载过该类,如果已经加载过,则直接返回已加载的Class对象;如果没有加载过,则会调用父类加载器的loadClass()方法来尝试加载,如果父类加载器加载失败,则会调用自己的findClass()方法来加载类。
总结来说,双亲委派机制保证了类的加载顺序,从而保证了类的唯一性和一致性。它的核心思想是"上级加载器优先",即优先使用父类加载器来加载类,只有在父类加载器无法加载时才由子类加载器来加载。这种机制有效地避免了类的重复加载和冲突,提高了系统的安全性和稳定性。
22. int占多少字节,最大为多少
在Java中,int类型占据4个字节(32位),可以表示的最大值为2^31-1,即2147483647。
23. 单例模式知道吗?单例模式里的双重检查?
下面写一个单例模式(面试中,可能会要你现场写一个)
代码语言:javascript复制public class Singleton {
private volatile static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。
双重检查锁定(Double-Checked Locking)是一种在多线程环境下使用的延迟初始化的优化技术。在单例模式中,双重检查锁定用于确保只有一个实例被创建。
双重检查锁定的优点:
- 提供了延迟初始化,只有在需要时才会创建实例,节省了系统资源。
- 在多线程环境下保证了只有一个实例被创建,提高了性能。
双重检查锁定的缺点:
- 实现复杂,需要考虑线程安全性和可见性。
- 在早期的JDK版本中,由于指令重排序的问题,可能会导致多个线程同时通过第一个null检查,从而创建多个实例。但在JDK5及以后的版本中,通过使用volatile关键字修饰instance变量,可以解决这个问题。
总结:双重检查锁定是一种常用的单例模式实现方式,它在多线程环境下保证了只有一个实例被创建,并提供了延迟初始化的优化。但需要注意在实现时要考虑线程安全性和可见性的问题。
24. hashmap如何解决哈希冲突问题?
HashMap使用了链地址法来解决哈希冲突问题。当发生哈希冲突时,即不同的键映射到了相同的哈希桶位置,HashMap会在该位置上维护一个链表或红黑树(JDK8之后),将具有相同哈希值的键值对存储在同一个桶中。
当需要插入一个键值对时,HashMap会先计算键的哈希值,然后根据哈希值找到对应的桶。如果桶中已经存在键值对,则会遍历链表或红黑树,找到对应的键值对进行更新。如果桶中不存在键值对,则直接将新的键值对插入到桶中。
在查找键值对时,HashMap会根据键的哈希值找到对应的桶,然后遍历链表或红黑树,找到对应的键值对进行返回。
通过使用链地址法,HashMap能够高效地解决哈希冲突问题,并且在大部分情况下,插入、查找和删除操作的时间复杂度都是O(1)。但是在极端情况下,如果哈希函数设计不好或者哈希冲突过多,可能会导致链表过长,从而降低HashMap的性能。为了解决这个问题,JDK8之后的HashMap引入了红黑树,当链表长度超过一定阈值时,会将链表转换为红黑树,进一步提高了性能。
25. https你了解吗?
HTTPS是一种用于安全传输数据的协议,它在HTTP的基础上加入了SSL/TLS加密机制。
优点:
- 数据传输安全:HTTPS使用SSL/TLS加密机制对数据进行加密,可以有效防止数据被窃取或篡改。
- 身份验证:HTTPS使用数字证书对网站进行身份验证,确保用户访问的是真实的网站,防止中间人攻击。
- 改善搜索引擎排名:搜索引擎会优先显示使用HTTPS的网站,因为HTTPS可以提供更安全的用户体验。
- 保护用户隐私:HTTPS可以隐藏用户的浏览历史和个人信息,提供更好的隐私保护。
缺点:
- 性能损耗:由于加密和解密的过程会增加服务器和客户端的计算负载,HTTPS的性能相对于HTTP会有所下降。
- 部署和维护成本高:为了使用HTTPS,网站需要购买数字证书并进行配置,这会增加网站的部署和维护成本。
- CDN缓存问题:HTTPS的加密机制会导致内容分发网络(CDN)无法缓存网站的内容,影响网站的访问速度。
总的来说,HTTPS在保护数据安全和用户隐私方面具有明显优势,但也存在一些性能和成本方面的缺点。在需要保护敏感信息的场景下,使用HTTPS是非常重要的。
26. 索引失效的原因?
MySQL索引失效的原因有以下几个:
- 索引列未被查询条件使用:当查询条件中没有使用到索引列时,MySQL无法利用索引进行快速查找,导致索引失效。
- 索引列使用了函数或表达式:如果查询条件中的索引列使用了函数或表达式,MySQL无法直接使用索引进行查找,而是需要对每一行数据进行计算,导致索引失效。
- 索引列上存在类型转换:如果查询条件中的索引列与索引的数据类型不一致,MySQL会进行类型转换,导致索引失效。
- 索引列上存在范围查询:当查询条件中的索引列使用了范围查询(例如大于、小于、区间等),MySQL只能使用索引的一部分进行查找,导致索引失效。
- 索引列上存在排序:如果查询条件中的索引列需要进行排序操作,MySQL无法直接使用索引进行排序,而是需要额外的排序操作,导致索引失效。
- 索引列上存在模糊查询:当查询条件中的索引列使用了模糊查询(例如LIKE操作),MySQL无法直接使用索引进行查找,而是需要对每一行数据进行匹配,导致索引失效。
- 索引列上存在NULL值:如果查询条件中的索引列包含NULL值,MySQL无法使用索引进行查找,导致索引失效。
为了避免索引失效,需要根据具体的查询场景进行优化,例如合理设计索引、避免使用函数或表达式、避免类型转换、避免范围查询、避免排序操作、避免模糊查询等。
27. 索引的数据结构为什么要用b 树?
MySQL索引的数据结构选择B 树的原因有以下几点:
- 有序性:B 树是一种有序的数据结构,可以快速进行范围查询和排序操作。在数据库中,索引的主要作用是提高查询效率,而B 树的有序性能够很好地支持这一点。
- 平衡性:B 树是一种自平衡的树结构,能够保持树的高度相对较低,从而减少磁盘I/O操作。在数据库中,磁盘I/O是一个相对较慢的操作,通过使用B 树可以减少磁盘I/O的次数,提高查询效率。
- 可扩展性:B 树的结构可以很容易地进行扩展和调整,适应数据的动态变化。在数据库中,数据的插入、删除和更新是常见的操作,B 树的可扩展性能够很好地支持这些操作。
- 支持多级索引:B 树的结构可以很容易地支持多级索引,即通过多个索引字段进行查询。在数据库中,多级索引可以提高查询的精确度和效率。
综上所述,B 树作为一种有序、平衡、可扩展且支持多级索引的数据结构,非常适合作为MySQL索引的数据结构。它能够提高查询效率、减少磁盘I/O操作,并且能够适应数据的动态变化。
28. coding:二分查找,分析边界问题
下面是一个用Java实现的二分查找的示例代码:
代码语言:javascript复制public class BinarySearch {
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid 1;
} else {
right = mid - 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 9};
int target = 5;
int result = binarySearch(arr, target);
if (result == -1) {
System.out.println("Element not found");
} else {
System.out.println("Element found at index " result);
}
}
}
在这个示例中,binarySearch
方法接受一个已排序的整数数组和一个目标值作为参数,然后使用二分查找算法在数组中查找目标值。如果找到目标值,则返回其索引;如果未找到目标值,则返回-1。
关于边界问题,二分查找算法的边界问题主要包括以下几个方面:
- 数组为空:在开始二分查找之前,需要先判断数组是否为空,如果为空,则直接返回-1。
- 数组长度为0或1:如果数组长度为0,则直接返回-1;如果数组长度为1,且该元素不等于目标值,则也直接返回-1。
- 目标值小于数组中的最小值或大于数组中的最大值:在二分查找过程中,如果目标值小于数组中的最小值或大于数组中的最大值,则说明目标值不在数组中,直接返回-1。
以上是对二分查找算法边界问题的分析。在实际编写代码时,我们需要考虑这些边界情况,并进行相应的处理,以确保算法的正确性和健壮性。