java后端知识点收集整理

一、集合类

  1. ArrayList扩容机制

    • 在没指定initialCapacity时,就是会使用延迟分配对象数组空间:当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10;之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,Arraylist继续扩容变为10*1.5=15;当添加第16个数据时,继续扩容变为15 * 1.5 =22个。
    • 指定initialCapacity时,就会直接分配对象数组空间;之后扩容会按照1.5倍增长
  2. StringBuffer 与 StringBuilder 区别

    • StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,

    • 只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。

    • 在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全。而StringBuffer则每次都需要判断锁,效率相对更低.

    • 初始化是:

      1.StringBuffer()的初始容量可以容纳16个字符,当该对象的实体存放的字符的长度大于16时,实体容量就自动增加。StringBuffer对象可以通过length()方法获取实体中存放的字符序列长度,通过capacity()方法来获取当前实体的实际容量。
      2.StringBuffer(int size)可以指定分配给该对象的实体的初始容量参数为参数size指定的字符个数。当该对象的实体存放的字符序列的长度大于size个字符时,实体的容量就自动的增加。以便存放所增加的字符。
      3.StringBuffer(String s)可以指定给对象的实体的初始容量为参数字符串s的长度额外再加16个字符。当该对象的实体存放的字符序列长度大于size个字符时,实体的容量自动的增加,以便存放所增加的字符。

    • 扩容机制

      使用append()方法在字符串后面追加东西的时候,如果长度超过了该字符串存储空间大小了就需要进行扩容:新容量扩为大小变成2倍+2,构建新的存储空间更大的字符串,将旧的复制过去;

  3. 说一说jdk1.8中,对hashMap的优化
    hashmap相关的问题

  4. 说一说jdk1.8中对concurrentHashMap的优化
    JDK1.8 对ConcurrentHashMap的优化
    彻头彻尾理解 ConcurrentHashMap

  5. Java里多个Map的性能比较
    Java里多个Map的性能比较(TreeMap、HashMap、ConcurrentSkipListMap)

  6. 为什么重写equals时必须重写hashCode方法?
    为什么重写equals时必须重写hashCode方法

    首先equals与hashcode间的关系是这样的:
    1、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;
    2、如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false)
    自我的理解:
    由于为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那没就不必在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的.
    一个很好的例子就是在集合中的使用;
    我们都知道java中的List集合是有序的,因此是可以重复的,而set集合是无序的,因此是不能重复的,那么怎么能保证不能被放入重复的元素呢,但靠equals方法一样比较的话,如果原来集合中以后又10000个元素了,那么放入10001个元素,难道要将前面的所有元素都进行比较,看看是否有重复,这个效率可想而知,因此hashcode就应遇而生了,java就采用了hash表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上,那么在后面定义进来的数据只要看对应的hashcode地址上是否有值,那么就用equals比较,如果没有则直接插入,只要就大大减少了equals的使用次数,执行效率就大大提高了。
    继续上面的话题,为什么必须要重写hashcode方法,其实简单的说就是为了保证同一个对象,保证在equals相同的情况下hashcode值必定相同。
    如果不重写equals方法,则equals方法默认使用“==”进行比较两个对象,即地址比较;
    如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的。

二、数据库

  1. 点击数据库系统知识整理

  2. mysql的索引,使用B+树索引的好处
    为什么mysql用B+树做索引而不用B-树或红黑树

  3. mysql性能查看以及如何优化
    性能分析方法、SQL性能优化和MySQL内部配置优化

  4. 说一说事务的ACID,事务的四大隔离机制
    Innodb中的事务隔离级别实现原理

  5. 聚集索引区别

    • 聚集索引
      数据行的物理顺序与聚集索引的逻辑顺序相同(即,表记录的排列顺序和与聚集索引的排列顺序相同),一个表中只能拥有一个聚集索引,所以查询效率快,只要找到第一个索引值记录,其余就连续性的记录在物理也一样连续存放。聚集索引对应的缺点就是修改慢,因为为了保证表中记录的物理和索引顺序一致,在记录插入的时候,会对数据页重新排序。
    • 非聚集索引
      索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。两种索引都采用B+树结构,非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针方式。非聚集索引层次多,添加和删除数据时不会造成数据重排。
      聚集索引与非聚集索引的总结

    SQL Sever索引类型有:唯一索引,主键索引,聚集索引,非聚集索引。
    MySQL 索引类型有:唯一索引,主键(聚集)索引,非聚集索引,全文索引。(主键就是聚集索引)

三、多线程

  1. 有多少种方法可以让线程阻塞,能说多少说多少

    如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。以下是详细的唤醒方法:
      1. sleep() 方法
      sleep(毫秒),指定以毫秒为单位的时间,使线程在该时间内进入线程阻塞状态,期间得不到cpu的时间片,等到时间过去了,线程重新进入可执行状态。(暂停线程,不会释放锁)
      2.suspend() 和 resume() 方法:。
      挂起和唤醒线程,suspend e()使线程进入阻塞状态,只有对应的resume e()被调用的时候,线程才会进入可执行状态。(不建议用,容易发生死锁)
      3. yield() 方法:
      会使的线程放弃当前分得的cpu时间片,但此时线程任然处于可执行状态,随时可以再次分得cpu时间片。yield()方法只能使同优先级的线程有执行的机会。调用 yield()的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。(暂停当前正在执行的线程,并执行其他线程,且让出的时间不可知)
      4.wait() 和 notify() 方法
      两个方法搭配使用,wait()使线程进入阻塞状态,调用notify()时,线程进入可执行状态。wait()内可加或不加参数,加参数时是以毫秒为单位,当到了指定时间或调用notify()方法时,进入可执行状态。(属于Object类,而不属于Thread类,wait()会先释放锁住的对象,然后再执行等待的动作。由于wait()所等待的对象必须先锁住,因此,它只能用在同步化程序段或者同步化方法内,否则,会抛出异常IllegalMonitorStateException.)
      5.join()方法
      也叫线程加入。是当前线程A调用另一个线程B的join()方法,当前线程转A入阻塞状态,直到线程B运行结束,线程A才由阻塞状态转为可执行状态。

  2. 锁是如何升级的
    锁升级,其实就是从偏向锁 < 轻量级锁(自旋锁) < 重量级锁
    https://blog.csdn.net/tongdanping/article/details/79647337
    https://blog.csdn.net/qq838642798/article/details/64439761

  3. 实现消费者生产者模型
    Java 实现生产者 – 消费者模型

  4. 线程间的通信方式
    https://blog.csdn.net/lovewebeye/article/details/79659312

  5. 多线程wait 和 notify的判断条件(if 和 while)和假死

  • 用while而不用if判断条件的原因

    经典的生产者和消费模式,使用wait和notify实现,判断条件为什么要用while而不能使用if呢?
    因为当线程wait之后,又被唤醒的时候,是从wait后面开始执行,而不是又从头开始执行的,所以如果用if的话,被唤醒之后就不会在判断if中的条件,而是继续往下执行了。举个例子,如果list只是添加了一个数据,而存在两个消费者被唤醒的话,就会出现溢出的问题了,因为不会在判断size是否==0,就直接执行remove了。但是如果使用while的话,从wait下面继续执行,还会返回执行while的条件判断,size>0了才会执行remove操作,所以这个必须使用while,而不能使用if来作为判断。
    基于以上认知,下面这个是使用wait和notify函数的规范代码模板:

synchronized (sharedObject) {   
    while (condition) {   
    	sharedObject.wait();   
        // (Releases lock, and reacquires on wakeup)   
    }   
    // do action based upon condition e.g. take or put into queue   
    sharedObject.notifyAll();
} 
  • wait 和 notify的假死问题

    多线程消费或者多线程生产时候,当调到notify()方法的时候,可能只叫醒同类的线性,即,生产者叫醒了另外一个生产者(或消费者叫醒了另外一个消费者),然后因为条件没有发生变化(如:本来一个生产者发现缓冲队列满了,调用wait()方法堵塞自己,然后想要notify()唤醒消费者消费是的队列减少,然后消费者再次notify()消费者进行生产;但生产者调用notify()时唤醒了另外一个生产者2,而不是消费者,这个生产者2被唤醒后判断while条件,发现队列还是满的,又调用wait()堵塞自己,那么现在所有生产者和消费者都被堵塞了。。。),所以又进入到wait中,造成系统假死状态。解决办法把notify()方法改成 notifyAll()

  1. 线程池的submit和execute的区别
    线程池的submit和execute的区别

  2. 线程池的实现原理,拒绝策略的时机
    线程池的实现原理,拒绝策略的时机

四、java虚拟机JVM

  1. Tomcat 类加载器之为何违背双亲委派模型

  2. 什么时候触发MinorGC?什么时候触发FullGC?

    触发MinorGC(Young GC)

    对象在新生代EDen区中分配时,当Eden去没有足够空间进行分配时,虚拟机将发起一次Minor GC。

    触发FullGC

    • 老年代空间不足
      如果创建一个大对象,Eden区域当中放不下这个大对象,会直接保存在老年代当中,如果老年代空间也不足,就会触发Full GC。为了避免这种情况,最好就是不要创建太大的对象。
    • 持久代空间不足
      如果有持久代空间的话,系统当中需要加载的类,调用的方法很多,同时持久代当中没有足够的空间,就触发一次Full GC。
    • 年轻代出现空间分配担保失败
      空间分配担保失败发生在Minor GC, 如果Survivor区当中存活对象的年龄达到了设定值,会就将Survivor区当中的对象拷贝到老年代,如果老年代的空间不足,判断是否开启HandlerPromotionFailure(是否允许担保失败),没有开启直接FullGC;如果开启了HanlerPromotionFailure, JVM会判断老年代的最大可用的连续内存空间是否大于历次晋升到老年代对象的平均大小,如果小于,则直接执行FullGC 。
    • 对象大小大于To Survivor 可用内存,并且大于年老代的空闲空间
      由Eden区、From Survivor 区向To Survivor 区复制时,对象大小大于To Survivor 可用内存,则把该对象转存到年老代,且年老代的可用内存小于该对象大小,则触发一次Full GC。
    • 显示调用System.gc
  3. GCroot可以为哪些?

    a. java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
    b.方法区中的类静态属性引用的对象。
    c.方法区中的常量引用的对象。
    d.本地方法栈中JNI本地方法的引用对象。

五、redis缓存

  1. redis是怎么做缓存的

  2. redis缓存穿透和雪崩效应
    Redis面试三大知识点:缓存雪崩、缓存穿透、缓存更新

  3. redis的持久化操作
    RDB与AOF

  4. 如何利用redis处理热点数据

  5. 分布式锁怎么实现
    分布式锁的三种实现的对比
    分布式锁的实现

  6. 键的淘汰策略,过期键的清除方式

    • 内存淘汰机制:

      1. volatile-lru:设置了过期时间的键空间中,优先移除最近未使用的key。
      2. volatile-random: 在设置了过期时间的键空间中,随机移除某个key。
      3. volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除。
      4. allkeys-lru:在所有键空间中,优先移除最近未使用的key。
      5. allkeys-random: 在所有键空间中,随机移除某个key。
      6. noeviction:当达到内存限额后,所有引起申请内存的命令会返回错误。
    • 过期键的清除方式

      1. 定期删除
      2. 惰性删除

六、Spring、Hibernate、Mybatis

  1. Java进阶面试精选系列:Spring+Hibernate+Mybatis+设计模式

  2. 循环依赖问题
    Spring-bean的循环依赖(单例模式)以及解决方式
    浅谈Spring解决循环依赖的三种方式

Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法–只有getbean才会在容器中生成一个
bean)时都会创建一个新的bean实例。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。

  1. spring使用的设计模式
    Spring中涉及的设计模式总结

  2. bean的生命周期

  3. SpringMVC执行流程

    • 客户端发起一个http请求,web应用服务器接收到这个请求,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),web容器将该请求转交给DispatcherServlet处理。
    • DispatcherServlet接收到这个请求后,根据URL和HandlerMapping的配置找到处理请求的处理器(Handler)。
    • DispatcherServlet根据HandlerMapping得到处理当前请求的Handler后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler。(HandlerAdapter是一个适配器,它以统一的接口对各种Handler方法进行调用)
    • 处理器完成业务逻辑的处理后将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和逻辑数据信息。
    • DispatcherServlet通过视图解析器ViewResolver对ModelAndView进行视图解析,得到真实的视图对象View。
    • DispatcherServlet对View进行视图渲染,然后将视图response到客户端。
    • 客户端得到服务器端响应的视图。
      在这里插入图片描述
  4. 浅谈Session与Cookie的区别与联系
    https://blog.csdn.net/bwh0520/article/details/78808181

七、Hibernate

23.为什么要使用 hibernate?

24.什么是 ORM 框架?

25.hibernate 中如何在控制台查看打印的 sql 语句?

26.hibernate 有几种查询方式?

27.hibernate 实体类可以被定义为 final 吗?

28.在 hibernate 中使用 Integer 和 int 做映射有什么区别?

29.hibernate 是如何工作的?

30.get()和 load()的区别?
Hibernate get和load区别

31.说一下 hibernate 的缓存机制?

32.hibernate 对象有哪些状态?

33.在 hibernate 中 getCurrentSession 和 openSession 的区别是什么?

34.hibernate 实体类必须要有无参构造函数吗?为什么?

八、消息队列

  1. 消息中间件部署及比较
    消息中间件部署及比较:rabbitMQ、activeMQ、zeroMQ、rocketMQ、Kafka、redis
    RabbitMQ和kafka从几个角度简单的对比–转

  2. 消息队列常见问题
    ++ 消息队列保证不重复消费
    ++ 如何保证消息的可靠性传输?或者说,如何处理消息丢失的问题?
    ++ 如何保证消息的顺序性

    RabbitMQ
    拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。

    ++ 如何保证消息队列的高可用

九、计算机网络

https://github.com/huihut/interview

  1. TCP三次连接、四次释放
    在这里插入图片描述
  • 为什么要三次握手
  • 为什么要四次释放
  • 为什么要传回 SYN

    接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
    SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。
    传了 SYN,为啥还要传 ACK
    双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方到接收方的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。

  1. 网络编程nio和netty相关,netty的线程模型,零拷贝实现
    大话 Select、Poll、Epoll
    Linux IO模式及 select、poll、epoll详解
    epoll比select和poll高效的原因

  2. tcp与http协议的联系与区别
    HTTP、TCP和Socket的概念和原理及其区别

  3. tcp的可靠是指什么
    tcp的可靠是指什么

  4. 在TCP的三次握手中,后采用随机产生的初始化序列号进行请求。

    这样做主要是出于网络安全的因素着想。
    如果不是随机产生初始序列号,黑客将会以很容易的方式获取到你与其他主机之间通信的初始化序列号,并且伪造序列号进行攻击,这已经成为一种很常见的网络攻击手段。

十、操作系统

  1. 操作系统的用户态和核心态切换条件以及为什么要切换
    操作系统的用户态和核心态切换条件:中断
    https://blog.csdn.net/u012333003/article/details/29384807

  2. 操作系统的虚拟内存

  3. 并发容器

十一、算法编程

  1. 二分查找变种
    你真的会写二分查找吗

  2. 在纸上写一个一个链表排序

/**
 * Created by 凌 on 2019/1/18.
 * 注释:148. Sort List
 */
public class SortList {
    /**
     * 将两个有序链表合并
     * 链表的二路归并排序
     * @param head
     * @return
     */
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null){
            return head;
        }
        ListNode slow = head;
        ListNode fast = head.next;

        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }

        ListNode mid = slow.next;
        //将链表切断
        slow.next = null;
        head = sortList(head);//第一条链表head - slow
        mid = sortList(mid);//第二条链表mid(slow-next) - end
        return mergeSort(head,mid);
    }
    public ListNode mergeSort(ListNode first,ListNode anotherFirst){
        ListNode newHead = new ListNode(-1);
        ListNode curr = newHead;
        while (first != null || anotherFirst != null){
            if (first == null || (anotherFirst != null && first.val >= anotherFirst.val)){
                curr.next = anotherFirst;
                curr = curr.next;
                anotherFirst = anotherFirst.next;
            }else if(anotherFirst == null || (first != null && anotherFirst.val >= first.val)){
                curr.next = first;
                curr = curr.next;
                first = first.next;
            }
        }
        return newHead.next;
    }
}
  1. 在纸上写一个Binary Search Tree的建立函数
package com.practice.binarytree;

/**
 * Created by 凌 on 2019/3/19.
 * 描述:构建二叉查找树
 */
public class BinarySearchTree {
    class TreeNode {
        int data;
        TreeNode left;//左节点
        TreeNode right;//右节点

        public TreeNode() {
        }

        public TreeNode(int data) {
            this.data = data;
            this.left = null;
            this.right = null;
        }
    }

    /**
     * 构建二叉查找树
     * @param root
     * @param data
     */
    TreeNode insert(TreeNode root, int data){
        if (root == null){
            root = new TreeNode(data);
            return root;
        }
        if (root.data < data){
            root.right = insert(root.right, data);
        }else {
            root.left = insert(root.left, data);
        }
        return root;
    }

    /**
     * 中序遍历,如果是二叉查找树,那么遍历完可以得到有序的序列
     * @param root
     */
    void inOrderTranversal(TreeNode root){
        if (root == null){
            return;
        }
        inOrderTranversal(root.left);
        System.out.printf(root.data + "\t");
        inOrderTranversal(root.right);
    }

    /**
     * 二叉树查找
     * @param root
     * @param data
     * @return
     */
    boolean search(TreeNode root, int data){
        if (root == null){
            return false;
        }
        if (root.data == data){
            return true;
        }else if (root.data < data){
            return search(root.right, data);
        }else{
            return search(root.left, data);
        }
    }

    public static void main(String[] args) {
        int[] nums = {3, 6, 1, 8, 5, 2};
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        TreeNode root = null;
        for (int i = 0; i < nums.length; i++) {
            root = binarySearchTree.insert(root, nums[i]);
        }
        System.out.println("中序遍历二叉排序树:");
        binarySearchTree.inOrderTranversal(root);
        System.out.println();

        int data = 5;
        System.out.printf("查找是否存在值为%d的节点:\n",data);
        boolean isExist = binarySearchTree.search(root, data);
        System.out.println(isExist);
    }
}

  1. 二叉树中两个节点的最近公共祖先节点
    二叉树中两个节点的最近公共祖先节点
发布了151 篇原创文章 · 获赞 104 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/qq_35923749/article/details/88675310