各大互联网厂面试题(付详细答案,持续更新中...)

文章目录

携程

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大类线程池;

  1. newCachedThreadPool()
    缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存;用的是SynchronousQueue队列;队列长度为 Integer.MAX_VALUE;
  2. newFixedThreadPool(int nThreads)
    创建一个数量固定的线程池;用的是LinkedBlockingQueue队列;队列长度为 Integer.MAX_VALUE;固定数量的线程池,并且corePoolSize=maximumPoolSize
  3. newSingleThreadExecutor()
    创建一个单线程线程池。用的是LinkedBlockingQueue队列;队列长度为 Integer.MAX_VALUE;
  4. newScheduledThreadPool(int corePoolSize)
    创建一个数量固定的线程池,支持执行定时性或周期性任务;用的是DelayedWorkQueue队列;
  5. newSingleThreadScheduledExecutor()
    单线程的 newScheduledThreadPool,延迟或定时执行
  6. 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协议的安全版.

  1. HTTPS的特点 ?
    • 数据隐私性:内容经过对称加密,每个连接生成一个唯一的加密密钥
    • 数据完整性:内容传输经过完整性校验
    • 身份认证:第三方无法伪造服务端(客户端)身份
  2. HTTPS并非是应用层的一种新协议。只是HTTP通信接口部分用SSL和TLS协议代替而已。通常,HTTP直接和TCP通信。当使用SSL时,则 演变成先和SSL通信,再由SSL和TCP通信了。简言之,所谓HTTPS,其实就是身披SSL协议这层外壳的HTTP。

在这里插入图片描述

  1. 在采用SSL后,HTTP就拥有了HTTPS的加密、证书和完整性保护这些功能。也就是说HTTP加上加密处理和认证以及完整性保护后即是HTTPS。

在这里插入图片描述

  1. HTTPS 协议的主要功能基本都依赖于 TLS/SSL 协议,TLS/SSL 的功能实现主要依赖于三类基本算法:散列函数 、对称加密和非对称加密,其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性
    在这里插入图片描述

作业帮

一面:
1.自我介绍
2.项目
3.手撕代码三道题,写链表反转–》链表成环
  1. 链表翻转
 /**
     * 遍历方式
     *
     * @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 + '\'' +
                    '}';
        }
    }
  1. 链表成环(检测是否有环)
    /**
     * 遍历:出现两个相同节点则证明出现环
     *
     * @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是一个接口。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术砖家--Felix

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值