云徙科技Java实习一面面经
1.用过Java集合没,讲一下HashMap,你平时在什么场景下用过HashMap?
HashMap是否是线程安全的?
HashMap的数据结构: 底层使用hash表数据结构,即数组和链表或红黑树
- 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
- 存储时,如果出现hash值相同的key,此时有两种情况。
a. 如果key相同,则覆盖原始值;
b. 如果key不同(出现冲突),则将当前的key-value放入链表或红黑树中
- 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
HashMap的jdk1.7和jdk1.8有什么区别
- JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
- jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8) 时并且数组长度达到64时,将链表转化为红黑树,以减少搜索时间。扩容 resize( ) 时,红黑树拆分成的树的结点数小于等于临界值6个,则退化成链表
2.重载和重写的区别
重载(Overloading)和重写(Overriding)都是Java中面向对象编程的特性,它们都可以让子类继承父类的方法。但是它们之间有一些重要的区别:
- 定义方式:重载(Overloading):在同一个类中,可以有多个方法名相同但参数列表不同的方法。当调用一个方法时,编译器会根据参数的数量、类型和顺序来判断调用哪个方法。重写(Overriding):在子类中,必须定义与父类同名、参数类型和返回值类型相同的方法。这样才能覆盖掉父类中的方法,使得子类对象调用该方法时执行的是子类自己的代码。
- 访问权限:重载(Overloading):方法名相同但参数列表不同,因此它们的访问权限是一样的,都是public、protected或private。重写(Overriding):子类中的方法必须使用super关键字来调用父类的方法,这意味着子类中的方法具有比父类中相同的方法更低的访问权限。
总之,重载和重写都是Java中多态性的重要特性,但它们的作用和实现方式有所不同。重载允许在同一类中定义多个同名但参数不同的方法,而重写允许子类覆盖父类的方法并提供自己的实现。
3.==和equals的区别
- == 既可以比较基本类型也可以比较引用类型,对于基本类型就是比较值,对于引用类型及时比较内存的地址
- equals的话,他是属于java.lang.Object类里面的方法,如果该方法没有被重写过,默认也是== ,我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equasl是比较值的错误观点.
- 具体要看自定义类里面有没有重写Object的equals方法来判断
- 通常情况下,重写equals方法,会比较类中的属性是否相等
/** * ==和equals的区别 */ public class Test_01 { public static void main(String[] args) { /** * 比较基本数据类型 */ int a=1; double b=1.0; char c=1; System.out.println(a==b); //true System.out.println(a==c); //true /** * 引用数据类型 */ Customer c1 = new Customer("小明", 20); Customer c2 = new Customer("小明", 20); System.out.println(c1 == c2); //false System.out.println(c1.equals(c2)); //false // String String str1 = new String("sth"); String str2 = new String("sth"); System.out.println(str1 == str2); // false System.out.println(str1.equals(str2)); // true // java中String new和直接赋值的区别 // 对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中, // 如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。 String s1="123"; //在常量池中 String s2="123"; System.out.println(s1==s2); //true String s3 = new String("123"); //存储在堆内存中 System.out.println(s1==s3); /** * Integer */ Integer i1=1; Integer i2=1; System.out.println(i1==i2); //true Integer i3=128; Integer i4=128; System.out.println(i3==i4); //false System.out.println(i3.equals(i4)); } } class Customer { private String namg; private int age; public Customer(){ } public Customer(String namg, int age) { this.namg = namg; this.age = age; } public String getNamg() { return namg; } public void setNamg(String namg) { this.namg = namg; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
4. m面向对象的三大特性
面向对象编程的三大特性是封装、继承和多态。
- 封装(Encapsulation):封装是指将数据和方法捆绑在一起,形成一个类,对外部隐藏实现细节,只提供必要的接口给外部使用。通过封装,可以保护数据的安全性和完整性,防止外部程序直接访问和修改数据,从而提高程序的可维护性和稳定性。
- 继承(Inheritance):继承是指子类可以从父类中继承属性和方法,避免重复编写代码。通过继承,可以提高代码的复用性和灵活性,使得程序更加模块化和可扩展。但是需要注意的是,过度的继承会导致代码变得复杂和难以维护。
- 多态(Polymorphism):多态是指同一个方法可以根据不同的参数类型和数量表现出不同的行为。通过多态,可以提高代码的灵活性和可扩展性,使得程序更加通用和易于维护。Java中的多态主要有两种形式:方法重载(Method Overloading)和方法重写(Method Overriding)。
5.synchronized 和 Lock 有什么区别?重点讲一下lock锁?
synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
6.MySQL四大特性
ACID是数据库系统中的四个基本属性,用于描述事务的正确性、一致性、隔离性和持久性。
- A代表原子性(Atomicity),指一个事务是一个不可分割的整体,要么全部执行成功,要么全部失败回滚。
- C代表一致性(Consistency),指事务执行的结果必须符合一定的约束条件,如外键约束、唯一性约束等。
- I代表隔离性(Isolation),指多个事务同时操作同一个数据时,每个事务都应该感觉不到其他事务的存在,即事务之间相互隔离。
- D代表持久性(Durability),指一旦事务提交,其对数据的修改应该是永久性的,即使系统发生故障或崩溃也不会丢失。
这些属性保证了数据库系统的可靠性和稳定性,使得数据库可以被广泛应用于各种应用程序中。
7.InnoDB存储引擎对MVCC的实现
8.MySQL索引
索引是帮助MySQL高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的IO成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低了CPU的消耗。
MySQL的默认的存储引擎InnoDB采用的B+树的数据结构来存储索引,选择B+树的主要的原因是:第一阶数更多,路径更短,第二个磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据,第三是B+树便于扫库和区间查询,叶子节点是一个双向链表
第一:在B树中,非叶子节点和叶子节点都会存放数据,而B+树的所有的数据都会出现在叶子节点,在查询的时候,B+树查找效率更加稳定
第二:在进行范围查询的时候,B+树效率更高,因为B+树都在叶子节点存储,并且叶子节点是一个双向链表
我主要说了聚簇索引和非聚簇索引。
9.MySQL中的锁
MySQL中常见的锁包括:
- 行级锁(Row-level Locking):也称为共享锁(Shared Lock),它允许事务读取一行数据,但不允许其他事务修改该行数据。行级锁适用于读多写少的场景。
- 表级锁(Table-level Locking):也称为排他锁(Exclusive Lock),它允许事务独占整个表,防止其他事务对表进行任何操作。表级锁适用于写操作频繁的场景。
- 页面锁(Page Locking):它是一种特殊的锁,用于锁定数据库中的页面(通常为4KB)。页面锁可以防止其他事务修改同一页上的数据,从而提高并发性能。
- InnoDB引擎的行级锁和事务隔离级别:InnoDB是MySQL中最常用的存储引擎之一,它支持多种事务隔离级别,如读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。不同的隔离级别会影响到InnoDB的锁机制,例如在可重复读隔离级别下,所有写操作都需要获取行级锁,而在串行化隔离级别下,所有写操作都需要获取表级锁。
除了上述常见的锁之外,还有一些其他的锁机制,如记录级锁、间隙锁等。不同的锁机制适用于不同的场景,需要根据具体情况来选择合适的锁策略。
10.rabbitMQ如何实现消息可靠性
我们要保证消息的不丢失。主要从三个层面考虑
第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据
第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化
第三个是开启消费者确认机制为auto,由spring确认消息处理成功后完成ack,当然也需要设置一定的重试次数,我们当时设置了3次,如果重试3次还没有收到消息,就将失败后的消息投递到异常交换机,交由人工处理
11.讲解一下线程池的核心参数,你在平时怎么使用到了多线程或者线程池
线程池核心参数主要参考ThreadPoolExecutor这个类的7个参数的构造函数
- corePoolSize 核心线程数目
- maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目)
- keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
- unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等
- workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
- threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
- handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略
工作流程
1,任务在提交的时候,首先判断核心线程数是否已满,如果没有满则直接添加到工作线程执行
2,如果核心线程数满了,则判断阻塞队列是否已满,如果没有满,当前任务存入阻塞队列
3,如果阻塞队列也满了,则判断线程数是否小于最大线程数,如果满足条件,则使用临时线程执行任务
如果核心或临时线程执行完成任务后会检查阻塞队列中是否有需要执行的线程,如果有,则使用非核心线程执行任务
4,如果所有线程都在忙着(核心线程+临时线程),则走拒绝策略
拒绝策略:
1.AbortPolicy:直接抛出异常,默认策略;
2.CallerRunsPolicy:用调用者所在的线程来执行任务;
3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4.DiscardPolicy:直接丢弃任务;
12.Spring AOP
aop是面向切面编程,在spring中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等
权限管理、表单验证、事务管理、信息过滤、拦截器、过滤器、页面转发等等。
13.Spring,循环依赖。
说了Spring三级缓存那一堆
循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
②二级缓存:缓存早期的bean对象(生命周期还没走完)
③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的
解决流程
第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories 第二,A在初始化的时候需要B对象,这个走B的创建的逻辑 第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories 第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键 第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects 第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects 第七,二级缓存中的临时对象A清除