最新面试问题
最新面试问题
设计模式及场景
- 单例模式:
网站在线人数统计;数据库连接池;Windows任务管理器等 - 工厂模式:
IOC创建 Bean; - 观察者模式:
一对多的关系,如商家上架新产品通知其粉丝;取消订单时其他相关的功能也撤销原有操作; - 策略模式:
会员打折(初级会员无折扣,中级会员9折,高级会员7折); - 责任链模式:
多个对象可以处理同一个请求,但具体由哪个对象处理则在运行时动态决定; - 装饰者模式:
在原有功能的基础上进行功能扩展;
集合
- ArrayList
- ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
- 以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。
- LinkList
- LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法
- HashMap
- JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间
- ConcurrentHashMap
- Java8 中的 ConcruuentHashMap 使用的 Synchronized 锁加 CAS 的机制
- Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Java8 Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。
ArrayList 与 LinkList 区别
- 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
- 底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
- 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以对于add(E e)方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置i插入和删除元素的话((add(int index, E element)) 时间复杂度近似为o(n))因为需要先移动到指定位置再插入。
- 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
- 内存空间占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
线程池
- 运行过程
- 判断核心线程池是否已满(都有任务),若不是则创建一个新的工作线程来执行新任务;否则判断盘对工作队列是否已满
- 判断工作队列是否已满,若不是则将提交的新任务加入工作队列中;若工作队列已满,则判断线程池是否已满
- 判断线程池是否已满,若不是则创建一个新的线程来执行新任务;否则交给饱和策略来处理这个任务
- 七大参数
-
corePoolSize 线程池核心线程大小
-
maximumPoolSize 线程池最大线程数量
-
keepAliveTime 空闲线程存活时间
-
unit 空间线程存活时间单位:keepAliveTime的计量单位
-
workQueue 工作队列
-
threadFactory 线程工厂
-
handler 拒绝策略/饱和策略
-
- 拒绝策略
1、AbortPolicy:直接抛出异常(默认)
2、CallerRunsPolicy:只用调用所在的线程运行任务
3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
4、DiscardPolicy:不处理,丢弃掉。
Mybatis
#{ } 与 ${}
-
#{} 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符;而${}仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。
-
#{} 解析之后会将String类型的数据自动加上引号,其他数据类型不会;而${} 解析之后是什么就是什么,他不会当做字符串处理。
-
#{} 很大程度上可以防止SQL注入(SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作);而${} 主要用于SQL拼接的时候,有很大的SQL注入隐患。
4.在某些特殊场合下只能用${},不能用#{}。例如:在使用排序时ORDER BY ${id},如果使用#{id},则会被解析成ORDER BY “id”,这显然是一种错误的写法。
分页插件
实现 Interceptor 接口
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
其中一个关键的方法就是 intercept,从而实现拦截
分页插件的原理就是使用MyBatis提供的插件接口,实现自定义插件,在插件的拦截方法内,拦截待执行的SQL,然后根据设置的dialect(方言),和设置的分页参数,重写SQL ,生成带有分页语句的SQL,执行重写后的SQL,从而实现分页。
所以原理还是基于拦截器
redis 5种数据结构
- String - 字符
set key valule / get key / del key - Hash - 哈希
hset key field value ( hset myhash username lixin;hset myhash password 123):myhash{username:lixin;password:123}
hget key filed (hget myhash username )
hgetall key (hgetall myhash )
hdel key field - List - 列表
lpush / rpush key value (lpush mylist a)
lrange key start end : 范围获取 lrange mylist 0 -1 : 获取全部
lpop / rpop key : 删除列表最左边/最右边的元素,并将元素返回
del key - Set - 集合
sadd key value
smembers key (所有)
srem key value - SortedSet - 有序集合
zadd** key score value ( zadd mysort 65 lisi ) :通过score属性来排序
zrange key start end (withsocres)
zrem key value
锁升级
- 偏向锁
- 轻量级锁
- 重量级锁
GC 垃圾回收
- 可达性算法
GC root 对象
-
Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的
-
Thread - 活着的线程
-
Stack Local - Java方法的local变量或参数
Java 中
a.虚拟机栈(栈桢中的本地变量表)中的引用的对象
b.方法区中的类静态属性引用的对象
c.方法区中的常量引用的对象
d.本地方法栈中JNI的引用的对象
mysql 索引
- 组合索引的有效及失效场景
Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c)。 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。
IOC 容器初始化流程
两种容器类型:BeanFactory && ApplicationContext
-
Resource定位;指对BeanDefinition的资源定位过程。通俗地讲,就是找到定义Javabean信息的XML文件,并将其封装成Resource对象。
-
BeanDefinition的载入;把用户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。
-
向IoC容器注册这些BeanDefinition。
排序算法时间复杂度
Java8 新特性
- Stream流
- Filter(过滤)
- Sorted(排序)
- Match(匹配)
- Count(计数)
- Map(映射)
中间操作
- Optional
- Optional 是一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在Java 8中,你应该返回 Optional 而不是 null。
//of():为非null的值创建一个Optional
Optional<String> optional = Optional.of("bam");
// isPresent(): 如果值存在返回true,否则返回false
optional.isPresent(); // true
//get():如果Optional有值则将其返回,否则抛出NoSuchElementException
optional.get(); // "bam"
//orElse():如果有值则将其返回,否则返回指定的其它值
optional.orElse("fallback"); // "bam"
//ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"`
- Lambda
举例
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
lambda 方式
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
//进阶
Collections.sort(names, (String a, String b) -> b.compareTo(a));
- 函数式接口
SpringBoot 各种 starter 如何实现
- 首先是@EnableConfigurationProperties注解import了EnableConfigurationPropertiesImportSelector后置处理器;
- EnableConfigurationPropertiesImportSelector后置处理器又向Spring容器中注册了ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar这两个bean;
- 其中ConfigurationPropertiesBeanRegistrar向Spring容器中注册了XxxProperties类型的bean;ConfigurationPropertiesBindingPostProcessorRegistrar向Spring容器中注册了ConfigurationBeanFactoryMetadata和ConfigurationPropertiesBindingPostProcessor两个后置处理器;
- ConfigurationBeanFactoryMetadata后置处理器在初始化bean factory时将@Bean注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用;
- ConfigurationPropertiesBindingPostProcessor后置处理器将外部配置属性值绑定到XxxProperties类属性的逻辑委托给ConfigurationPropertiesBinder对象,然后ConfigurationPropertiesBinder对象又最终将属性绑定的逻辑委托给Binder对象来完成。
CMS收集器四个阶段,哪两个阶段工作线程会停止工作
-
初始标记
初识标记:这个过程是标记从gc root出发发的直接相关的引用。这个时间很短,但是是stop the world; -
并发标记
并发标记:用户线程并行执行,进行相关的引用标记。这个时间很长,一般决定于堆内存的大小。所使用的线程数为(cpu个数+3)/4,所以当cpu核数很少时,在并发标记阶段会出现严重的性能下降。为了解决这个问题,对于cpu核数很少时,在并发标记阶段会与用户线程交叉执行,以使服务器性能不至于下降的太严重。但是这样操作会使标记过程所耗费的时间更长。 -
重新标记
重新标记:因为在并发标记时,用户线程在执行,可能会造成再次的实例引用。所以需要重新标记一下。这个阶段的标记也是stop the world,并且是并行标记。 -
并发清除
并发清除,即清除相关的垃圾。
CMS使用的是标记清除算法,会造成内存碎片,当老年代无法再次分配内存时需要FULL GC。CMS提供了一个参数-XX+UseCMSCompactAtFullCollection,即在执行FULL GC时开启内存碎片的合并整理过程。这也会引起stop the world。
CMS在进行垃圾回收时,无法处理浮动垃圾。所以在进行垃圾回收时,需要留有一定的内存供用户线程使用。CMS提供了一个内存触发垃圾回收的内存使用比例:
-XX:CMSInitiatingOccupancyFraction,如果预留的内存不够使用,就会出现Cocurrent Mode Failure失败,这时就需要启动后备预案:临时使用串行收集器重新进行老年代的垃圾收回,这个时间更长。
CMS 用来进行老年代的垃圾回收,这个与ParNew(多线程的串行垃圾回收)进行组合,用于整个的堆内存的垃圾回收。
JDK 自带命令
- jps:查看Java进程
- jstack:查看线程堆栈命令
- jstat:性能监控工具命令
- jmap:打印内存映射
索引为什么用 B+ 树,不用 B 树
-
B+树只有叶节点存放数据,其余节点用来索引,而B-树是每个索引节点都会有Data域。这就决定了B+树更适合用来存储外部数据,也就是所谓的磁盘数据。
-
IO 次数
常用linux命令
- 查询端口号:netstat -anp |grep 端口号;lsof -i:端口号
- 查询进程:pstree -aup(树状);ps aux(查询当前运行线程状态)
- 终止进程:kill -9(强制);kill -15
限流
- Guava 用户限流
- Redis 限制重复操作
上一篇: Linux中进程间通信之管道