1.int和Integer的区别
1、数据类型不同:int 是基础数据类型,而 Integer 是包装数据类型;
2、默认值不同:int 的默认值是 0,而 Integer 的默认值是 null;
3、内存中存储的方式不同:int 在内存中直接存储的是数据值,而 Integer 实际存储的是对象引用,当 new一个 Integer 时实际上是生成一个指针指向此对象;
4、实例化方式不同:Integer 必须实例化才可以使用,而 int 不需要;
5、变量的比较方式不同:int 可以使用 == 来对比两个变量是否相等,而 Integer 一定要使用 equals 来比较两个变量是否相等。
2.Set集合的去重过程
HashSet最终的去重是由HashMap来实现的
1.set() 函数中会先调用对象的 hash() 方法,获取 hash 结果;
2.如果 hash (哈希值)结果相同,用比较操作符 == (也就是调用函数 eq())判断二者的值是否相等;
3.如果都相等,去重;否则,set() 认为二者不同,两个都保留到结果中。
3.List的排序方式
使用 Comparable 进行排序;
使用 Comparator 进行排序;
如果是 JDK 8 以上的环境,也可以使用 Stream 流进行排序。
4.日志的级别都有哪些
日志的级别通常由不同的日志框架或库定义,但是在大多数日志系统中,常见的日志级别包括以下几种:
TRACE(追踪):最详细的日志级别,用于追踪程序的执行过程,通常用于调试目的。这个级别的日志会输出非常详细的信息,包括方法的入参、返回值等。
DEBUG(调试):用于输出调试信息,通常用于开发和调试阶段。这个级别的日志会输出一些详细的调试信息,但比TRACE级别的日志更加精简。
INFO(信息):用于输出程序运行时的一些重要信息,例如应用程序的启动、关闭,关键操作的执行等。这个级别的日志通常用于生产环境,用于监控应用程序的运行状态。
WARN(警告):用于输出一些警告信息,表示程序可能存在潜在的问题,但不会影响程序的正常运行。这个级别的日志通常用于记录一些非致命性的异常或异常情况。
ERROR(错误):用于输出错误信息,表示程序发生了错误或异常。这个级别的日志通常用于记录一些严重的错误,可能会导致程序崩溃或无法正常运行。
FATAL(致命):最高级别的日志,用于输出致命错误信息,表示程序发生了无法恢复的错误,导致程序无法继续执行。这个级别的日志通常用于记录一些非常严重的错误,可能会导致系统崩溃或数据丢失。
不同的日志框架可能还会定义其他级别,或者允许用户自定义级别。在实际应用中,可以根据具体的需求和日志输出的详细程度来选择合适的日志级别。通常在开发和测试阶段,可以使用较低的日志级别(如DEBUG或INFO)来获取更多的信息,而在生产环境中,可以使用较高的日志级别(如INFO或WARN)来减少日志输出的数量,提高性能。
5.String中直接赋值和newString的区别
在Java中,字符串可以通过直接赋值和使用 new 关键字创建。这两种方式有一些区别:
直接赋值:使用双引号将字符串文本括起来,例如 String str = “Hello”;。直接赋值的字符串会被存储在字符串常量池中。如果字符串常量池中已经存在相同内容的字符串,则直接返回常量池中的引用,而不会创建新的字符串对象。这样可以节省内存空间,并提高性能。
使用 new 关键字:使用 new 关键字创建字符串对象,例如 String str = new String(“Hello”);。使用 new 关键字创建的字符串对象会在堆内存中分配新的内存空间,并创建一个新的字符串对象。即使字符串常量池中已经存在相同内容的字符串,仍然会创建一个新的对象。
区别总结如下:
直接赋值的字符串会被存储在字符串常量池中,而使用 new 关键字创建的字符串对象会在堆内存中分配新的内存空间。
直接赋值的字符串会尝试复用字符串常量池中已存在的字符串对象,而使用 new 关键字创建的字符串对象不会进行复用。
直接赋值的字符串效率更高,因为它可以避免创建新的对象和分配额外的内存空间。
使用 new 关键字创建的字符串对象可以通过 == 运算符进行引用比较,而直接赋值的字符串只能通过 equals() 方法进行内容比较。
6.线程的创建方式有哪些
1.继承 Thread 类:创建一个继承自 Thread 类的子类,并重写 run() 方法来定义线程的执行逻辑。然后通过创建子类的实例并调用 start() 方法来启动线程。
2.实现 Runnable 接口:创建一个实现了 Runnable 接口的类,并实现 run() 方法来定义线程的执行逻辑。然后通过创建该类的实例,并将其作为参数传递给 Thread 类的构造方法来创建线程对象,最后调用线程对象的 start() 方法来启动线程。
3.使用 Callable 和 Future:创建一个实现了 Callable 接口的类,并实现 call() 方法来定义线程的执行逻辑。Callable 接口可以返回一个结果,并且可以通过 Future 对象来获取线程执行的结果。
4.使用 Executor 框架:Executor 框架提供了一种更高级的线程管理方式,可以通过 ExecutorService 接口来创建和管理线程。可以使用 Executors 类的静态方法来创建不同类型的线程池,然后将任务提交给线程池执行。
7.谈谈对AQS的理解,以及AQS的原理
AQS(AbstractQueuedSynchronizer)是Java并发包中的一个基础框架,用于实现同步器(synchronizer)的构建。AQS提供了一种简单且灵活的方式来实现各种同步器,如锁(Lock)和同步队列(Synchronizer Queue)。
AQS的原理是基于一个FIFO双向队列(CLH队列)和一个状态变量(state)来实现的。每个线程在尝试获取同步器的独占资源时,会先尝试获取同步状态(state)。如果获取成功,线程可以继续执行临界区代码;如果获取失败,线程会被加入到同步队列中,进入等待状态。
AQS的核心思想是通过CAS(Compare and Swap)操作来实现对状态变量的原子更新,以及通过自旋和阻塞来实现线程的等待和唤醒。具体来说,AQS提供了以下几个关键方法:
acquire(int arg):尝试获取同步状态,如果获取失败则进入等待队列,直到获取成功。
release(int arg):释放同步状态,并唤醒等待队列中的下一个线程。
tryAcquire(int arg):尝试获取同步状态,如果获取成功则返回true,否则返回false。
tryRelease(int arg):尝试释放同步状态,如果释放成功则返回true,否则返回false。
AQS的具体实现方式是通过内部的同步队列(CLH队列)来管理等待线程,并使用一个volatile的state变量来表示同步状态。当一个线程尝试获取同步状态时,如果获取失败,它会被加入到同步队列的尾部,并进入等待状态。当同步状态被释放时,AQS会从同步队列的头部唤醒下一个等待线程。
AQS的设计使得它可以支持独占锁(如ReentrantLock)和共享锁(如Semaphore、CountDownLatch)等不同类型的同步器。通过继承AQS并实现相应的方法,开发者可以方便地构建自定义的同步器。
总结来说,AQS是Java并发包中一个重要的基础框架,通过内部的同步队列和状态变量,实现了线程的等待和唤醒机制,为开发者提供了一种简单且灵活的方式来构建各种类型的同步器。
8.谈谈对CAS的理解以及CAS的原理
CAS(Compare and Swap)是一种并发编程中常用的原子操作,用于实现多线程环境下的无锁同步。CAS操作包括三个操作数:内存位置(或称为变量)、期望值和新值。CAS操作会比较内存位置的当前值与期望值,如果相等,则将新值写入内存位置;如果不相等,则不做任何操作。
CAS的原理是基于硬件的原子性操作,通常使用底层的原子指令(如CMPXCHG指令)来实现。CAS操作可以保证在多线程环境下对共享变量的原子性操作,避免了传统锁机制(如synchronized)的开销和线程阻塞。
CAS的执行过程如下:
读取内存位置的当前值。
比较当前值与期望值是否相等。
如果相等,则将新值写入内存位置。
如果不相等,则不做任何操作。
返回操作结果,通常是成功或失败。
如果CAS操作成功,表示当前线程成功修改了内存位置的值;如果CAS操作失败,表示当前线程的操作被其他线程干扰,需要重新尝试。
CAS操作的优点是无锁,避免了线程的阻塞和唤醒,减少了线程切换的开销,提高了并发性能。然而,CAS操作也存在一些问题,例如ABA问题(即在CAS操作期间,内存位置的值可能被其他线程修改多次,最终又恢复为原来的值),需要通过版本号或标记来解决。
在Java中,java.util.concurrent.atomic包提供了一系列的原子类,如AtomicInteger、AtomicLong等,使用CAS操作来实现线程安全的原子操作。这些原子类可以在多线程环境下保证共享变量的原子性操作,避免了使用锁的开销和线程阻塞。
9.什么是Spring的IOC和DI
Spring的IOC:控制反转,spring框架中其它功能都是依赖于IOC来实现将对象交给spring来管理控制反转,spring框架中其它功能都是依赖于IOC来实现将对象交给spring来管理
1.对象生命周期管理 对象的创建 对象的初始化 对象的销毁等等
2.对象之间的依赖关系管理
DI(依赖注入),当我们将对象注册到IOC容器之后,如果两个对象之间有依赖关系,将对象注入到另外一个对象中,例如将service对象注入到controller中
10.什么是AOP
AOP:面向切面编程,可以将某些功能在运行期间织入到需要该功能的对象中
com.lanou.service.impl...(…)
11.AOP的通知有哪些
通知(advice):就是定义了额外功能的方法
5.1 前置通知:在连接点方法执行之前执行
5.2 后置(最终)通知:在连接点方法执行完成之后执行(不论方法是否发生异常)
5.3 后置返回通知:在连接点方法执行完成之后执行(方法发生异常,该通知不执行)
5.4 后置异常通知:在连接点方法发生异常时执行
5.5 环绕通知:最强大的一种通知,它可以模拟出来以上四种通知
12.常用的spring的注解有哪些?以及这些注解的作用
Spring框架提供了丰富的注解,用于在应用程序中进行配置、管理组件和实现各种功能。以下是一些常用的Spring注解以及它们的作用:
-
@Component
: 用于标记一个类为Spring的组件,供Spring容器扫描并实例化。通常与@Autowired
结合使用进行依赖注入。 -
@Service
: 与@Component
类似,用于标记一个类为业务逻辑组件,通常在服务层使用。 -
@Repository
: 用于标记一个类为数据访问组件,通常在数据访问层使用。 -
@Controller
: 用于标记一个类为控制器组件,通常在Web应用中使用,处理HTTP请求。 -
@RestController
: 与@Controller
类似,但还包括了@ResponseBody
,用于返回JSON数据。 -
@Configuration
: 用于标记一个类为配置类,相当于XML配置文件,定义了Spring的Bean配置。 -
@Bean
: 用于在配置类中定义一个Bean,方法的返回值将作为Bean的实例。 -
@Autowired
: 自动装配(依赖注入),通过类型或名称将依赖注入到属性、构造函数或方法参数中。 -
@Qualifier
: 与@Autowired
一起使用,通过指定Bean的名称解决自动装配的歧义。 -
@Value
: 用于将属性值注入到Bean中,支持基本类型、String、SpEL表达式等。 -
@Scope
: 定义Bean的作用域,例如单例、原型等。 -
@Primary
: 与@Autowired
一起使用,标记一个Bean作为默认的自动装配候选者。 -
@PostConstruct
: 在Bean的初始化方法上使用,表示在构造函数执行之后、依赖注入完成之后执行。 -
@PreDestroy
: 在Bean销毁之前执行。 -
@Transactional
: 用于标记方法或类的方法,表示该方法是一个事务,支持声明式事务管理。 -
@Aspect
: 用于声明一个切面类,结合其他注解定义切点和通知。 -
@Pointcut
: 用于定义切点,通常与@Aspect
一起使用。 -
@Around
、@Before
、@After
: 定义切面的通知类型,在方法执行前、后、环绕时执行。 -
@Enable*
: 用于启用特定的Spring功能,如@EnableTransactionManagement
启用事务管理。
以上只是一些常用的Spring注解,实际上Spring提供了更多的注解用于不同的功能和场景。这些注解能够简化配置,提高开发效率,并且使代码更具可读性。
13.springMVC的执行流程
客户端发送HTTP请求到DispatcherServlet。
DispatcherServlet是前端控制器,它接收到请求后,根据配置的HandlerMapping找到对应的处理器(Controller)。
处理器(Controller)处理请求,并返回一个ModelAndView对象,其中包含了模型数据和视图名称。
DispatcherServlet通过ViewResolver解析视图名称,找到对应的视图对象。
视图对象负责渲染模型数据,生成最终的响应结果。
DispatcherServlet将响应结果返回给客户端。
在整个执行流程中,Spring MVC的核心组件包括:
DispatcherServlet:前端控制器,负责接收请求并将其分发给相应的处理器。
HandlerMapping:根据请求的URL路径,找到对应的处理器(Controller)。
HandlerAdapter:负责调用处理器(Controller)的方法,并处理请求参数的绑定。
HandlerInterceptor:拦截器,可以在请求处理前后进行一些预处理和后处理操作。
Handler:处理器(Controller),负责处理具体的业务逻辑。
ModelAndView:包含模型数据和视图名称的对象。
ViewResolver:视图解析器,根据视图名称解析出对应的视图对象。
View:视图对象,负责渲染模型数据,生成最终的响应结果。
整个流程中,DispatcherServlet充当了中央调度器的角色,负责协调各个组件的工作,并将请求和响应传递给相应的组件进行处理。通过配置不同的HandlerMapping、HandlerAdapter、ViewResolver等组件,可以实现灵活的请求处理和视图渲染。开发者可以通过编写Controller类和配置文件来定义请求处理逻辑,并通过模型数据和视图来生成最终的响应结果。
14.什么是数据库事务?
数据库事务是指一组数据库操作(例如插入、更新、删除等),这些操作要么全部成功执行,要么全部回滚,以保持数据库的一致性和完整性。
事务是数据库管理系统(DBMS)中的一个重要概念,用于确保数据库操作的原子性、一致性、隔离性和持久性,通常使用ACID(原子性、一致性、隔离性和持久性)来描述事务的特性。
ACID是事务的四个基本特性:
原子性(Atomicity):事务是一个不可分割的操作单元,要么全部执行成功,要么全部回滚。如果事务中的任何一个操作失败,整个事务将回滚到初始状态,不会对数据库产生影响。
一致性(Consistency):事务在执行前和执行后,数据库的状态必须保持一致。这意味着事务中的操作必须满足数据库的约束和规则,以确保数据的完整性。
隔离性(Isolation):事务的执行是相互隔离的,一个事务的操作不应该对其他事务产生影响。隔离性可以防止并发事务之间的数据冲突和不一致。
持久性(Durability):一旦事务提交成功,其对数据库的修改将永久保存,即使发生系统故障或重启,也能够恢复到提交后的状态。
通过使用数据库事务,可以确保数据库操作的一致性和完整性,避免数据损坏和不一致的情况。在开发应用程序时,特别是涉及到多个数据库操作的场景,使用事务可以保证数据的正确性,并提供可靠的数据处理机制。
15.vilatile关键字的作用
volatile 是 Java 中的一个关键字,用于修饰变量。
volatile 关键字的作用是确保被修饰的变量在多线程环境下的可见性、有序性和禁止指令重排序。
具体来说,volatile 关键字具有以下作用:
1.可见性:当一个变量被声明为 volatile 时,对该变量的写操作会立即被刷新到主内存中,而对该变量的读操作会从主内存中获取最新的值。这样可以确保不同线程之间对该变量的读写操作是可见的,避免了线程之间的数据不一致性。
2.有序性:volatile 关键字可以防止指令重排序,保证被修饰变量的读写操作按照程序的顺序执行。这样可以避免由于指令重排序导致的线程执行顺序不一致的问题。
需要注意的是,volatile 关键字不能保证复合操作的原子性。
如果需要保证复合操作的原子性,可以使用锁或原子类来实现。
使用 volatile 关键字时需要注意以下几点:
volatile 关键字适用于对变量的写操作不依赖于变量的当前值的场景,
例如标志位的设置和重置。
volatile 关键字不能替代锁,它只能保证可见性、有序性和禁止指令重排序,
无法保证原子性。
volatile 关键字的使用应谨慎,过度使用 volatile 可能会导致性能下降,因为它会增加内存的访问开销。
总结来说,volatile 关键字用于保证被修饰变量在多线程环境下的可见性、有序性和禁止指令重排序。它是一种轻量级的同步机制,适用于对变量的写操作不依赖于变量的当前值的场景
16.详细说明mybatis的缓存
MyBatis是一个开源的持久层框架,它提供了一种缓存机制来提高数据库访问性能。MyBatis的缓存可以分为一级缓存和二级缓存两种。
1.一级缓存:
一级缓存是MyBatis默认开启的缓存机制,它是基于线程的本地缓存。当一个会话(SqlSession)执行查询操作时,查询的结果会被缓存在会话的一级缓存中。
一级缓存的作用范围是会话级别,同一个会话中的多次相同查询会直接从缓存中获取结果,而不需要再次查询数据库。
一级缓存是自动的,无需手动配置,但也可以通过清除缓存的方式来刷新缓存。
2.二级缓存:
二级缓存是基于命名空间(Mapper)级别的缓存,它可以被多个会话共享。
二级缓存需要手动配置,通过在Mapper XML文件中添加 标签来启用。配置了二级缓存后,MyBatis会将查询的结果缓存到指定的缓存区域中(如内存、Redis等)。
二级缓存的作用范围是跨会话的,不同的会话可以共享同一个命名空间下的缓存数据。
二级缓存的缓存策略是LRU(Least Recently Used,最近最少使用),当缓存空间不足时,会根据最近使用的时间来淘汰最不常用的缓存项。
需要注意的是,一级缓存和二级缓存是相互独立的,它们的生命周期不同。一级缓存的生命周期是会话级别的,当会话关闭时,一级缓存也会被清空。而二级缓存的生命周期是应用级别的,当应用关闭时,二级缓存才会被清空。
17.将对象注册到IOC容器的方式都有哪些
1.使用@Component或其衍生注解:通过在类上添加@Component、@Service、@Repository、@Controller等注解,将类标识为Spring的组件,并自动扫描并注册到IOC容器中。
2.使用@Configuration和@Bean:通过在配置类上添加@Configuration注解,将其标识为配置类。然后使用@Bean注解将方法标记为Bean定义方法,返回的对象将被注册到IOC容器中。
3.使用XML配置文件:在XML配置文件中使用 元素来定义和配置Bean,然后通过加载配置文件将Bean注册到IOC容器中。
4.使用Java代码手动注册:通过编写Java代码手动创建BeanDefinition,并将其注册到IOC容器中。
5.使用@ComponentScan注解:通过在配置类上添加@ComponentScan注解,指定要扫描的包路径,Spring会自动扫描并注册带有@Component及其衍生注解的类。
这些方式可以根据具体的需求和场景来选择使用。通过将对象注册到IOC容器中,可以实现依赖注入、AOP切面、事务管理等功能,提高代码的可维护性和灵活性。
18.过滤器和拦截器的区别
1.拦截器(Interceptor)和过滤器(Filter)是在Web应用程序中用于处理请求和响应的两种不同的组件。它们在功能和使用方式上有一些区别。
-
执行顺序:拦截器是在控制器方法执行前后进行拦截处理的,而过滤器是在请求进入Servlet容器之前和响应返回给客户端之前进行处理的。换句话说,拦截器是在控制器层面进行拦截,而过滤器是在Servlet容器层面进行拦截。
作用对象:拦截器主要用于拦截和处理控制器方法的调用,可以对请求进行预处理和后处理。过滤器则可以对请求和响应进行过滤和修改,可以在请求到达Servlet之前对请求进行预处理,也可以在响应返回给客户端之前对响应进行后处理。 -
依赖关系:拦截器是依赖于框架的,例如在Spring MVC中使用拦截器需要依赖于Spring MVC框架。过滤器是独立于框架的,它是基于Servlet规范的,可以在任何Java Web应用程序中使用。
-
配置方式:拦截器的配置通常是通过在配置文件中声明拦截器的Bean,并将其与特定的URL或请求进行关联。过滤器的配置是通过在web.xml文件中进行配置,指定过滤器的名称、类和拦截的URL模式。
-
功能:拦截器可以访问和修改控制器方法的参数和返回值,可以进行权限验证、日志记录等操作。过滤器可以对请求进行修改、过滤和验证,例如对请求进行编码转换、字符集处理、身份验证等。
总的来说,拦截器更加专注于控制器方法的拦截和处理,而过滤器更加通用,可以对请求和响应进行更广泛的处理。选择使用拦截器还是过滤器取决于您的需求和具体的应用场景
19.mybatis的映射文件除了select、update、delete、insert以外还有哪些标签
MyBatis是一个Java持久化框架,用于简化数据库操作。除了常见的select
、update
、delete
、insert
标签外,MyBatis的映射文件(Mapper XML)还有许多其他标签,用于定义更复杂的查询和操作逻辑。以下是一些常见的MyBatis映射文件标签:
-
resultMap
: 用于定义查询结果的映射关系,将查询结果映射到Java对象或基本类型。通过此标签可以配置对象属性和列的映射关系,支持复杂对象嵌套。 -
parameterMap
: 用于定义参数映射,将参数值传递给SQL语句。不常用,一般使用parameterType
属性来指定参数类型。 -
sql
: 用于定义可重用的SQL片段,可以在其他SQL语句中引用。通过此标签可以提高SQL语句的可维护性和重用性。 -
include
: 用于包含外部的SQL片段,可以将其他的SQL片段嵌套在当前SQL语句中。一般与<sql>
标签一起使用。 -
if
、choose
、when
、otherwise
: 用于条件判断,可以根据条件动态生成SQL语句的一部分。例如,在<select>
标签中根据不同的条件拼接不同的WHERE
子句。 -
trim
、where
、set
、foreach
: 用于对SQL语句进行修饰,去除多余的逗号和空格,或者循环生成某些部分。例如,在<update>
标签中使用<set>
来生成SET
子句。 -
bind
: 用于给参数绑定一个临时变量,可以在SQL语句中引用。 -
cache
: 用于配置缓存,控制SQL语句执行结果的缓存策略。可以配置一级缓存和二级缓存。 -
selectKey
: 用于在插入操作后获取生成的主键值,通常在自动生成主键时使用。 -
association
、collection
: 用于映射复杂对象的关联关系,例如一对一、一对多。 -
discriminator
: 用于在继承关系映射中根据特定的字段值来决定使用哪个子映射。 -
sqlProvider
: 用于引用提供自定义SQL语句的类,允许在Java代码中动态生成SQL语句。
这些标签使得MyBatis映射文件非常灵活,可以满足各种复杂的数据库操作需求。根据具体的业务场景,你可以灵活运用这些标签来编写映射文件。
20.sql中如何去掉重复的记录
在SQL中,要去除重复记录,你可以使用SELECT DISTINCT
语句或者GROUP BY
语句来实现,具体取决于你的需求和数据结构。以下是两种常见的方法:
-
使用 DISTINCT 关键字:
SELECT DISTINCT
用于从结果集中选择不重复的记录。它会返回去除重复记录后的结果。SELECT DISTINCT column1, column2, ... FROM table_name;
在上面的查询中,
column1, column2, ...
是你想要选择的列名,table_name
是你要查询的表名。 -
使用 GROUP BY 子句:
GROUP BY
语句用于根据指定的列对结果集进行分组,并可以结合聚合函数如 SUM、COUNT、AVG 等。当你使用GROUP BY
时,相同的值会被分组在一起,从而达到去重的效果。SELECT column1, column2, ... FROM table_name GROUP BY column1, column2, ...;
在上面的查询中,
column1, column2, ...
是你想要选择的列名,table_name
是你要查询的表名。
需要注意的是,使用 GROUP BY
时,查询的结果会按照指定的分组进行汇总,因此你需要确保在选择列的时候,除了分组的列外,还选择了需要显示的其他列。使用 DISTINCT
关键字则不需要指定分组。
另外,如果你需要在查询中加入条件来筛选数据,可以使用 WHERE
子句。例如:
SELECT DISTINCT column1, column2, ...
FROM table_name
WHERE condition;
或者:
SELECT column1, column2, ...
FROM table_name
WHERE condition
GROUP BY column1, column2, ...;
这样,你可以根据特定条件去除重复记录。
21.Map的底层实现原理
●底层的数据结构
JDK1.7之前 数组+单向链表
JDK1.8之后 数组+单向链表+红黑树
●数组的默认初始大小 16 数组的大小为2的n次幂最优的
●数组的最大容量为 2的30次幂
●负载因子默认为0.75,负载因子和扩容机制相关
达到当前容量的 * 负载因子子后就会触发扩容
例如 当前容量16 负载因子为0.75 当达到16 * 0.75 之后就会扩容
●链表长度达到8时,有可能触发链表转红黑树(还需要数组的长度不低于64)
●当红黑树元素少于6个时,将红黑树转为链表
6.扩容机制
●检查集合中的键值对个数是否超过(当前数组长度 * 负载因子)
●如果超过就需要扩容
●扩容为原来的2倍
●新建一个数组,数组长度就是新容量
●将原数组的元素重新添加到新数组
和原来的顺序不一定一样,因为是根据hash值和新容量重新计算的下标
7.添加过程
●根据key计算出hash值
●检测桶数组是否可用,如果数组为null或者数组长度为0,新提供一个数组
●根据hash值找出新节点在数组中的位置
●如果该位置没有元素,直接将新节点放在该位置
●如果该位置有元素,该位置的单向链表中的节点比较key的值equals,如果有相等的
用新节点覆盖原有节点
●如果没有相等的,新节点添加到链表的末尾
●判断链表长度是否达到8,如果达到8链表转换为红黑树
8.为什么数组长度最好是2的n次幂?
●减少hash碰撞(减少hash值相同的概率)
●扩容之后的新索引,就是原索引 + 原容量 . 保证节点相对位置不变
9.泊松分布
●负载因子 0.75 ==> 0.5 ==> 8
10.HashMap和Hashtable的区别?
1.父类不同 table 是Dictionary;map是AbstractMap
2.初始容量不同 table 为11 map为16
3.map的键和值都允许为null;table不允许为null
4.map将key的hashCode值异或上key的hashCode值右移16位作为hash值table将key的hashCode值作为hash值
5.table是线程安全的 map是非线程安全;map性能是优于table
6.table有contains方法,map中没有该方法
7.table可以通过枚举器进行值得遍历,map没有枚举器
8.map扩容时,扩容为原来的2倍,table是扩容为原来的2倍+1
HashMap的底层实现原理:
HashMap使用哈希表(数组+链表/红黑树)来存储键值对。
当插入一个键值对时,HashMap会根据键的哈希值计算出在数组中的位置,如果该位置为空,则直接插入;如果该位置已经存在其他键值对,则通过链表或红黑树解决哈希冲突。
当获取一个键值对时,HashMap会根据键的哈希值计算出在数组中的位置,然后在链表或红黑树中查找对应的值。
HashMap通过哈希函数和链表/红黑树的结构,实现了快速的插入、查找和删除操作。
TreeMap的底层实现原理:
TreeMap使用红黑树来存储键值对,保持键的有序性。
当插入一个键值对时,TreeMap会根据键的比较结果,将键值对插入到合适的位置,以保持键的有序性。
当获取一个键值对时,TreeMap会通过红黑树的查找操作,找到对应的值。
TreeMap通过红黑树的平衡性,实现了有序的插入、查找和删除操作。
LinkedHashMap的底层实现原理:
LinkedHashMap继承自HashMap,底层实现与HashMap类似,但额外维护了一个双向链表,用于保持插入顺序或访问顺序。
当插入一个键值对时,LinkedHashMap会在HashMap的基础上,将键值对插入到双向链表的尾部。
当获取一个键值对时,LinkedHashMap会通过HashMap的查找操作,找到对应的值,并根据访问顺序更新双向链表。
LinkedHashMap通过HashMap和双向链表的结合,实现了保持插入顺序或访问顺序的特性。
不同的Map实现类在性能和特性上有所差异,选择合适的Map实现类取决于具体的需求和场景。HashMap通常是最常用的Map实现类,提供了较好的性能;TreeMap适用于需要有序存储的场景;LinkedHashMap适用于需要保持插入顺序或访问顺序的场景
22.Java反射机制的原理,以及常用方法有哪些
Java反射机制是指在运行时动态地获取类的信息、调用对象的方法和访问对象的属性的能力。通过反射机制,可以在运行时检查类、实例化对象、调用方法、获取和设置属性等,而不需要提前知道类的具体信息。
Java反射机制的原理如下:
获取Class对象:通过类的全限定名或对象的getClass()方法获取类的Class对象,Class对象包含了类的结构信息。
创建对象:通过Class对象的newInstance()方法或Constructor类的newInstance()方法创建类的实例。
调用方法:通过Method类的invoke()方法调用类的方法,可以传递参数并获取返回值。
访问属性:通过Field类的get()和set()方法访问类的属性,可以获取和设置属性的值。
常用的反射方法包括:
获取Class对象的方法:
Class.forName(“类的全限定名”):根据类的全限定名获取Class对象。
对象.getClass():通过对象的getClass()方法获取Class对象。
类名.class:直接使用类名.class获取Class对象。
创建对象的方法:
Class.newInstance():通过Class对象的newInstance()方法创建类的实例。
Constructor.newInstance():通过Constructor类的newInstance()方法创建类的实例。
调用方法的方法:
Method.invoke():通过Method类的invoke()方法调用类的方法。
访问属性的方法:
Field.get():通过Field类的get()方法获取属性的值。
Field.set():通过Field类的set()方法设置属性的值。
除了上述方法,还有其他一些方法用于获取类的信息,如获取类的构造方法、获取类的方法、获取类的属性等。
反射机制在一些框架和工具中广泛应用,如Spring框架的依赖注入、ORM框架的实体映射等。通过反射机制,可以实现动态加载类、动态调用方法、动态修改属性等灵活的操作。但是,由于反射机制的使用需要额外的开销,性能可能会受到一定影响,因此在性能要求较高的场景下需要谨慎使用。
23.java中都有哪些类加载器,Java类的加载过程,什么是双亲委派模式,有什么作用?
在Java中,有以下几种类加载器:
启动类加载器(Bootstrap Class Loader):负责加载Java的核心类库,如rt.jar等。它是由C++编写的,是虚拟机的一部分,不是Java类。
扩展类加载器(Extension Class Loader):负责加载Java的扩展类库,如jre/lib/ext目录下的JAR包。
应用程序类加载器(Application Class Loader):也称为系统类加载器,负责加载应用程序的类,是默认的类加载器。它从类路径(classpath)中加载类,包括用户自定义的类和第三方库。
自定义类加载器:开发者可以通过继承ClassLoader类,实现自定义的类加载器,用于加载特定的类或实现特定的加载策略。
Java类的加载过程如下:
加载(Loading):查找并加载类的字节码文件,将其转换为内部数据结构(Class对象)。
链接(Linking):
验证(Verification):验证字节码的正确性和安全性。
准备(Preparation):为类的静态变量分配内存,并设置默认初始值。
解析(Resolution):将符号引用转换为直接引用,即将类、方法、字段等的引用解析为内存地址。
初始化(Initialization):执行类的初始化代码,包括静态变量的赋值和静态代码块的执行。
双亲委派模式(Delegation Model)是Java类加载器的一种工作机制,它是一种层次化的加载机制。当一个类加载器收到加载类的请求时,它首先将请求委派给父类加载器,只有当父类加载器无法加载时,才由当前类加载器自己加载。
双亲委派模式的作用有以下几点:
避免重复加载:通过委派给父类加载器,可以避免重复加载同一个类,提高了类加载的效率。
安全性保证:通过双亲委派模式,可以确保核心类库的安全性,防止恶意代码替换核心类库。
类隔离:不同的类加载器加载的类处于不同的命名空间,实现了类的隔离,避免了类之间的冲突。
通过双亲委派模式,Java类加载器可以形成一个层次结构,每个类加载器都有自己的加载范围和加载策略,从而实现了类的动态加载和隔离。
24.java中如何确定一个对象是否需要回收,什么是GC
在Java中,对象的回收是由垃圾回收器(Garbage Collector,GC)负责的。GC通过判断对象是否可达来确定是否需要回收对象。
Java中的垃圾回收器使用了可达性分析算法来判断对象的可达性。当一个对象不再被任何活动的引用链所引用时,即对象不可达,垃圾回收器会将其标记为可回收的垃圾对象,并在适当的时候回收这些对象的内存。
对象的可达性是通过以下几种引用关系来判断的:
强引用(Strong Reference):如果一个对象具有强引用,即存在一个活动的强引用链指向该对象,那么该对象被认为是可达的,不会被回收。
软引用(Soft Reference):如果一个对象只有软引用指向它,那么在内存不足时,垃圾回收器可能会回收该对象。
弱引用(Weak Reference):如果一个对象只有弱引用指向它,那么在下一次垃圾回收时,无论内存是否足够,垃圾回收器都会回收该对象。
虚引用(Phantom Reference):虚引用主要用于跟踪对象被垃圾回收的状态,它不能单独使用,必须与引用队列(Reference Queue)一起使用。
GC的主要作用是自动管理内存,释放不再使用的对象所占用的内存空间,以避免内存泄漏和内存溢出的问题。GC通过回收垃圾对象的内存,使得可用内存得以重复利用,提高了程序的性能和稳定性。
需要注意的是,GC的具体实现和策略因Java虚拟机的不同而有所差异,如标记-清除算法、复制算法、标记-整理算法等。开发者无需手动触发垃圾回收,GC会根据需要自动进行回收。
25.Java的泛型的特点
1.类型安全性: 泛型可以在编译时捕获类型错误,防止在运行时出现类型转换异常。这有助于提前发现并解决类型相关的错误。
2.代码重用: 泛型允许编写可用于多种类型的通用代码,从而提高代码的重用性和维护性。
3.抽象性: 泛型可以将数据结构和算法与特定的数据类型解耦,使得代码更加通用和抽象。
4.避免强制类型转换: 在使用泛型的情况下,不再需要手动进行强制类型转换,减少了代码中的冗余和错误。
5.编译时检查: 泛型可以在编译时检查类型,确保集合中只存储了指定类型的对象,减少了运行时错误。
6.通配符: 泛型可以使用通配符(通配符类型)来表示未知类型,从而提高代码的灵活性。
7.泛型方法: 可以在普通类中定义泛型方法,使方法可以独立于类的泛型类型进行泛型化。
8.泛型接口和类: 可以定义泛型接口和类,允许实现类或子类在继承时指定具体的泛型类型。
9.类型擦除: Java的泛型在编译后会进行类型擦除,泛型信息只存在于编译阶段,在运行时被擦除。这可能会导致一些运行时的限制和行为。
26.springMVC的统一异常处理
在Spring MVC中,统一异常处理是一种将应用程序中的异常统一处理的机制,使得在异常发生时可以进行统一的处理操作,例如返回友好的错误信息页面、记录日志等。通过统一异常处理,可以提升用户体验和应用程序的可维护性。
以下是实现Spring MVC中统一异常处理的步骤:
- 自定义异常类: 首先,需要定义自己的异常类,可以继承自
RuntimeException
或其他适合的异常类。自定义异常类可以包含自己的异常信息字段。
public class CustomException extends RuntimeException {
// 添加自定义异常信息字段
}
- 编写异常处理器: 创建一个类,用于处理自定义异常和其他异常。这个类需要使用
@ControllerAdvice
注解,并在内部定义各种异常处理方法。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomException.class)
public ModelAndView handleCustomException(CustomException ex) {
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("errorMessage", ex.getMessage());
return modelAndView;
}
@ExceptionHandler(Exception.class)
public ModelAndView handleOtherExceptions(Exception ex) {
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("errorMessage", "An error occurred.");
return modelAndView;
}
}
在上述代码中,@ExceptionHandler
注解用于指定处理特定异常的方法。ModelAndView
用于返回一个错误页面,可以根据需要自定义。
-
配置视图解析器: 配置视图解析器,将返回的视图名称映射到实际的视图页面。
-
配置异常映射: 在Spring MVC的配置文件中,配置异常与视图的映射关系。
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<map>
<entry key="com.example.CustomException" value="error-page"/>
<entry key="java.lang.Exception" value="error-page"/>
</map>
</property>
</bean>
以上步骤完成后,当应用程序中抛出自定义异常或其他异常时,都会被GlobalExceptionHandler
捕获并进行统一处理,返回相应的错误页面或信息。
通过统一异常处理,可以有效地管理和处理异常,提高用户体验和应用程序的健壮性。
27.Java的内存模型(jmm)
Java内存模型(Java Memory Model,简称JMM)定义了Java程序在多线程环境下,对于内存的读写操作是如何进行的,以及多线程之间如何进行通信的规范。JMM确保多线程程序在不同的平台上表现一致,同时提供了可见性、有序性和原子性等保证,以解决多线程编程中的并发问题。
以下是JMM的一些关键特点和概念:
-
主内存和工作内存: JMM将内存划分为主内存和每个线程独立的工作内存。主内存存储所有共享变量,而每个线程的工作内存存储局部变量副本。
-
可见性: JMM保证,一个线程对共享变量的修改对其他线程是可见的。即一个线程修改了一个共享变量的值,其他线程能够立即看到这个修改。
-
有序性: JMM保证,程序的执行结果在单线程中与在多线程中的执行顺序是一致的。不同线程之间的操作可能会被重排序,但JMM会保证特定操作之间的顺序性。
-
原子性: JMM提供了一些原子操作,例如
volatile
变量的读写和锁操作,以保证特定操作的原子性。原子操作在多线程环境下具有不可分割性。 -
happens-before关系: happens-before是JMM中的一个概念,表示一个操作的结果对于另一个操作是可见的。happens-before关系是可传递的,可以用于指定不同操作之间的顺序关系。
-
volatile关键字:
volatile
关键字用于声明变量为volatile变量,保证了对该变量的写操作对其他线程的读操作是可见的,同时防止指令重排序。 -
synchronized关键字:
synchronized
关键字用于同步代码块或方法,提供了互斥访问共享资源的机制,保证了多线程访问的原子性和可见性。 -
final关键字:
final
关键字用于修
28.spring 中bean的模式(范围)有那些?
在Spring框架中,Bean的作用域(Scope)指定了一个Bean实例在应用程序中的生命周期和可见范围。Spring提供了多种Bean的作用域,每种作用域都适用于不同的使用场景。以下是Spring中常见的Bean作用域:
-
singleton(单例): 默认作用域,一个Bean定义对应一个实例,整个应用程序共享同一个实例。无论在何处注入该Bean,都将得到同一个实例。
-
prototype(原型): 每次请求或注入时,都会创建一个新的Bean实例。每个实例都是独立的,不会共享状态。
-
request: 在Web应用中有效,每个HTTP请求都会创建一个新的Bean实例,该实例仅在当前请求中有效,不同请求之间不会共享。
-
session: 在Web应用中有效,每个HTTP会话(用户会话)都会创建一个新的Bean实例,该实例在用户会话的整个生命周期内有效。
-
global session: 在基于portlet的Web应用中使用,类似于session作用域,但该作用域在不同portlet间共享。
-
application: 在Web应用中有效,每个ServletContext上下文中只会存在一个Bean实例,整个Web应用共享该实例。
-
websocket: 在Web应用中有效,每个WebSocket连接都会创建一个新的Bean实例,该实例仅在当前WebSocket连接中有效。
不同的作用域适用于不同的场景。例如,单例作用域适用于那些希望共享状态的Bean,原型作用域适用于每次请求都需要一个新实例的情况,而会话和请求作用域适用于Web应用中的会话和请求管理。
在Spring中,可以通过在Bean定义的时候使用scope
属性来指定作用域。例如:
<bean id="mySingletonBean" class="com.example.MyBean" scope="singleton"/>
<bean id="myPrototypeBean" class="com.example.MyBean" scope="prototype"/>
通过合理选择合适的Bean作用域,可以更好地管理Bean的生命周期,提高应用程序的性能和可维护性。
29.自动装配中byType、byName和constructor的区别?
自动装配(Autowiring)是Spring框架中的一项功能,用于自动将依赖注入到Bean中,而无需显式配置每个依赖关系。Spring支持多种自动装配模式,包括byType
、byName
和constructor
。以下是它们之间的区别:
- byType(按类型自动装配): 在
byType
模式下,Spring容器会自动查找与属性类型匹配的Bean,并将其注入到属性中。如果存在多个与属性类型匹配的Bean,则会抛出异常。这种模式要求属性类型在容器中必须是唯一的。
<bean id="myBeanA" class="com.example.MyBeanA" />
<bean id="myBeanB" class="com.example.MyBeanB" />
<!-- byType自动装配 -->
<bean id="myContainer" class="com.example.MyContainer" autowire="byType" />
- byName(按名称自动装配): 在
byName
模式下,Spring容器会根据属性的名称来查找对应的Bean,并将其注入到属性中。需要保证属性名称与Bean的id相匹配。
<bean id="myBeanA" class="com.example.MyBeanA" />
<bean id="myBeanB" class="com.example.MyBeanB" />
<!-- byName自动装配 -->
<bean id="myContainer" class="com.example.MyContainer" autowire="byName" />
- constructor(构造函数自动装配): 在
constructor
模式下,Spring容器会自动选择合适的构造函数并将依赖注入到构造函数的参数中。类似于byType
,要求参数类型在容器中必须是唯一的。
<bean id="myBeanA" class="com.example.MyBeanA" />
<bean id="myBeanB" class="com.example.MyBeanB" />
<!-- 构造函数自动装配 -->
<bean id="myContainer" class="com.example.MyContainer" autowire="constructor" />
总结:
byType
模式适合于属性类型唯一的情况,容易出现歧义,但也能简化配置。byName
模式通过属性名称来注入,需要保证属性名称与Bean的id相匹配,相对来说较安全。constructor
模式适用于构造函数注入,要求构造函数参数类型唯一,可以有效地保证依赖的注入顺序。
在选择自动装配模式时,需要根据具体的场景和依赖关系来进行选择,以满足应用程序的需求。
30.详细说明SSM整合流程
SSM 整合是指将 Spring、Spring MVC 和 MyBatis 这三个框架整合在一起,用于构建 Java Web 应用程序。这种整合可以让开发者更方便地利用这些框架的优点,提高开发效率和代码质量。下面是 SSM 整合的详细步骤:
-
创建 Maven 项目: 首先,创建一个 Maven 项目作为基础工程。
-
导入相关依赖: 在项目的
pom.xml
文件中,导入 Spring、Spring MVC 和 MyBatis 的依赖。 -
配置 Spring: 在项目中创建 Spring 的配置文件,如
applicationContext.xml
。在配置文件中,定义数据源、事务管理器、扫描组件等。 -
配置 Spring MVC: 创建 Spring MVC 的配置文件,如
springmvc-servlet.xml
。在配置文件中,配置组件扫描、视图解析器、处理器映射等。 -
配置 MyBatis: 创建 MyBatis 的配置文件,如
mybatis-config.xml
。在配置文件中,定义数据源、Mapper 扫描路径等。 -
创建持久层 Mapper: 创建 Mapper 接口和对应的 Mapper.xml 文件,用于定义数据库操作接口和 SQL 映射。
-
创建业务逻辑层 Service: 创建 Service 层接口和实现类,用于编写业务逻辑。
-
创建控制层 Controller: 创建 Spring MVC 的 Controller 类,处理 HTTP 请求和响应。
-
整合配置文件: 在
web.xml
中配置 Spring MVC 的前端控制器 DispatcherServlet,并指定配置文件的位置。 -
编写业务逻辑和控制层代码: 在 Service 层编写业务逻辑,Controller 层编写请求处理代码。
-
运行和测试: 部署项目到服务器,启动应用程序,通过访问相应的 URL 进行测试。
需要注意的是,整合过程中需要保证配置文件的正确性,特别是数据源、事务管理器、扫描路径等配置。整合后的项目可以充分利用 Spring 的依赖注入、AOP 和事务管理、Spring MVC 的请求处理和视图渲染,以及 MyBatis 的数据库访问和 ORM 功能。
总之,SSM 整合是将 Spring、Spring MVC 和 MyBatis 整合在一起,构建一个具有强大功能的 Java Web 应用程序。这种整合可以充分发挥各个框架的优点,使开发更高效、代码更清晰。