文章目录
-
-
- 携程
- 映客
- 作业帮
-
- 一面:
- 1.自我介绍
- 2.项目
- 3.手撕代码三道题,写链表反转--》链表成环
- 4.问我LinkedList知道不?你说一下他的底层,list和set、map区别是什么,他们分别使用在什么场合?
- 5.map有哪几种?
- 6.然后开始问红黑树的优势,还有特点,说了旋转与着色,问我旋转与着色的实现过程
- 7.Hashmap的线程问题,如何解决,线程安全的Map,hashmap中为什么要用异或运算符?
- 8.场景题,Hashmap插入一万个元素之后会不会扩容,扩容扩多少?
- 9.JDK1.7和1.8中hashmap的区别?在1.7和1.8扩容有什么区别,1.8是先插入后扩容,又问我为啥要先插入呢,先扩容不是更好?
- 11.ConrrentHashMap 如何保证线程安全?
- 12.你知道Arraylist的接口有哪几种?
- 13,什么是泛型?编译器如何处理泛型,类型擦除以及类型擦除的过程,List<?>和原始类型List之间的区别
- 14.异常的场景题,try catch带return返回顺序,出了四个场景
- 二面:
- 1.自我介绍
- 2.项目
- 3.手撕代码---0-1背包问题
- 4.问我动态规划你了解多少?
- 5.项目问题
- 6.事务中的回滚原理是什么?
- 7.那你回到undolog呢?说一下
- 8.JDBC连接数据库过程,那你知道prestatement与statement的区别呢?
- 9.try catch finally的机制问题
- 10.final、finally、finalize的区别
- 11.Object类有哪些方法,你知道为什么Object里有wait等线程方法呢?
- 美团一面
-
- 1自我介绍
- 2项目
- 3 mvcc多版本并发控制
- 4,b+树
- 5 ,两个线程获取内存时用到什么算法
- 6 对象创建的过程回收过程
- 7, GC问的很深,为什么stw?
- 8, synchronized lock区别哪个锁开销大?
- 9 redis数据结构
- 10 redis缓存穿透
- 11 redis多路复用
- 12 redis缓存过期淘汰策略
- 13 redis缓存删除策略
- 14 JUC了解吗?
- 15 FORK/JOIN
- 16 mysql分页原理
- 17 volatile基本原理
- 18 怎么快速的找到GCroot(我答了可达性分析面试官说有个算法)
- 19 mybatis分页怎么做的?
- 20 说下springboot?
- 21 springboot开发部署测试环境怎么进行隔离?
- 22 #和$?
- 23 mybatis中like 怎么写?
- 24 MyBatis字符串拼接?
- 25 BIO NIO ?
- 26 IO多路复用?
- 27 is-a has-a
- 28 还问了很多我没听过的概念
- 29 手写代码:多线程交替打印数组
- 美团二面
-
- 1自我介绍
- 2怼项目(项目真的菜)
- 3 springboot自动扫描原理
- 4 spring底层知道哪些?
- 5 beanfactory和factorybean
- 6 currenthashmap扩容机制
- 7 future
- 8 类加载过程和机制
- 9类加载的过程中jvm内存模型做了哪些工作双亲委派模型
- 10垃圾收集算法
- 11垃圾收集器cms
- 12老年代元空间永久代之间的区别和联系
- 13 jvm调优做过吗
- 14索引讲一讲
- 15 b+树底层
- 16主键索引和辅助索引的区别和联系
- 17内存频道
- 18 happen before
- 19拒绝策略
- 20 notify和notifyall
- 21 synchronized
- 22 voiltle原理
- 23可重入锁原理
- 24线程池了解吗说一说
- 25用的什么容器Tomcat
- 26 BIO NIO(又被问到了。。。)
- 27 redis熟悉哪些数据结构
- 28 MQ怎么保证消息收到了
- 29 bean的初始化过程
- 30你还了解哪些技术栈
- 31算法:最长公共子串
- 32手写快排
- 33还问了一些细节忘记了
-
携程
1. 上来就手撕单例模式,线程安全的懒汉模式的实现,然后问怎么优化,双重锁校验,如果要实现变量可见性?**
双检锁/双重校验锁(DCL,即 double-checked locking),线程安全,懒加载,并且保持高性能
public class Singleton {
private static Singleton singleton;
private Singleton (){
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
1,给 instance 分配内存;
2,调用 Singleton 的构造函数来初始化成员变量
3,将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错
所以要实现变量可见,并且要避免上述问题:需要给Singleton增加 volatile关键字;
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。
2. 线程池相关核心线程满了线程放哪里?阻塞队列满了放哪里?基本线程和阻塞队列都满了放哪里?
核心参数:
- int corePoolSize, // 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。 int
- maximumPoolSize, // 线程数的上限
- long keepAliveTime, TimeUnit unit, //超过corePoolSize的线程的idle时长,超过这个时间,多余的线程会被回收。
- workQueue, // 任务的排队队列
- ThreadFactory threadFactory, // 新线程的产生方式
- RejectedExecutionHandler handler) // 拒绝策略
执行顺序:corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略
核心线程池满了会将线程放入阻塞队列,如果队列满了,则会创建新的线程,当线程池线程大于最大线程后再进入的线程会执行拒绝策略;
3. 拒绝策略有哪些?
如图:实现了四种拒绝策略
AbortPolicy: 抛出RejectedExecutionException
DiscardPolicy: 什么也不做,直接忽略
DiscardOldestPolicy: 丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置
CallerRunsPolicy :直接由提交任务者执行这个任务
4. 几种线程池?哪些参数不一样?
一共分为12种构造方式6大类线程池;
- newCachedThreadPool()
缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存;用的是SynchronousQueue队列;队列长度为 Integer.MAX_VALUE; - newFixedThreadPool(int nThreads)
创建一个数量固定的线程池;用的是LinkedBlockingQueue队列;队列长度为 Integer.MAX_VALUE;固定数量的线程池,并且corePoolSize=maximumPoolSize - newSingleThreadExecutor()
创建一个单线程线程池。用的是LinkedBlockingQueue队列;队列长度为 Integer.MAX_VALUE; - newScheduledThreadPool(int corePoolSize)
创建一个数量固定的线程池,支持执行定时性或周期性任务;用的是DelayedWorkQueue队列; - newSingleThreadScheduledExecutor()
单线程的 newScheduledThreadPool,延迟或定时执行 - newWorkStealingPool()
Java 8 新增创建线程池的方法,使用ForJoinPool创建,ForkJoinPool适合cpu密集型任务newWorkStealingPool适合使用在很耗时的操作
5. 线程池的阻塞队列为什么不用ArrayList?
- 阻塞队列在于阻塞功能,阻塞队列拥有阻塞的取和放方法,运用到线程池中,当任务队列满后,队列将阻塞,而且队列不进行放入和扩容,当队列有空间后,线程将自动放入队列,不需要人工放入,同时从队列中取任务,如果队列中没有任务将阻塞,如果有任务村子将自动取出,也不需要人工取。
6. 手撕算法第一个动态规划?
- 此题考点为动态规划,题目不明!
映客
1.手写代码
2.讲下快排思想,归并思想,为什么复杂度是O(NlogN)
快排:时间复杂度是O(N*logN)分治法,该方法的基本思想是:
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
快速排序代码实现:
public class Sort快速 {
public static void main(String[] args) {
int[] a = {
2, 1, 5, 8, 3, 9, 4, 7, 8, 6, 5, 3};
sort(a, 0, a.length - 1);
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]+",");
}
System.out.println();
}
public static void sort(int[] a, int f, int e) {
if (f < e) {
int mid = div(a, f, e);
sort(a, f, mid);
sort(a, mid + 1, e);
}
}
public static int div(int[] a, int f, int e) {
int i = f, j = e;
int x = a[f];
while (i < j) {
while (i < j && a[j] >= x) {
j--;
}
if (i < j) {
a[i] = a[j];
i++;
}
while (i < j && a[i] < x) {
i++;
}
if (i < j) {
a[j] = a[i];
j--;
}
}
a[i] = x;
return i;
}
}
快速排序是一种交换类的排序,它同样是分治法的经典体现。在一趟排序中将待排序的序列分割成两组,其中一部分记录的关键字均小于另一部分。然后分别对这两组继续进行排序,以使整个序列有序。在分割的过程中,枢纽值的选择至关重要,快速排序平均时间复杂度也为O(nlogn)级,最差时间复杂度为O(n²)。
归并思想:分治思想
归并排序的实现思想:
归并排序代码实现:
public class Sort归并 {
public static void main(String[] args) {
int[] a = {
2, 1, 5, 8, 3, 9, 4, 7, 8, 6, 5, 3};
int[] temp = new int[a.length];
;
sort(a, 0, a.length - 1, temp);
for (int each : a) {
System.out.print(each + " ");
}
}
public static void sort(int[] arr, int start, int end, int[] temp) {
if (start < end) {
int mid = (start + end) / 2;
sort(arr, start, mid, temp);
sort(arr, mid + 1, end, temp);
merge(arr, start, mid, end, temp);
}
}
public static void merge(int[] a, int start, int mid, int end, int[] temp) {
int i = start, j = mid + 1;
int k = 0;
while (i <= mid && j <= end) {
if (a[i] <= a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
while (i <= mid) {
temp[k++] = a[i++];
}
while (j <= end) {
temp[k++] = a[j++];
}
k = 0;
//将temp中的元素全部拷贝到原数组中
while (start <= end) {
a[start++] = temp[k++];
}
}
}
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)
3.HTTPS原理?
HTTPS是在HTTP上建立SSL加密层,并对传输数据进行加密,是HTTP协议的安全版.
- HTTPS的特点 ?
- 数据隐私性:内容经过对称加密,每个连接生成一个唯一的加密密钥
- 数据完整性:内容传输经过完整性校验
- 身份认证:第三方无法伪造服务端(客户端)身份
- HTTPS并非是应用层的一种新协议。只是HTTP通信接口部分用SSL和TLS协议代替而已。通常,HTTP直接和TCP通信。当使用SSL时,则 演变成先和SSL通信,再由SSL和TCP通信了。简言之,所谓HTTPS,其实就是身披SSL协议这层外壳的HTTP。
- 在采用SSL后,HTTP就拥有了HTTPS的加密、证书和完整性保护这些功能。也就是说HTTP加上加密处理和认证以及完整性保护后即是HTTPS。
- HTTPS 协议的主要功能基本都依赖于 TLS/SSL 协议,TLS/SSL 的功能实现主要依赖于三类基本算法:散列函数 、对称加密和非对称加密,其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性。
作业帮
一面:
1.自我介绍
2.项目
3.手撕代码三道题,写链表反转–》链表成环
- 链表翻转
/**
* 遍历方式
*
* @param p
* @return
*/
public static Node flip(Node p) {
Node pre = null;
Node next = null;
while (p != null) {
// next =2->3->4
next = p.next;
// p=1
p.next = pre;
//pre=1
pre = p;
//p =2->3->4
p = next;
}
return pre;
}
/**
* 递归方式
*
* @param head
* @return
*/
public static Node reverse(Node head) {
if (head == null || head.next == null) {
return head;
}
Node temp = head.next;
Node reverse = reverse(temp);
temp.next = head;
head.next = null;
return reverse;
}
@Data
static class Node {
private Node next;
private String data;
@Override
public String toString() {
return "Node{" +
"next=" + next +
", data='" + data + '\'' +
'}';
}
}
- 链表成环(检测是否有环)
/**
* 遍历:出现两个相同节点则证明出现环
*
* @param head 头
*/
private static void method1(Node head) {
Set<String> data = new HashSet<>();
while (head.next != null) {
data.add(head.data);
head = head.next;
if (data.contains(head.data)) {
System.out.println("出现环!");
break;
}
}
}
/**
* 两个指针判断
* 例如链表A->B->C->D->B->C->D,两个指针最初都指向节点A,进入第一轮循环,
* 指针1移动到了节点B,指针2移动到了C。第二轮循环,指针1移动到了节点C,指
* 针2移动到了节点B。第三轮循环,指针1移动到了节点D,指针2移动到了节点D,
* 此时两指针指向同一节点,判断出链表有环
*
* @param head 头
*/
private static void method2(Node head) {
Node p1, p2;
p1 = head.next;
p2 = head.next.next;
while (!p1.data.equals(p2.data)) {
if (p2 == null || p2.next == null) {
System.out.println("无环,结束!");
return;
}
p1 = p1.next;
p2 = p2.next.next;
}
System.out.println("有环!");
}
@Data
static class Node {
private Node next;
private String data;
}
4.问我LinkedList知道不?你说一下他的底层,list和set、map区别是什么,他们分别使用在什么场合?
底层: linkedList的底层结构实现是利用对象Node,包含pre(上一个节点),next(下一个节点)和item(存储数据内容)实现,是一个双向链表;
Linkedlist的删除和插入要比ArrayList快,只需要移动指针即可。
ArrayList和LinkedList底层实现的区别
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
list:
- 1.可以允许重复的对象。
- 2.可以插入多个null元素。
- 3.是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
- 4.常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
set:
- 1.不允许重复对象
- 2.无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
- 3.只允许一个 null 元素
- 4.Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。而且可以重复;
map:
- 1.Map不是collection的子接口或者实现类。Map是一个接口。