上海某公司二面

之前因为感冒发烧其他方面,就忘记发一面(比较简单就不发了)

面试时长35分钟。

1.自我介绍(传统)

2.拷打八股文


2.1 ArrayList 和 LinkedList 的区别?

我:ArrayList:基于动态数组实现,默认初始容量为10,当元素数量超过当前容量时会自动扩容。

随机访问效率高:由于基于数组,ArrayList支持通过索引快速访问元素,时间复杂度为O(1)。

插入和删除效率低:在中间或开头插入/删除元素时,需要移动后续元素,时间复杂度为O(n),尾部插入O(1)

LinkedList:底层数据结构:LinkedList基于双向链表实现,每个节点包含数据元素和指向前后节点的引用。

插入和删除效率高:在任意位置插入(头尾部)/删除元素时,只需调整相邻节点的引用,时间复杂度为O(1)。

顺序访问效率低:由于基于链表,LinkedList不支持随机访问,需要从头或尾开始遍历,时间复杂度为O(n)。

都不能保证线程安全


面试官:arrayList底层扩容是怎么样扩容的?

我: arrayList在插入元素(调用add方法时),会调用ensureCapacityInternal(size + 1)方法来确定集合确保添加元素成功的最小集合容量minCapacity的值。然后调用ensureExplicitCapacity(minCapacity)方法来确保为了添加minCapacity容量的值是否需要进行扩容,首先将结构性修改计数器加1;然后判断minCapacity与当前元素数组的长度的大小,如果minCapacity比当前元素数组的长度的大小大的时候需要扩容。最后调用grow(minCapacity)方法,首先将原元素数组的长度增大1.5倍(oldCapacity + (oldCapacity >> 1)),然后对扩容后的容量与minCapacity进行比较:① 新容量小于minCapacity,则将新容量设为minCapacity;②新容量大于minCapacity,则指定新容量。最后将旧数组拷贝到扩容后的新数组中。


2.2 了解HashMap吗?

我:HashMap 是一个用键值存储对象的元素,在jdk1.8前用数组+链表的,通过对key进行计算出hascode在通过函数计算出hsah值最后与(数组长度-1)跟hash值取余运算计算出存放的位置,如果当前位置有元素,判断key和hash值是否相同,相同则覆盖,不相同则会出现hash冲突,用拉链法解决hash冲突:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

jdk1.8后则使用数组+链表+红黑树,当链表长度大于8时会转为红黑树提高搜索效率。


面试官: 你能说一下hashmap的扩容过程吗?

我: HashMap有两个重要元素:初始化容量(默认16)和负载因子(默认0.75),负载因子可以理解为键值对在数组中的最大密度,当键值对数值大于数组长度乘以负载因子时也是HashMap数组容量达到上限时会进行扩容,此时会调用Resize函数对数组进行初始化和扩容,先创建新的数组,再遍历旧数组元素和节点,并重新计算节点,将元素插入到新数组中,这样就扩容完毕了


面试官Hashmap为什么要用红黑树这个平衡树不采用其他的树?

我:

因为红黑树是一种平衡的二叉树,其插入、删除、查找的最坏时间复杂度都为 O(logn),避免了二叉树最坏情况下的O(n)时间复杂度。之所以不用平衡二叉树是因为平衡二叉树是比红黑树更严格的平衡树,为了保持保持平衡,需要旋转的次数更多,也就是说平衡二叉树保持平衡的效率更低,所以平衡二叉树插入和删除的效率比红黑树要低。

而不用B/B+树的原因:

B和B+树主要用于数据存储在磁盘上的场景,如果用B/B+树的话,在数据量不是很多的情况下,数据都会“挤在”一个结点里面,这个时候遍历效率就退化成了链表。而红黑树多用于内存中排序,也就是内部排序。



2.3 索引的数据结构是什么,为什么要采用这个数据结构?

我: 索引一般有B+tree,Btree,hash索引等,然后主要回答了一下他们的区别和优缺点


2.4 mysql #和$符号分别代表什么?

我: #{}:占位符号,可以防止sql注入(替换结果会增加单引号‘’)

${}:sql拼接符号(替换结果不会增加单引号‘’,like和order by后使用,存在sql注入问题,需手动代码中过滤)$方式一般用于传入数据库对象,例如传入表名


2.5 mysql分页查询后面越来越慢,怎么优化?

我: 这是因为随着offset的增大,性能会越来越慢

优化: 解决方案:Select * from product limit 80000,20;

1.子表查询(id查询) Select * from product where id >=(Select id from product limit 80000,1)limit 20;

2.Join(链表 id查询-推荐) Select * from product a Join(Select id from product order by id limit 80000,20) b on a.id = b.id; 如果数据不要保证顺序性可以不要加order by id,可以提升效率


2.6 有了解过synchronized锁的升级吗?

我: synchronized锁分为四个状态:无锁,偏向锁,轻量锁和重量锁

无锁:当一个对象被创建之后,还没有线程进入,这个时候对象处于无锁状态。


偏向锁当锁处于无锁状态时,有一个线程A访问同步块并获取锁时,会在对象头和栈帧中的锁记录记录线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来进行加锁和解锁,只对比对象头中的线程ID和当前线程是否一致。


轻量级锁在偏向锁的基础上,又有另外一个线程B进来,这时判断对象头中存储的线程A的ID和线程B不一致,就会使用CAS竞争锁,然后线程尝试获取锁,如果成功,则当前线程获得锁;失败,表示其他线程竞争锁,当前线程便尝试CAS来获取锁。


重量级锁当线程没有获得轻量级锁时,线程会CAS自旋来获取锁,当一个线程自旋10次之后,仍然未获得锁,那么就会升级成为重量级锁。成为重量级锁之后,线程会进入阻塞队列(EntryList),线程不再自旋获取锁,而是由CPU进行调度,线程串行执行。


2.7 知道voliate吗?

我: voliate的作用是可见性和防止指令重排序,举个例子 i++ ,它分为三步,第一步先向内存申请空间,然后进行+1操作,最后返回内存中。如果不用voliate的话在高并发情况下会导致顺序执行乱序。


面试官: 它是怎样保证可见性和防止指令重排序的呢? (当时脑子宕机了就回答了一个内存屏障)


应该这样回答:Volatile是通过MESI缓存一致性协议来保证可见性的。首先cpu会根据共享变量是否带有Volatile字段,来决定是否使用MESI协议保证缓存一致性。如果有Volatile,汇编层面会对变量加上Lock前缀,当一个线程修改变量的值后,会马上经过store、write等原子操作修改主内存的值(如果不加Lock前缀不会马上同步),为什么监听到修改会马上同步呢?就是为了触发cpu的嗅探机制,及时失效其他线程变量副本。cpu总线嗅探机制监听到这个变量被修改,就会把其他线程的变量副本由共享S置为无效I,当其他线程在使用变量副本时,发现其已经无效,就回去主内存中拿一个最新的值。

通过内存屏障保证防止指令重排序。(个人理解不知道回答的对不对)


2.8 Aop实现原理?

我:动态代理


面试官:动态代理有哪几种呢?

我: 静态代理,jdk动态代理,cglib动态代理?

面试官:jdk动态代理,cglib动态代理的区别有哪些?

我: JDK 动态代理是基于接口的代理技术。它使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来创建代理对象。当你调用代理对象的任何方法时,调用会被转发到 InvocationHandler 的 invoke 方法。为了使用 JDK 动态代理,你的类必须实现一个或多个接口。JDK 动态代理的局限性在于,它只能代理接口方法,如果你有一个类并希望代理其非接口方法,则不能使用 JDK 动态代理。

CGLIB(Code Generation Library)是一个强大的、高性能、高质量的 Code 生成库,它可以在运行时扩展 Java 类和实现 Java 接口。不同于 JDK 动态代理,CGLIB 不需要接口,它是通过继承方式实现代理的。


最后反问













0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
鱼友7987
下载 APP