面试题集0515

1.Java中集合类型

List 的实现类有 ArrayList,Vector 和 LinkedList;

Set 的实现类有 HashSet(LinkedHashSet继承于HashSet) 和 TreeSet;

Map 接口有四个实现类:Hashtable,HashMap,TreeMap,LinkedHashMap;

2.ArrayList、LinkedList的区别

  • ArrayList 是基于动态数组的数据结构,LinkedList 是基于链表的数据结构;

  • 对于查询操作,ArrayList 较优 ; 对于新增和删除操作 add 和 remove,LinedList 较优

3.ArrayList扩容机制

https://www.cnblogs.com/zeroingToOne/p/9522814.html

在JDK1.8中,如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时(即添加第一个元素时),才真正分配容量,默认分配容量为10;当容量不足时(容量为size,添加第size+1个元素时),先判断按照1.5倍(位运算)的比例扩容能否满足最低容量要求,若能,则以1.5倍扩容,否则以最低容量要求进行扩容。

执行add(E e)方法时,先判断ArrayList当前容量是否满足size+1的容量; 在判断是否满足size+1的容量时,先判断ArrayList是否为空,若为空,则先初始化ArrayList初始容量为10,再判断初始容量是否满足最低容量要求;若不为空,则直接判断当前容量是否满足最低容量要求; 若满足最低容量要求,则直接添加;若不满足,则先扩容,再添加。

ArrayList的最大容量为Integer.MAX_VALUE

4.HashMap的底层实现原理?扩容机制

底层实现原理:

HashMap底层原理(图文并茂,简单易懂)_hashmap底层实现原理-CSDN博客

1、用HashMap存储数据( put(key,value) )时,会先操作key调用.hashcode()方法得出hash值,然后再通过哈希算法转换成数组的一个下标,对应的就是在数组上的存储位置。

2、如果该位置没有数据,则直接存储。如果该位置有数据,则会发生数据碰撞。

3、数据碰撞的时候遍历该位置上链表中的所有数据,并且通过equals()方法来比对每个数据的key。如果key相同的话,会将链表上该位置的数据进行覆盖。如果key不相同的话,在JDK1.8之前是实行的头插法,数据存储在链表头部,1.8之后实行的是尾插法数据存储在链表尾部。

4、JDK1.8之后,当链表上的节点个数(数据个数)大于等于8时并且数组长度不小于64的时候,链表数据结构自动进行树化转化成红黑树,当链表上的数据小于8个时,又会自动退化成链表

5、如果数组的长度小于64的话链表数据个数达到了8的话也不会转化成红黑树,而是先进行扩容,直到数组长度达到64

6、HashMap默认的数组长度是16,扩容的话有两种情况: 1)一种是数组上的元素达到了阈值,16*默认负载因子0.75,也就是12个元素的时候,数组长度扩容为两倍32,阈值变为24 2)还有一种是在没有红黑树的情况下,添加元素后数组中某个链表的长度超过了8,数组会扩容为两倍(比如创建HashMap集合后刚开始添加元素全都在一个链表中,当链表长度是9的时候数组扩容成32,链表长度是10的时候数组扩容成64,此时再添加元素,满足了数组长度为64链表长度到达8的两个条件,链表转换成红黑树)

扩容机制:(wanho课件)

  • HashMap底层是用数组存储数据的,数组存储的话不可能所有的数组存满16以后就变成链表,上面说了链表过长会影响效率,链表只是为了解决冲突准备的。

  • 数组扩容的原理:存储量达到75% (一个数组有16个长度,75%大概就是12个长度)就对数组进行扩容,由于计算机里都是二进制的,所以扩容的标准是最好是变长2倍。75%是基于时间与空间的考虑,如果扩容比例设得很小,例如是50%,那么有一半的空间是浪费的,如果比例设置得很高,例如是90%,当发生hash冲突的时候很难去触发这个条件。

5.HashMap和HashTable区别

HashMap和Hashtable的区别(绝对经典)_hashmap hashtable-CSDN博客

相同点:

两者都实现了 Map 接口

不同点:

  • HashMap 允许空键值,Hashtable 键和值不允许空;

  • HashMap 继承自 AbstractMap,Hashtable 继承自 Dictionary 类;

  • HashMap 的方法不是同步的,Hashtable 的方法是同步的。

6.HashMap和CurrentHashMap的区别:

HashMap 和 currentHashMap 终于总结清楚了!-腾讯云开发者社区-腾讯云

HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好

由于HashMap是线程不同步的,虽然处理数据的效率高,但是在多线程的情况下存在着安全问题,因此设计了CurrentHashMap来解决多线程安全问题。

HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。

7.怎样确保一个集合元素不被修改

我们很容易想到用final关键字进行修饰,我们都知道

final关键字可以修饰类,方法,成员变量,final修饰的类不能被继承,final修饰的方法不能被重写,final修饰的成员变量必须初始化值,如果这个成员变量是基本数据类型,表示这个变量的值是不可改变的,如果说这个成员变量是引用类型,则表示这个引用的地址值是不能改变的,但是这个引用所指向的对象里面的内容还是可以改变的。

那么,我们怎么确保一个集合不能被修改?首先我们要清楚,集合(map,set,list…)都是引用类型,所以我们如果用final修饰的话,集合里面的内容还是可以修改的。

那我们应该怎么做才能确保集合不被修改呢?我们可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的。他会报 java.lang.UnsupportedOperationException错。

同理:Collections包也提供了对list和set集合的方法。

Collections.unmodifiableList(List)、Collections.unmodifiableSet(Set)

8.Redis保持缓存的一致性

策略1:先更新数据库,再删除缓存

  • 问题:A查缓存没有,读数据库5,正准备放到缓存; B更新数据库6,删除缓存,A设置缓存5

策略2: 先删除缓存,再更新数据库

  • 问题: A删除缓存,准备更新数据库6 ; B查缓存没有,查数据库5,设置缓存5,A更新数据库6

策略3: 先删除缓存,再更新数据库,再延迟删除缓存(延迟双删)

  • 问题: A删除缓存,准备更新数据库6 ; B查缓存没有,查数据库5,设置缓存5,A更新数据库6

  • 解决:A更新数据库后,延迟N秒,再删除一下缓存

策略3的这种方式,称为延迟双删!

  • 原理:先删除缓存,然后更新数据库,最后(延迟N秒)再执行删除缓存。进行两次删除,且中间需要延迟一段时间。

  • 延迟时间要大于一次写操作的时间。原因:如果延迟时间小于写入redis的时间,会导致请求1清除了缓存,但是请求2缓存还未写入的尴尬。

延迟双删策略只是一种同步数据库与缓存的手段,在系统并发量不高的情况下可以使用这种方式解决,如果是并发量高的情况下,可以另寻其他解决方案,如使用canal

更好的方案,要借助于中间件canal,它能分析和收集binlog的日志结果。binlog是mysql自带的增、删、改的日志机制,但默认没有启用,要先启用。

9.什么是分布式锁

单体锁存在的问题:

  • 单体锁,即单体应用中的锁,通过加单体锁(synchronized或RentranLock)可以保证单个实例并发安全

  • 单体锁是JVM层面的锁,只能保证单个实例上的并发访问安全

  • 如果将单体应用部署到多个tomcat实例上,由负载均衡将请求分发到不同的实例

  • 每个tomocat实例都是一个JVM进程,多实例下会存在数据一致性问题。

分布式锁:

  • 分布式应用中所有线程都去获取同一把锁,但只有一个线程可以成功的获得锁,其他没有获得锁的线程必须全部等待,直到持有锁的线程释放锁。

  • 分布式锁是可以跨越多个tomcat实例,多个JVM进程的锁,所以分布式锁都是设计在第三方组件中的

  • 分布式锁都是通过第三方组件来实现的,目前主流的解决方案是使用Redis或Zookeeper来实现分布式锁

10.Redis缓存的淘汰策略有哪些

主要有以下几种策略:

  • volatile-lru:从已设置过期时间的数据集(server. db[i]. expires)中,挑选最近最少使用的数据淘汰。

  • volatile-ttl:从已设置过期时间的数据集(server. db[i]. expires)中,挑选距离过期时间最近的数据淘汰。

  • volatile-random:从已设置过期时间的数据集(server. db[i]. expires)中,随机选择数据淘汰。

  • allkeys-lru:从所有数据集(server. db[i]. dict)中,挑选最近最少使用的数据淘汰。

  • allkeys-random:从所有数据集(server. db[i]. dict)中,随机选择数据淘汰。

  • noeviction:不淘汰任何数据,当内存满时,新的写入操作会报错。

具体如何配置:

  • 在Redis的配置文件redis.conf,找到如下的配置项:

    maxmemory <bytes>  # 设置Redis使用的内存上限
    maxmemory-policy noeviction  # 设置淘汰策略
  • 当Redis使用的内存达到maxmemory设置的上限时,会按照maxmemory-policy设置的策略进行淘汰数据

11.项目中使用Redis的地方,为什么使用Redis

  • 数据缓存(数据查询、短连接、权限菜单、新闻内容、商品内容等)

  • 分布式锁

  • 消息队列/任务队列(秒杀、抢购、12306等)

  • 分布式集群架构中的session分离

  • 聊天室的在线好友列表

  • 应用排行榜

  • 网站访问统计

  • 数据过期处理(可以精确到毫秒)

使用redis的好处? (1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) (2)支持丰富数据类型,支持string,list,set,sorted_set,hash (3) 支持事务 :redis对事务是部分支持的,如果是在入队时报错,那么都不会执行;在非入队时报错,那么成功的就会成功执行。 redis监控:锁的介绍 (4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除。

12.Mybatis中有大量数据插入时,怎么处理

两种方式:

  1. foreach操作,使用动态SQL中foreach拼接字符串

  2. batch模式,使用MyBatis提供的BATCH模式

SqlSession session = factory.openSession(ExecutorType.BATCH); // Batch批处理模式

13.Linux中命令:查看日志、进程、磁盘、结束进程?

tail     显示文件尾部内容       tail -f  动态查看文件的内容,一般用于查看日志
netstat  查看网络状态、进程信息  netstat  -ntpl      
ps       查看进程信息           ps  -ef  |  grep  ssh
kill     结束进程               kill  -9  进程号
df       查看磁盘              df  -hT

14.Mybatis中如何实现分页的?

https://www.cnblogs.com/tanghaorong/p/14017180.html

  1. 原生SQL的Limit分页

  2. Mybatis自带的RowBounds分页

  3. 自定义拦截器插件进行分页

  4. 使用PageHelper插件分页

15.Mybatis中一级缓存和二级缓存的区别?

将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能。

合理使用缓存是优化中最常见的操作。

一级缓存

一级缓存是SqlSession级别的,存储在SqlSession中,默认是开启的

一般来说,一个请求中的所有增删改查操作都是在同一个sqlSession里面的,可以认为每个请求都有自己的一级缓存

如果同一个SqlSession会话中 2个查询中间有一个 insert 、update或delete 语句,那么之前查询的所有缓存都会清空

流程:

  • 用户发起查询请求,查找某条数据,SqlSession 先去缓存中查找,是否有该数据,如果有,读取;

  • 如果没有,从数据库中查询,并将查询到的数据放入一级缓存区域,供下次查找使用。

  • 当SqlSession 执行commit,即增删改操作时会清空缓存。这么做的目的是避免脏读。

生效的条件:

  • 必须使用同一SqlSession,执行同一个查询方法才会有效

  • 同一个SqlSession,如果查询条件不同,则无效

  • 同一个SqlSession,如果两次查询期间执行了任何一次的增删改操作,则无效

二级缓存

二级缓存是 mapper 级别的缓存,多个SqlSession去操作同一个Mapper的sql语句时,多个SqlSession可以共用二级缓存

二级缓存是跨SqlSession的,因此二级缓存的作用范围更大

二级缓存默认是关闭的,需要手动开启

流程:

  • 开启二级缓存后,用户查询时,会先去二级缓存中找,找不到了再去一级缓存中找

  • 一级缓存也没有查询到,则查询数据库

  • 当SqlSession会话提交或者关闭时,一级缓存的数据会刷新到二级缓存中

启用二级缓存:在 XxxMapper.xml 映射文件中,添加:<cache/>

16.Vuex组成,核心概念

Vuex是单向数据流的设计模式,其有五大核心概念State、Getter、Mutation、Action和Module

17.MySQL中聚合函数、统计函数有哪些?

五大常见聚合函数类型

  • AVG() :只适用于数值类型的字段或变量。不包含NULL值

  • SUM() :只适用于数值类型的字段或变量。不包含NULL值

  • MAX() :适用于数值类型、字符串类型、日期时间类型的字段(或变量)不包含NULL值

  • MIN() :适用于数值类型、字符串类型、日期时间类型的字段(或变量)不包含NULL值

  • COUNT() :计算指定字段在查询结构中出现的个数(不包含NULL值)

18.MySQL如何进行分组统计?

1.只有在分组之后,可以在select里面使用聚合函数

2.having——进行分组之后的过滤操作

where只能进行分组前的过滤,分组后的过滤工作只有having才能做

where语句中不允许使用聚合函数,having语句中允许使用聚合函数

19.常用的设计模式有哪些?

单例模式(Singleton Pattern): 确保一个类只有一个实例,并提供全局访问点。例如,Java中的Runtime类使用了单例模式,保证整个应用程序中只存在一个Runtime对象。在Spring框架中,默认的bean作用域也是单例模式,确保每个bean只有一个实例。

工厂模式(Factory Pattern):

将对象的创建和使用分离,通过工厂类来创建对象。Java中的Calendar类使用工厂模式,通过getInstance()方法获取对象。Spring提供了BeanFactory和ApplicationContext等工厂模式的实现,用于创建和管理对象的实例。

观察者模式(Observer Pattern): 定义对象之间的一对多依赖关系,当一个对象状态改变时,其所有依赖者都会收到通知并自动更新。Java中的事件监听机制就是使用了观察者模式。Spring事件机制也是基于观察者模式实现的,通过事件源和监听器来实现对象之间的解耦。

适配器模式(Adapter Pattern): 将一个类的接口转换为客户端所期望的另一个接口,使得原本不兼容的类能够一起工作。Java中的InputStreamReader类将字节流转换为字符流。在Spring MVC中,处理器适配器就是基于适配器模式实现的,将不同类型的处理器适配到统一的处理器接口上。

策略模式(Strategy Pattern): 定义一系列算法,并将每个算法封装起来,使它们可以互相替换。Java中的Comparator接口是策略模式的应用。Spring的BeanPostProcessor是基于策略模式实现的,通过在不同的时机调用不同的策略方法来实现对bean的后置处理。

模板方法模式(Template Method Pattern): 定义一个算法的骨架,将具体步骤的实现延迟到子类中。Java中的Servlet中的doGet()和doPost()方法使用了模板方法模式。Spring的JdbcTemplate也是基于模板方法模式实现的,通过定义抽象的模板方法和具体的子类实现来实现数据访问的流程控制。

代理模式(Proxy Pattern): 为其他对象提供一种代理以控制对这个对象的访问。Java中的动态代理和静态代理是代理模式的应用。Spring的AOP(面向切面编程)是基于代理模式实现的,通过在运行时动态生成代理对象来实现对目标对象的增强。

JDK动态代理:使用Java的Proxy类,基于接口代理。它要求目标对象必须实现一个或多个接口。所有通过代理对象调用的方法都会被转发到一个调用处理器,这个处理器负责调用原始对象的方法,以及执行任何额外的操作。 CGLIB代理:如果目标对象没有实现任何接口,则Spring会退回到使用CGLIB库,通过继承目标对象的方式创建代理对象。这种方式不要求目标对象实现接口。

20.创建线程的方式有哪些?

1、继承Thread类创建线程类

(1) 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。

(2) 创建Thread子类的实例,即创建了线程对象。

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

2、通过Runnable接口创建线程类,方便数据共享

(1) 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

(2) 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

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

3、通过Callable和Future创建线程,可以有返回值

(1) 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值,而且可抛出异常

(2) 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

(3) 使用FutureTask对象作为Thread对象的target创建并启动新线程。

(4) 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

21.创建线程池的方式

【Thread】线程池的 7 种创建方式及自定义线程池-CSDN博客

Executors.newFixedThreadPool():创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待; Executors.newCachedThreadPool():创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收多余的线程,若线程数不够,则新建线程; Executors.newSingleThreadExecutor():创建单个线程数的线程池,它可以保证先进先出的执行顺序; Executors.newScheduledThreadPool():创建一个可以执行延迟任务的线程池; Executors.newSingleThreadScheduledExecutor():创建一个单线程的可以执行延迟任务的线程池; Executors.newWorkStealingPool():创建一个抢占式执行的线程池(任务执行顺序不确定)JDK 1.8 添加 ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置

22.线程池的参数有哪些?

强调:阿里巴巴的JAVA开发手册,上面有个建议:【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

七大参数

序号名称类型含义
1corePoolSizeint核心线程池大小
2maximumPoolSizeint最大线程数大小
3keepAliveTimelong线程最大空闲时间
4unitTimeUnit时间单位
5workQueueBlockingQueue<Runnable>线程等待队列
6threadFactoryThreadFactory线程创建工厂
7handlerRejectedExecutionHandler拒绝策略

23.控制线程的函数有哪些(生命周期的方法)?

生命周期

1、 新建 (new):新创建了一个线程对象。

2、 就绪 (可运行状态)(runnable):线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。

3、 运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

4、 阻塞 (block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。

5、 死亡 (dead)(结束):

线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

24.什么是死锁?

什么是死锁?死锁如何解决?-CSDN博客

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。

当多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进,这种情况就是死锁。

死锁产生的四个必要条件 (1)互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

(2)不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

(3)请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

(4)循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。

附页题集:

  1. Java中集合类型

  2. ArrayList、LinkedList的区别

  3. ArrayList扩容机制

  4. HashMap的底层实现原理?扩容机制

  5. HashMap和HashTable区别

  6. HashMap和current

  7. 怎样确保一个集合元素不被修改

  8. Redis保持缓存的一致性

  9. 什么是分布式锁

  10. Redis缓存的淘汰策略有哪些

  11. 项目中使用Redis的地方,为什么使用Redis

  12. Mybatis中有大量数据插入时,怎么处理

  13. Linux中命令:查看日志、进程、磁盘、结束进程?

  14. Mybatis中如何实现分页的?

  15. Mybatis中一级缓存和二级缓存的区别?

  16. Vuex组成,核心概念

  17. MySQL中聚合函数、统计函数有哪些?

  18. MySQL如何进行分组统计?

  19. 常用的设计模式有哪些?

  20. 创建线程的方式有哪些?

  21. 创建线程池的方式

  22. 线程池的参数有哪些?

  23. 控制线程的函数有哪些(生命周期的方法)?

  24. 什么是死锁?

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值