农行软开面经9/5

List、Set、Map区别

① List 和 Set 实现了 Collection 接口,List 的元素有序可重复、Set 的元素无序不可重复,Map 是以键值对存储元素的。

Set不能根据索引获取元素,检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。

List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。

② List 的实现包括 ArrayList(数组实现)、LinkedList(链表实现)、Vector(线程安全的 ArrayList) 和 Stack(继承 Vector,有栈的语义)。

③ Set 的实现包括 HashSet(通过 HashMap 实现,元素就是 HashMap 的 Key,Value 是一个 Object 类型的常量)、LinkedHashSet(通过 LinkedHashMap 实现)和 TreeSet(可以对元素排序,通过实现 Compare 接口或 Comparator 接口)。

④ Map 的实现主要包括 HashMap、LinkedHashMap(通过 LinkedList 维护插入顺序) 和 TreeMap(可以按 Key 排序,通过实现 Compare 接口或 Comparator 接口)。

  • ## List转为数组

在Java中,要将List转换为数组,我们可以使用List接口的toArray()方法。这个方法将List中的元素复制到一个数组中。下面是一个示例代码:

import java.util.List;
import java.util.ArrayList;

public class ListToArrayExample {
    public static void main(String[] args) {
        List<Integer> myList = new ArrayList<>();
        myList.add(1);
        myList.add(2);
        myList.add(3);

        Integer[] myArray = new Integer[myList.size()];
        myList.toArray(myArray);

        for (Integer num : myArray) {
            System.out.println(num);
        }
    }
}

需要注意的是,我们需要传递一个与List大小相同的数组来容纳所有元素。这是将List转换为数组的一种常见方法,可以在实际项目中非常有用

java的内存空间

方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有
的内存区域。
Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内
存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实
例都在这里分配内存
方法区(Method Area),方法区(Method Area)与Java堆一样,是各个线程共享的内存区
域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数

程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块
较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是
线程私有的,它的生命周期与线程相同。
3个特点:
每个线程运行时所需要的内存,称为虚拟机栈
每个栈由多个栈帧 (frame) 组成,对应着每次方法(多个方法)调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发
挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服
务,而本地方法栈则是为虚拟机使用到的Native方法服务。

线程的状态

新建状态(New):当通过关键字 new 创建一个线程对象,但还没有调用其 start() 方法启动线程时,线程处于新建状态。在这个状态下,线程对象已经创建,但尚未分配操作系统资源。

就绪状态(Runnable):线程进入就绪状态后,表示它已经准备好运行,但尚未分配到CPU执行时间。线程可能在就绪队列中等待操作系统的调度。

运行状态(Running):线程进入运行状态时,表示它正在执行代码,占用CPU资源。在多核处理器上,多个线程可以同时处于运行状态。

阻塞状态(Blocked):线程在某些情况下会被阻塞,例如等待I/O操作、等待获取锁、等待其他线程的通知等。在阻塞状态下,线程不会消耗CPU资源,它需要等待特定的事件发生,才能继续执行。

等待状态(Waiting):线程进入等待状态时,表示它正在等待某些条件满足,而不是像阻塞状态那样等待具体的事件。线程可以通过调用 Object.wait()、Thread.join() 或类似的方法进入等待状态。

超时等待状态(Timed Waiting):这是等待状态的一种变体,表示线程等待一段时间后才会继续执行。线程可以通过调用 Thread.sleep()、Object.wait(timeout)、Thread.join(timeout) 等方法进入超时等待状态。

终止状态(Terminated):线程完成了它的任务或者因为异常而终止时,线程进入终止状态。一旦线程处于终止状态,它不能再次运行。

线程的状态之间可以相互转换,例如,一个新建的线程可以调用 start() 方法进入就绪状态,然后在操作系统调度后进入运行状态。线程在运行时也可以因等待某些条件而进入等待状态,或因I/O操作而进入阻塞状态。最终,线程完成执行或抛出异常后会进入终止状态。

了解线程的不同状态对于多线程编程和线程调试非常重要,因为它们有助于理解线程的行为和调试线程相关的问题。

Java 怎么实现线程?⭐

① 继承 Thread 类并重写 run 方法。实现简单,但不符合里氏替换原则,不可以继承其他类。

② 实现 Runnable 接口并重写 run 方法。避免了单继承局限性,实现解耦。

③实现 Callable 接口并重写 call 方法。可以获取线程执行结果的返回值,并且可以抛出异常。

在 JAVA中,用 Thread 类代表线程,所有线程对象,都必须是Thread类或者Thread类子类的实例。每个线程的任务就是执行一段顺序执行的代码,JAVA使用线程执行体来容纳这段代码。

第一种,通过继承Thread类创建线程类

通过继承Thread类来创建并启动多线程的步骤如下:

1、定义一个类继承Thread类,并重写Thread类的run()方法,run()方法的方法体就是线程要完成的任务,因此把run()称为线程的执行体;

2、创建该类的实例对象,即创建了线程对象;

3、调用线程对象的start()方法来启动线程;

第二种,通过实现Runnable接口创建线程类

这种方式创建并启动多线程的步骤如下:

1、定义一个类实现Runnable接口;

2、创建该类的实例对象obj;

3、将obj作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象;

4、调用线程对象的start()方法启动该线程;

第三种,通过CallableFuture接口创建线程

通过实现Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程的执行体,那么,是否可以直接把任意方法都包装成线程的执行体呢?从JAVA5开始,JAVA提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大,call()方法的功能的强大体现在:

1、call()方法可以有返回值;

2、call()方法可以声明抛出异常;
通过Furetask的get方法可以获得返回的结果。

线程的状态

6种
在这里插入图片描述

wait和sleep的区别

方法的归属不一样
1、sleep是Tread的静态方法,
2、wait是Object的成员方法,每个对象都有
醒来的时机不一样
1、执行sleep(long)和wait(long)都会停留一段时间
2、wait(long)和wait()可以被notify唤醒
锁的特性不一样:
1、await方法需要与锁配合使用,调用wait方法时会释放锁,让别的线程去占用(占用了cpu,但是你们还是可以使用)
2、sleep的话,调用之后不会释放对象锁(占用cpu,别的线程仍然不可以使用)

数据库的索引

官方介绍索引是帮助MySQL高效获取数据的数据结构。更通俗的说,数据库索引好比是一本书
前面的目录,能加快数据库的查询速度。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往是存储在磁盘上的文件中
的(可能存储在单独的索引文件中,也可能和数据一起存储在数据文件中)。
我们通常所说的索引,包括聚集索引、覆盖索引、组合索引、前缀索引、唯一索引等,没有特
别说明,默认都是使用B+树结构组织(多路搜索树,并不一定是二叉的)的索引。

B+树索引和Hash索引

在这里插入图片描述
1、BTree是最常用的mysql数据库索引算法,也是mysql默认的算法。因为它不仅可以被用在**=,>,>=,<,<=和between这些比较操作符上,而且还可以用于like**操作符,只要它的查询条件是一个不以通配符开头的常量

2、Hash Hash索引只能用于对等比较,例如**=,<=>(相当于=)操作符。由于是一次定位数据**,不像BTree索引需要从根节点到枝节点,最后才能访问到页节点这样多次IO访问,所以检索效率远高于BTree索引。

Hash索引的特性

①、Hash 索引只支持等值比较查询、无法索成范围查询检索,B+tree索引的叶子节点形成有序链表,便于范围查询。

②、Hash 索引无法做 like ‘xxx%’ 这样的部分模糊查询,因为需要对 完整 key 做 Hash 计算,定位bucket。而 B+tree 索引具有最左前缀匹配,可以进行部分模糊查询。

③、Hash索引中存放的是经过Hash计算之后的Hash值,而且Hash值的大小关系并不一定和Hash运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算。B+tree 索引的叶子节点形成有序链表,可用于排序。(Hash索引是无序的

3、Hash 索引不支持多列联合索引,对于联合索引来说,Hash 索引在计算 Hash 值的时候是将索引键合并后再一起计算 Hash 值,不会针对每个索引单独计算 Hash 值。因此如果用到联合索引的一个或者几个索引时,联合索引无法被利用;

4、因为存在哈希碰撞问题,在有大量重复键值情况下,哈希索引的效率极低。B+tree 所有查询都要找到叶子节点,性能稳定;

mysql隔离级别

READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可
能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

脏读是什么,脏读发生在哪种隔离级别下

就是别人还没有提交事务,你就拿来使用了。这个数据不是准确的
发生在读未提交的隔离级别下

不可重复读是什么

"不可重复读"是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外-个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

幻读是什么

是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据这种修改是向表中插入一行新数据。一般都是插入数据。
例子:
事务A要两次读取表T的中数据,虽然设置 repeatable read 可以防止事务B对数据进行修改,但是事务B却可以向表T中插入新的数据

Redis数据结构用过哪些

总的来说redis是用来缓存数据的,可以不同的应用场景来选择数据结构

回答:一共五种 (一)String 这个其实没啥好说的,最常规的set/get操作,value可以是String也可以
是数字。一般做一些复杂的计数功能的缓存。 (二)hash 这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以
cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。

        //8.1随机生成token,作为登录令牌;  生成key
        String token = UUID.randomUUID().toString();
        //8.2将User对象转为HashMap存储;  生成value
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(user,userDTO);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);
        //8.3存储
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY+token,userMap);//用定义的常量拼接key值
        stringRedisTemplate.expire(LOGIN_USER_KEY+token,30,TimeUnit.MINUTES);//设置有效时间
        //返回token
        return Result.ok(token);

(三)list 使用
List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用range命令,做基于
redis的分页功能,性能极佳,用户体验好。

 // 1.从 Redis 中查询商铺缓存
        List<String> shopTypeJsonList = stringRedisTemplate.opsForList().range(CACHE_SHOP_TYPE_LIST_KEY, 0, -1);

(四)set 因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。做过的项目是缓存了商铺的信息

 // 1.从 Redis 中查询商铺缓存
        List<String> shopTypeJsonList = stringRedisTemplate.opsForList().range(CACHE_SHOP_TYPE_LIST_KEY, 0, -1);

(五)sorted set sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。

//1.查询top5的点赞用户 zrange key 0 4
        Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);

在这里插入图片描述

@Controller注解和@Service注解的区别

1、在平时的开发中,我们通常在控制层采用注解@Controller,在业务层采用注解@Service。spring在启动时,有一个非常核心的类ConfigurationClassPostProcessor会对类路径下的所以类进行扫描,将符合条件的bean扫描出来添加到beanDefinitionMap集合中,方便接下来的实例化。具体的扫描过程比较复杂,仅仅贴出核心判断逻辑代码。
org.springframework.core.type.filter.AnnotationTypeFilter

protected boolean matchSelf(MetadataReader metadataReader) {
	AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
	return metadata.hasAnnotation(this.annotationType.getName()) ||
			(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}

代码解释:
先会堆@Conponent的类加载,如果没有,会对类组合注解@Controller等加载
(1)this.annotationType.getName():获取的是注解@Component的全路径名org.springframework.stereotype.Component。
(2)metadata.hasAnnotation(this.annotationType.getName()):判断当前的类是否直接采用注解@Component。
(3)metadata.hasMetaAnnotation(this.annotationType.getName()):如果当前的类没有直接采用@Component,而是采用了类组合注解@Controller,判断组合注解@Controller中是否包含@Component。

@Contoller在SpringMVC中的特别作用:

Spring会遍历上面扫描出来的所有bean,过滤出那些添加了注解@Controller的bean,将Controller中所有添加了注解@RequestMapping的方法解析出来封装成RequestMappingInfo存储到RequestMappingHandlerMapping中的mappingRegistry。后续请求到达时,会从mappingRegistry中查找能够处理该请求的方法。

MVC的流程

在这里插入图片述

接口开发模式

1、用户发送出请求到前端控制器DispatcherServlet,
2、DispatcherServlet收到请求调用HandlerMapping (处理器映射器)
3、HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter (处理器适配器)
5、HandlerAdapter经过适配调用具体的处理器 (Handler/Controller)方法上
6、添加了@ResponseBody
7、通过HttpMessageConverter来返回结果转换为JSON并响应

@RequestMapping注解作用,提交方式除了get和post还有哪些

GET:GET请求是来获取数据的,相当于数据库中的select,不对服务器数据做任何改动,get请求参数加在URL后面,必要时需进行涉密处理。
POST:POST向服务器发送数据,相当于数据库的insert操作。会修改数据种类,在创建新的数据,提交表单时,采用POST请求。
PUT:PUT也是想服务器发送数据,相当于数据库的update操作,用来修改数据内容,但是不会增加
DELETE:DELETE用来删除数据,相当于数据库中的delete操作。

spring的Bean生命周期

嗯!,这个步骤还是挺多的,我之前看过一些源码,它大概流程是这样的
首先会通过一个非常重要的类,叫做BeanDefinition获取bean的定义信息,这里面就封装了bean的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等等这些信息
在创建bean的时候,第一步是调用构造函数实例化bean
第二步是bean的依赖注入,比如一些set方法注入,像平时开发用的@Autowire都是这一步完成
第三步是处理Aware接口,如果某一个bean实现了Aware接口就会重写方法执行
第四步是bean的后置处理器BeanPostProcessor,这个是前置处理器
第五步是初始化方法,比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct
第六步是执行了bean的后置处理BeanPostProcessor,主要是对bean进行增强,有可能在这里产生代理对象(这也是为啥需要三级缓存的原因)
最后一步是销毁bean
在这里插入图片描述

依赖注入的方式

注解开发模式:

构造函数注入(Constructor Injection):
Setter 方法注入(Setter Injection):
自动装配,在·html定义注意的bean,在java代码中使用@Autowired来导入:byType、byName、autodetect结合了byType和byName两种方式,首先尝试byType,如果没有找到匹配的bean,再尝试byName
ssm中同样是这几种方法,但是不需要写在xml中,可以使用@Aotuwire这个直接调用被@Controller、conponent、serverce定义的bean

spring AOP中before和after是干什么的,切点的注解?

Spring切面可以应用五种类型的通知:
before:前置通知,在一个方法执行前被调用。
after: 在方法执行之后调用的通知,无论方法执行是否成功。
after-returning: 仅当方法成功完成后执行的通知。
after-throwing: 在方法抛出异常退出时执行的通知。
around: 在方法执行之前和之后调用的通知。

spring中使用了工厂模式,说一说工厂模式

单例模式:Spring 中的 Bean 默认情况下都是单例的。无需多说。

工厂模式:工厂模式主要是通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象。

代理模式:最常见的 AOP 的实现方式就是通过代理来实现,Spring主要是使用 JDK 动态代理和 CGLIB 代理。
1、BeanFactory:IOC的设计当然就涉及到BeanFactory;有了Spring框架,我们就很少自己进行对象的创建了,而我们使用到的对象当然就是交给Spring的工厂模式来创建的了。其中BeanFactory是Spring容器的顶层接口,也是Bean工厂最上层的接口,其会有很多工厂实现例如,我们可以把BeanFactory看成是一种工厂方法模式。
2、FactoryBean
作用:考虑下,如果需要实例化一个复杂Bean,按照传统方式,我们需要在中提供大量的配置信息,但这些信息还是我们不需要去关注的,我们只想要创建一个Bean,并不想知道这个Bean是经过多么复杂的流程创建出来的,这时就可以使用FactoryBean来把一些配置信息、依赖项进行封装在内部,很简单的来实例化一个Bean
简化第三方bean的导入
和第三方框架的整合,首先在第三方框架内部是不会出现像@Component这样的Spring注解,为什么呢?因为如果使用了@Component注解,是不是就要依赖Spring。第三方框架与Spring框架整合,就是需要将第三方框架中的类转化为BeanDefinition放到Spring容器中去,以注解或者XML形式都行。往往第三方类都有一个非常重要的类,我们只需要将这个类注入到Spring就可以了,不过这个类想要正常工作可能需要其他依赖,所以我们在Spring中想注入这个类,就需要先知道它依赖的其他类,可是其他类还会有依赖,这样做就太麻烦了,所以第三方框架可以写一个FactoryBean类,提供一个简单的入口和Spring框架进行整合,我们只需要将这个FactoryBean类以注解或XML的格式放到容器就可以了。(Mybatis集成入Spring就是这样)
源码比较复杂

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值