前言
去深圳某医疗健康行业的互联网公司面试,要求是2年以上的开发。
正文
Java 基础
1、聊一聊 HashMap(1.7 和 1.8)
答:hashMap 的底层结构在 jdk1.7 中由数组+链表实现,在 jdk1.8 中由数组+链表+红黑树实现,以数组+链表的结构为例。
默认初始容量时 16 ,负载因子时 0.75。
线程安全:非线程安全
解决哈希冲突:1.7 采用头插法的拉链表式,如果成百上千个节点 hash 碰撞,会退化成一个长链表,会花费 O(N) 时间查找,而且容易形成死循环链接
1.8 采用尾插法+链表+红黑树,当某个桶的链表的长度到达某个阈值(8),这个链表会变成红黑树,查找时间为 O(N Log N)。
2、1.8后的 HashMap 链表长度到达多少后会变成红黑树,多少退化成链表,为什么?
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
答:8, 6。 为什么就可以基于空间和时间来权衡了。
1) TreeNodes占用空间是普通 Nodes 的两倍,所以只有当 bin(bin就是bucket,即HashMap中hashCode值一样的元素保存的地方) 包含足够多的节点时才会转成TreeNodes,而是否足够多就是由TREEIFY_THRESHOLD 的值决定的。
Because TreeNodes are about twice the size of regular nodes, we
use them only when bins contain enough nodes to warrant use
(see TREEIFY_THRESHOLD). And when they become too small (due to
removal or resizing) they are converted back to plain bins. In
usages with well-distributed user hashCodes, tree bins are
rarely used. Ideally, under random hashCodes, the frequency of
nodes in bins follows a Poisson distribution
(http://en.wikipedia.org/wiki/Poisson_distribution) with a
parameter of about 0.5 on average for the default resizing
threshold of 0.75, although with a large variance because of
resizing granularity. Ignoring variance, the expected
occurrences of list size k are (exp(-0.5)*pow(0.5, k)/factorial(k)).
The first values are:
0: 0.60653066
1: 0.30326533
2: 0.07581633
3: 0.01263606
4: 0.00157952
5: 0.00015795
6: 0.00001316
7: 0.00000094
8: 0.00000006
more: less than 1 in ten million
2)当 hashCode 离散性很好的时候,树型bin用到的概率非常小,因为数据均匀分布在每个bin中,几乎不会有bin中链表长度会达到阈值。
但是在随机hashCode下,离散性可能会变差,然而JDK又不能阻止用户实现这种不好的hash算法,因此就可能导致不均匀的数据分布。
不过理想情况下随机hashCode算法下所有bin中节点的分布频率会遵循泊松分布,我们可以看到,一个bin中链表长度达到8个元素的概率为0.00000006,几乎是不可能事件。
所以,之所以选择8,是根据概率统计决定的
3、为什么要用 ConcurrentHasMap,1.7 和 1.8 有什么区别,分段锁是怎么实现的?
答:
底层数据结构: 1.7 底层采用 分段的数组 + 链表实现, 1.8 采用 数组 + 链表/红黑树。实现线程安全:1.7 使用分段锁,对整个桶数组进行分割分段,每一把锁只锁住容器其中一部分数据,多线程访问容器里不同数据段的数据,就不存在锁竞争,提高并发率。
1.8 之后使用 Node 数组 + 链表 + 红黑树,并发控制用 Synchronized 和 CAS 来操作。
分段锁的设计思想是。
首先 ConcurrentHashMap 最外层不是一个大的数组,而是一个 Segment 的数组。 每个
Segment 包含一个与 HashMap 数据结构差不多的链表数组。整体数据结构如下图所示。其次,并发级别控制了 Segment 的个数,在一个ConcurrentHashMap 创建后Segment
的个数是不能变的,扩容过程过改变的是每个 Segment 的大小。分段 Segment 继承了重入锁 ReentrantLock,有了锁的功能,每个锁控制的是一段,当每个 Segment
越来越大时,锁的粒度就变得有些大了。
4、Volatile 可见性是怎么实现的?
首先 volatile 是一个变量修饰符,只能用来修饰变量。
可见性 :当一个变量对共享变量进行修改,那其他的线程都是可以看到的修改后的最新值。
原理 : Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。
不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。
Java 中的 volatile 关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。
综上所述:基于 CPU 缓存一致性协议,JVM实现了volatile的可见性。当一个变量被 volatile 修饰时,那么对它的修改会立刻刷新到主存,当其它线程需要读取该变量时,会去内存中读取新值。
补充:
缓存一致性协议
一致性缓存:所有缓存副本中的值都相同,多个CPU处理器共享缓存并且更改共享数据时,更改必须广播到所有缓存副本。
在处理器中,嗅探是一致性缓存的常见的机制。工作原理是通过总线监听所有的共享数据的操作,当修改共享数据的事件发生时,所有监听处理器都会检查是否存在当前共享数据的副本,如果存在,监听处理器会执行刷新或者直接失效缓存副本操作。
5、说一下动态代理,JDK 和 Cglib 以及实现方式?
JDK 动态代理
利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。
Cglib动态代理
利用 ASM 开源包,将代理对象类的文件加载进来,它允许我们在运行时对字节码进行修改和动态生成。Cglib通过继承方式实现代理。
区别
JDK动态代理:代理所有“实现的有接口”的目标类 Cglib 动态代理:代理任意一个目标类
JDK动态代理要比 Cglib 代理执行速度快,但性能不如 Cglib 好
注意
Cglib 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 Cglib 做动态代理的
6、说一下泛型,为什么需要,以及工作机制,用 Object 不行?
什么是泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
好处
它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
工作机制
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如 List<String> 在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。
什么是类型擦除
Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。如在代码中定义 List<Object> 和 List<String> 等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass());//true
}
什么是原始类型?
原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
其他的可以看看这个10 道 Java 泛型面试题
7、JDK 1.8 新特性有哪些,lambda 有哪些用法?
框架
1、说一下 Dubbo 以及 RPC
可以分为为什么要,它是什么,有什么作用来回答。
为什么需要 Dubbo?
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
这是摘自官网的原话,你能把这个发展过程说一下,基本就可以了!Dubbo 官网
也可简单的说:
随着服务化的进一步发展,服务越来越多,服务之间的调用和依赖关系也越来越复杂,诞生了面向服务的架构体系(SOA),也因此衍生出了一系列相应的技术,如对服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出等行为进行封装的服务框架。就这样为分布式系统的服务治理框架就出现了,Dubbo 也就这样产生了。
Dubbo 是什么?
是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
有什么作用
1、透明化的远程方法调用
就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。
2、软负载均衡及容错机制
可在内网替代F5等硬件负载均衡器,降低成本,减少单点。
3、服务自动注册与发现
不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的 IP 地址,并且能够平滑添加或删除服务提供者。
也可从 服务管理、服务依赖、服务扩容来回答。
参考官网
什么是 RPC ?
RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
简单的说,RPC就是从 A 机器上通过参数传递的方式调用 B 机器上的一个函数或方法并得到返回的结果。
2、说一下Dubbo 的 Netty 通信协议
这是 Dubbo 默认使用 Netty 作为通讯框架。
是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty是基于 NIO(Nonblocking IO,非阻塞IO) 的,它封装了jdk的 NIO
这个通信框架可以扯半个小时以上
Netty面试题(2020最新版)
3、说一说 Dubbo 的监控器是用到什么设计模式?
4、Spring 框架的注解( @RequestMapping)怎么实现的,如果让你设计要怎么做?
设计模式
1、适配器模式
2、单例模式(DCL),解释为什么需要加 Volatile
DCL 与 Volatile
网络协议
1、HTTP 是长链接还是短链接?
2、HTTP 的结构有什么,比如 header 有什么,request body 有什么?
Linux
1、Linux 启动过程加载那些文件,具体点
2、Linux 的 用 tail 查看倒数 2000 行日志
tail -n 2000 xxx.log
3、解释 777 ,每个位置分别对应什么的?
第一个 7 代表文件属主或者拥有者的权限
第二个 7 代表同组用户的权限
第三个 7 代表其他用户的权限
那么上面的777 代表的含义是这样的:
r : read 表示读权限 --数字4表示
w : write 表示写权限 --数字2表示
x: excute 表示执行权限 --数字1表示
4、查看进程用什么命令?
5、find 命令使用
MySQL
1、平时怎么优化 SQL 语句的?
2、Mysql 的索引,包括最左适配原则,怎么样会失效,依据什么原理实现的?
3、数据库引擎(InnoDB)什么时候用到表锁?
杂七杂八:
聊一聊 Redis
说一下 CAP 原理以及 Zookeeper 用到哪些?
平时是怎么用的 Git 简单说一下。
有了解过 Docker 吗?
额外
平时怎么学技术的?
未来的规划是什么?
面试官给我的建议:找个框架吃透底层,后面会发现框架的结构都是通用,大同小异。
参考文章:
阿里面试题:为什么Map桶中个数超过8才转为红黑树
窥探真相:volatile 可见性实现原理
深入理解Volatile关键字及其实现原理
Java泛型类型擦除以及类型擦除带来的问题
聊一聊-JAVA 泛型中的通配符 T,E,K,V,?
Dubbo面试题