记一次面试经历
# Hashmap的put过程
- 计算index的过程
- hash碰撞时数据的插入方式
- 超出临界值的resize的过程
- 参考链接
# Hashmap的key到下标的计算方式
- hashcode
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
1
2
3
4 - 容量取模
- hash方法详解
# Hashmap的数据结构
- 数组+链表(大于8则是红黑树)
# Hashmap发生hash碰撞后数据是插入头节点还是尾节点,为什么
- JDK8以前是头插法,JDK8后是尾插法
- 因为头插法会造成死链(并发)
- JDK7用头插是考虑到了一个所谓的热点数据的点(新插入的数据可能会更早用到),但这其实是个伪命题,因为JDK7中扩容 rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置(就是因为头插) 所以最后的结果 还是打乱了插入的顺序 所以总的来看支撑JDK7使用头插的这点原因也不足以支撑下去了 所以就干脆换成尾插 一举多得
- hashmap用数组+链表。数组是固定长度,链表太长就需要扩充数组长度进行rehash减少链表长度。如果两个线程同时触发扩容,在移动节点时会导致一个链表中的2个节点相互引用,从而生成环链表
# ConcurrentHashMap 是怎么实现线程安全的
- volatile ,cas, 锁(RenntrantLock/synchronized)
# synchronized 和 Lock 的区别
# RenntrantLock 特性, 公平锁和非公平锁的实现
# RenntrantLock 介绍,怎么实现可重入的
# JDK8 的 ConcurrentHashMap 为什么用 synchronized 代替了RenntrantLock
synchronized 在put 发生hash碰撞时锁定链表的第一个Node()
- 减少内存开销(主要) 假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。
- 获得JVM的支持 可重入锁毕竟是API这个级别的,后续的性能优化空间很小。 synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。
# GC 展开
# HTTP 请求类型
# HTTP 状态码,3开头代表啥意思
# 为什么存在跨域问题
# 跨域的解决方案
- jsonp
- nginx 代理
- cors
# jsonp的实现原理
# Spring 实现的设计模式
- IOC,DI
- 工厂设计模式
- 单例设计模式
- 代理设计模式
- 模板方法 xxxTemplate
- 观察者模式 ApplicationEvent,ApplicationListener
- 适配器模式 HandlerAdapter 适配 不同的controller 到 handler
- 装饰者模式 InputStream,Wrapperxxx, Decoratorxxx
# Spring 哪里用到了 责任链
# 过滤器和拦截器的区别
# 集群和分布式的区别
# 微服务的概念
# 微服务的带来的问题
# zuul 和 Gateway 的区别
# 分布式事务的解决方案, Seata的实现原理
# redis 分布式锁的实现 setnx(set if no exit)
# redis redision 实现(lua 脚本的原子性)
# mysql b+ tree 和 mongoDB的 b tree 的区别
# 注册中心加 负载均衡实现灰度调用
# RabbmitMQ 的exchange 类型
消息流程 Producer > Broker[Exchange> Queues] > Consumer
- direct (routingkey相同)
- topic (routingkey 支持通配符 *,#)
- fanout (全部Queues)
- header (消息头 参数路由)