每天一套面试题Day9-数字马力
3.arraylist和linkedlist的区别。
ArrayList底层基于动态数组,因此用索引访问快,因为可以直接定位地址(基址+元素大小×索引),但是增删慢,因为可能涉及到元素的移动。 linkedlist底层基于链表(是双向吗?),因此增删快(头的),但是想要访问只能从头开始遍历,增加删除快,但是由于存储了指针,所以内存也相对占据更多。可以用来做队列和栈。
是双向链表,维护了头尾指针 中间位置的插入删除:LinkedList只需要修改指针,ArrayList需要移动元素,(前者消耗在遍历,后者在移动),大量数据LinkedList会更快
4.arraylist插入的底层逻辑。
移动? 检查容量:检查当前数组剩余空间是否足够。 扩容:如果不够,则创建一个新的、更大的数组(通常是原容量的1.5倍),并将旧数组的所有元素拷贝到新数组中。 元素移动:从插入索引开始,将所有后续元素都向后移动一位。 插入新元素:在指定索引位置放入新元素。 修改大小:size属性加1。
5.hashmap 1.7和1.8的区别。
一,原来是数组+链表,现在是数组+链表/红黑树(在数组大小>=64且链表长度>=8的时候,转成红黑树) 二,在扩容后确定元素新的位置的时候,原本是重新取余计算,但是由于扩容是数组大小变成原来的二倍,所以要么在原本的位置,要么在原本位置+原数组大小的位置,用哈希值与上原数组的大小,如果为0那么在原位置,如果不为0,就在原本位置+原数组大小。
插入方式:JDK 1.7采用头插法(新元素插入链表头部),在并发扩容时可能产生死循环。JDK 1.8改为尾插法(新元素插入链表尾部),解决了死循环问题,但仍非线程安全。
6.线程池的好处。
线程的创建销毁需要开销。如果用线程池的话,可以直接复用线程,无需等待。
- 降低资源消耗(节约),复用已创建的线程。线程的创建和销毁涉及到与操作系统内核的交互,是重量级的操作,成本很高。
- 提高响应速度,无需等待线程创建
- 提高线程的可管理性(核心价值:可控),核心线程数、最大线程数:可以控制系统的并发量,任务队列:可以缓冲来不及处理的任务,平滑流量峰值。拒绝策略:当线程池和队列都已满时,可以通过预定义的策略(如丢弃、抛异常等)来处理新任务,增强系统的健壮性。线程工厂:可以方便地设置线程名、优先级、守护线程状态等,便于在出问题时进行排查和调试。
7.equals和==的区别。
equals默认比较哈希值,也可以重写哈希值来确定。❌ ==对于基本数据类型比较值,对于引用数据类型比较的是地址。 因此如果比较两个属性相同的student对象,前者可以一样,后者不会。
有误更正: equals方法: equals是Object类中的一个方法,因此所有的类都继承了该方法。默认情况下,Object类的equals方法实现与==相同,即比较两个引用是否指向同一个对象。 但是,很多类(如String、Integer等)重写了equals方法,使其比较的是对象的内容是否相等。因此,对于这些类,equals方法用于比较两个对象的内容是否相同。 重写自己类的equals方法时,必须同时重写hashCode()方法,这是为了满足equals和hashCode之间的契约(例如在HashMap、HashSet等集合中正常工作)。HashMap在散列的时候哈希值相同后会调用equals方法,不重写的话,原本认为相同的对象的哈希值不同,直接散列到了不同的位置。
8.引用类型作为方法参数,修改属性会影响原对象吗。
会修改。因为方法参数以及指向了原对象。
9.ThreadLocal中key为什么是弱引用,value为什么是强引用。
10.垃圾回收算法。
基于两种 引用计数 可达性分析算法 标记-复制算法(新生代,没有内存碎片),标记-整理算法(老年代,没有内存碎片),标记-回收?❌
标记-清除(老年代,有内存碎片) 引用计数式垃圾收集引用计数算法判断是否存活
11.new 一个对象的完整流程。
对象的创建 检查类加载 分配空间 初始化0 真正的初始化
类加载检查
当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池(方法区的运行时中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
分配内存
接下来虚拟机将为新生对象分配内存,
初始化为零值
内存分配完成之后,虚拟机必须将分配到的内存空间(但不包括对象头)都初始化为零值
必要设置(如对象头)
对象头中 Mark Word(标记字)支持Java的synchronized关键字,分代年龄用于分代收集算法,哈希码确保对象的唯一标识
构造函数 init方法
实例变量的初始化(成员变量包括实例变量和静态变量。如果类中有静态变量,它们已经在类加载时初始化了,不会等到对象创建。) 合并了字段初始化、初始化块、构造函数代码
12.spring aop原理。
面向切面编程。底层基于动态代理(如果对象没有实现接口,用子类作为代理,如果对象实现了接口,用??
如果目标对象实现了接口,默认使用JDK动态代理。 如果目标对象没有实现接口,则使用CGLIB库生成目标对象的子类作为代理
13.spring事务不会生效的情况。
spring事务基于AOP,所以如果在一个类中的方法调用另一个有事务注解的方法,那么不会生效,因为方法拿到的是this,而不是代理对象。AOP基于代理。
14.如何实现两个独立的事务。
为了确保每个方法都在各自独立的事务中运行,我们应该确保这两个方法是通过代理对象调用的。通常,我们将这两个方法放在不同的类中,假设我们有两个服务类:ServiceA和ServiceB,每个服务类中都有一个事务方法。 然后,在另一个类中(比如一个组合服务类)同时调用这两个服务类的方法,这样这两个方法就会在各自的事务中运行。 @Transactional(propagation = Propagation.REQUIRES_NEW)创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说,无论外部是否有事务,内部方法都会开启一个新的事务,且内部事务与外部事务相互独立。 REQUIRES = 需要、要求 NEW = 新的 传播行为(Propagation)
15.常见mysql数据库引擎的区别。
只知道Innor DB?
16.联合索引题目,索引失效场景。
联合索引是由多个列的值构成的复合索引。创建的时候顺序如果是A,B ,C使用的时候也必须是这个顺序,否则会失效
失效: 对索引列进行运算或函数操作 使用不等于操作符 模糊查询以通配符开头 复合索引未遵循最左前缀原则
17.深分页问题解决方案。
深分页是指当数据量很大时,查询靠后的页码(比如第1000页)会出现性能问题。因为数据库需要扫描 offset 之前的所有数据,然后丢弃,再返回需要的 size 条数据。 游标分页:记录上一页最后一条的ID sql SELECT * FROM table WHERE id > last_id ORDER BY id LIMIT 10 覆盖索引:只查索引不回表。指一个索引包含了查询中所有需要字段,这样数据库引擎只需要通过索引就可以返回查询结果,而无需回表(即不需要再根据主键去主键索引中获取行数据) 延迟关联:先查ID再关联。先通过覆盖索引找到需要的主键,然后再根据这些主键去关联原表获取需要的其他列。这样做可以避免在大偏移量时回表大量数据。
18redis基本数据类型。
五种。String,Set,去重比如关注的人。ZSet,排序的去重集合。比如班级成绩。List,有顺序。Hash,存储对象结构,比如用户信息
Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等
19.redis持久化机制,设计一个签到功能如何实现。
RDB和AOF混合。RDB相当于上个时间点的复制品,AOF记录了从上次RDB后追加的命令。这样既不会因为持久化阻塞redis太久,也不会因为AOF需要重新再都执行一遍命令而导致恢复的时间长。
也避免了RDB可能丢失数据的问题。 签到功能:用Redis的BitMap,当用户签到就设置对应日期的位为1,配合Redis的持久化。 位图很大的节省的空间。 小林
题目贡献作者:offer哪里来
链接:https://www.nowcoder.com/discuss/816248046596980736
来源:牛客网
#面试真题#记录刷过的面试真题
查看20道真题和解析