Java面试笔记

基础篇

  1. final,finally,finalize之前的区别
    1. final关键字,代表着不可变,可以保证在过程中不被修改。final修饰的数据,只能读取,不能修改。final修饰的方法表示任何继承类都无法重写。final修饰的类,表示无派生子类,无法被继承。
    2. finally表示始终被执行的意思,和try,catch一起使用,无论是否发生异常finally内的的语句都会执行。
    3. finalize是Object的方法,但JVM 进行 GC时,如果对象没有被调用,需要清除,就会执行此方法类,做生前最后的事情(最终遗言),且仅被调用一次
  2. 重载(Overload)和重写(Override)的区别
    1. 多个同名的函数存在,有不同类型的参数和不同个数的的参数。(返回类型、访问权限大小和异常不同不算为重载)。
    2. 又叫覆盖,是子类继承父类的方法,并重新定义。
  3. 简单介绍一下反射机制,反射的用途和实现
    1. 反射在Java中有运行和编译两种状态。指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象。

    2. 获取class对象的方法来使用反射

    3. 通过对象Object的getClass()方法、通过类的静态class属性、通过Class类的静态方法forName(String className)

      img

  4. 反射获取私有方法
    1. method.setAccessible(true);//获取私有权限
      //可以绕过私有的安全检查,所以我们就可以调用类的私有方法啦。
      
  5. 序列化和反序列化的理解
    1. 序列化是将java对象转换成字节序列的过程,反序列化也即是相反的过程
    2. 作用:
      1. 持久化存储,(持久化存储到本地磁盘)
      2. 远程调用(序列化成流 远程传输)
  6. 抽象类和接口的区别
    1. 抽象类是被abstract修饰的类,抽象类中可以拥有任意范围的成员数据,可以定义非抽象方法。
    2. 接口时interface,接口中只能拥有静态的不可修改的数据且所有的方法都是抽象的
  7. equals和==的区别
    1. ==可以比较基本数据类型,比较两者在内存中的值,当 == 比较的复合类型的时候是比较的堆内存地址。
    2. equals比较复合数据也是比较的堆内存地址和==的功能是一样的,底层equals也是 ==,但是String重写了equals()方法,比较的是内容是否相同。
  8. 各类内部类的区别
    1. 成员内部类:普通内部类,与外部类存在联系,需要依赖外部类方能实例化
    2. 静态内部类:与外部对象不存在联系。可直接实例化
    3. 匿名内部类:无名,继承或实现那个类
    4. 局部内部类:方法体内或作用域内可以使用
  9. 作用域 公开的public,受保护的protected默认friendly,私有的private的区别
    image-20200601215905553
  10. Object常用的方法有哪些?

​ equals()、toString()、finalize()、getClass()、hashCode()、clone()、wait()、notify()、notifyAll()

  1. wait和sleep的区别
    1. sleep是线程中的方法,但是wait是Object中的方法。
    2. sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
    3. sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
    4. sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
  2. notify()、notifyAll()是什么,有什么区别
    1. 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。

    2. notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁。

    3. notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

    区别

    notify()是只唤醒一个等待的线程,如果存在多个线程的情况,由操作系统对多线程管理来决定。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。在多线程中如果根据条件决定是否执行,使用while不用if。因为while 具有判断条件成立后在执行。

  3. String和StringBuffer、StringBuilder的区别
    1. String是一个Java的基础数据类型,在内存堆上被创建。因为被final修饰,String 一旦被创建后出来,值不能改变,String的所有方法都不能改变本身的值,而是返回一个新的String对象。String是使用字符数组保存字符串
    2. 共同的父类。StringBuffer是和StringBuilder的父类是AbstracStringBuilder,这两种对象都是可变的。
    3. 线程安全性。String不可变,可以理解为常量,所以是线程安全的,StringBuffer引入了同步锁或者对调用的方法使用了线程锁,所以也是线程安全的。
    4. StringBuilder没有线程锁,是线程不安全的。
  4. 可变与不可变化性

    不可改变的都是一定是线程安全的,因为只有一种状态,被final修饰,对象创建后不可修改。

  5. hashCode和equals方法的关系
    1. 对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
    2. 如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
    3. 如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
    4. 如果两个对象的hashcode值相等,则equals方法得到的结果未知。
  6. HTTP 请求的 GET 与 POST 方式的区别

    image-20200603221720943

  7. Session 与 Cookie 区别
    1. cookie数据存放在客户的浏览器上,session数据放在服务器上。

    2. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。

    3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。

    4. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

      所以个人建议:将登陆信息等重要信息存放为session。其他信息如果需要保留,可以放在cookie中

集合篇

  1. List,Set,Map之间的区别
    1. List,Set,Map都是接口,但是List,Set是继承Collection接口,Map为单独的接口

    2. Set下有HashSet,LinkedHashSet,TreeSet

    3. List下有ArrayList,Vector,LinkedList

    4. Map下有Hashtable,LinkedHashMap,HashMap,TreeMap

    5. Collection接口下还有个Queue接口,有PriorityQueue类

      img

    6. 注意:
      Queue接口与List、Set同一级别,都是继承了Collection接口。
      看图你会发现,LinkedList既可以实现Queue接口,也可以实现List接口.只不过呢, LinkedList实现了Queue接口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。

      SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。

  2. 集合类分析总结
    1. — List 有序,可重复

      ArrayList
      优点: 底层数据结构是数组,查询快,增删慢。
      缺点: 线程不安全,效率高
      LinkedList
      优点: 底层数据结构是链表,查询慢,增删快。
      缺点: 线程不安全,效率高

      Vector
      优点: 底层数据结构是数组,查询快,增删慢。
      缺点: 线程安全,效率低

    2. Set 无序,唯一

      HashSet
      底层数据结构是哈希表。(无序,唯一)
      如何来保证元素唯一性?
      1.依赖两个方法:hashCode()和equals()

      LinkedHashSet
      底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
      1.由链表保证元素有序
      2.由哈希表保证元素唯一

      TreeSet
      底层数据结构是红黑树。(唯一,有序)

      1. 如何保证元素排序的呢?
        自然排序
        比较器排序
        2.如何保证元素唯一性的呢?
        根据比较的返回值是否是0来决定
  3. 针对Collection集合我们到底使用谁呢?(掌握)
    1. 如何选择使用那个集合框架

      if(唯一?){
          使用Set
          if(排序?){
              TreeSet或LinkedHashSet
          }else{
              HashSet(默认)
          }    
      }else{
          List
          if(线程安全吗?){
              Vector
          }else{
              ArrayList:查询速度快,增删慢
              LinkedList: 查询速度慢,增删快    
          }
      }
      
  4. Set存储是唯一,底层如何去重
    1. HashSet:采用Hash是通过调用元素内部的hashCode和equals方法实现去重,首先调用hashCode方法,比较两个元素的哈希值,如果哈希值不同,直接认为是两个对象,停止比较。如果哈希值相同,再去调用equals方法,返回true,认为是一个对象。返回false,认为是两对象
    2. **TreeSet:**如果compareTo返回0,说明是重复的,返回的是自己的某个属性和另一个对象的某个属性的差值,如果是负数,则往前面排,如果是正数,往后面排;
  5. Map的结构
    1. 这里写图片描述
    2. Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。
    3. TreeMap是有序的,HashMap和HashTable是无序的。
    4. Hashtable的方法是同步的,也是线程安全,HashMap的方法不是同步的,也是线程不安全。这是两者最主要的区别。
  6. HashTable和HashMap的区别
    1. **线程安全**,HashTable 是同步的也是线程安全的,而HashMap是不同步的,也是线程不安全。
    2. **效率**。HashTable的执行效率要比HashMap低一些,但是如果对同步性和线程问题没要求的情况下推荐使用HashMap
    3. **Null值**。在HashTable 中是不允许使用Null值,但是在Hash Map中Key和Value 都可以为Null。
    4. **父类不同**。HashTable 的父类是Dictionary而HashMap的父类是AbstractMap。
    
  7. HashSet 和 HashMap 区别
    1. HashMap实现了Maps HashSet继承的是Collection接口
    2. HashMap存储kay-value,HashSet只存储对象
    3. 两者都使用hashCode来计算kay和对象,equals方法判断对象相同 也相同
  8. ConcurrentHashMap 的工作原理
    1. ConcurrentHashMap 的加锁粒度要比HashTable更细一点。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。(分段锁)

    2. Segment继承了ReentrantLock,所以它就是一种可重入锁(ReentrantLock)。在ConcurrentHashMap,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对于不同Segment的数据进行操作是不用考虑锁竞争的。(就按默认的ConcurrentLeve为16来讲,理论上就允许16个线程并发执行)

    3. 具体解释和总结,很棒!

      https://www.baidu.com/link?url=f3UWlcJcGkMogsmnsGc_Hrgbnh1Qfkdl8ojdzZM73lZIpjvOAXK0n8fCRHY-nKAWyhbe0wrA0TdJA1qEkmBgaa&wd=&eqid=bfc65ebf00036d59000000065b9a2f25

  9. ConcurrentHashMap与HashMap的区别
    1. HashMap和concurrentHashMap都是基于散列,数组和链表
    2. 最大的区别就是线程安全问题,ConcurrentHashMap有分段锁的机制。当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
  10. CopyOnWriteArrayList和CopyOnWriteArraySet的工作原理
  CopyOnWriteArraySet是基于CopyOnWriteArrayList实现的,只有add的方法稍微有些不同,因为CopyOnWriteArraySet是Set也就是不能有重复的元素,故在CopyOnWriteArraySet中用了addIfAbsent(e)这样的方法。

    写入时复制一个数组,对复制后产生的新数组进行操作,而旧的数组不会有影响,所以旧的数组可以依旧就行读取(可以看出来,读的时候如果有新的数据正在写是无法实时的读取到的,有延时,得等新数据写完以后,然后才可以读到新的数据)

    多个线程同时去写,多线程写的时候会Copy出N个副本出来,那么可能内存花销很大,所以用一个重入显式锁ReetrantLock锁住,一次只能一个线程去添加。
  读取时,不用进行线程同步。

  可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块
  java中常用的可重入锁
  synchronized
  java.util.concurrent.locks.ReentrantLock

线程篇

  1. 启动线程是run() 还是start()?
    1. Strat()方法是启动(开辟)线程的方法,因此线程的启动必须通过此方法.
    2. run()方法是线程的一个方法。主要写现成的具体业务内容,不具有启动的能力。
  2. 实现多线程的几种方式?
    1. 继承Thread
    2. 实现Runnable接口来实现,
    3. 通过Callable和FutureTask创建线程
    4. 通过线程池创建线程
  3. ThreadLocal线程隔离
    1. 解释:ThreadLocal 是一个线程的内部存储类,主要方法有set和get,可以用于介于对现在多线程的情况下出现线程安全的问题。通过get和set方法就可以得到当前线程对应的值。

    2. 实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。

    3.  public void set(T value) {
           	//获取线程t,
              Thread t = Thread.currentThread();
           	//在ThreadLocalMap中 用线程t作为key 要存储的对象作为vlaue
              ThreadLocalMap map = getMap(t);
              if (map != null)
                  map.set(this, value);
              else
                  createMap(t, value);
          }
         
      public T get() {
          	//获取线程t,
              Thread t = Thread.currentThread();
          	//获取线程t的map
              ThreadLocalMap map = getMap(t);
              if (map != null) {  //判断map是否存在
                  ThreadLocalMap.Entry e = map.getEntry(this); //判断
                  if (e != null) {
                      @SuppressWarnings("unchecked")
                      T result = (T)e.value;
                      return result;
                  }
              }
              return setInitialValue();
          }
      
      
    4. 作用:

      1. 进行事务操作,用于存储线程事务信息。
      2. 数据库连接,Session会话管理。
      3. 线程间数据隔离
      4. 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  4. Thread和Runnable 的区别
    1. Java只支持单继承,所以使用Thread 有局限性,Runnable接口可以实现多个。

    2. 在实现Runable接口的时候调用Thread的Thread(Runnable run)或者Thread(Runnable run, String name)构造方法创建进程时,使用同一个Runnable实例,所以建立的多线程的实例变量是可以共享的。

      public class TraditionalThread {
      
          public static void main(String[] args) {
      
              Runnable runnable = new Runnable() {
                  public void run() {
                      System.out.println("runnable :" + Thread.currentThread().getName());
                  }
              };
      
              Thread thread = new Thread(runnable) {
                  public void run() {
                      System.out.println("thread :" + Thread.currentThread().getName());
                  }
              };
      
              thread.start();
          }
      }
      ————————————————
      结果
      thread:Thread-0
      
      当我们调用thread.start()方法时,虚拟机会自动去调用其run()方法。而当run()方法被覆盖时会调用我们重写的方法,便调用不到runnable .run()方法。这点我们可以从Thread类的源代码中看到。
      
  5. 什么叫守护线程,用什么方法实现守护线程?
    1. 守护线程是运行在后台的一种特殊进程。当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
    2. Thread.setDeamon(true)方法实现守护线程
  6. 如何停止一个线程?
    1. 使用interrupt方法中断线程。
  7. sleep() 、join()、yield()、wait()有什么区别
    1. sleep()将运行状态的线程转为其他阻塞状态。,线程进入其他阻塞(睡眠)状态,期间对对象的锁并不会解开。会在指定时间结束后转为就绪状态。是Thread类的方法。不考虑优先级问题 。在指定的时间内不可能再次获得执行机会 。
    2. wait()该线程放弃对该对象的锁,线程进入等待阻塞状态,期间对对象的锁会解开。必须在经过其他线程调用notify()或notifyAll()方法唤醒后,转到锁池,等到得到对象的锁后,才会转为就绪状态 。是Object类的方法,和notify()和notifyAll()需要在synchronized(object)同步代码块中才能使用
    3. yield()暂停当前正在执行的线程对象,放入就绪状态,从就绪状态中重新调出相同或更高优先级的线程。在让步后,可能会再次获得执行机会 ,会考虑优先级问题 ,会直接转为就绪状态
    4. join()方法,在当前线程中调用另一个线程的join()方法,使当前前程进入其他阻塞状态,直到另一个线程运行结束,当前线程才从其他阻塞状态转为就绪状态
  8. 线程的生命周期
    1. 新建,就绪,运行,阻塞(等待阻塞,同步阻塞,其他阻塞),死亡

      1. 新建一个线程对象
      2. 使用start()方法将线程处于就绪状态
      3. 就绪状态后CPU会调度该线程
      4. 阻塞状态:是线程因为某些原因放弃了CPU使用权,暂时停止运行。只用当程序重新进入就绪状态,才有机会转为运行状态。阻塞状态分为三种:
          (1) 等待阻塞:指运行的线程执行了wait()方法,当前线程会放弃对该对象的锁,JVM将该线程放入等待池中(wait会释放该线程所持有的锁)
          (2) 同步阻塞:指运行的线程在获取对象的同步锁时,该锁被其他线程占用,JVM会将该线程放入锁池
          (3)其他阻塞:运行的线程执行sleep()或join()方法,进入阻塞状态,等待CPU调度,线程重新进入就绪状态(sleep不会释放该线程所持有的锁)
      5. 死亡状态:该线程执行完了,或者调用了某些方法退出了run()方法,结束了生命周期

      线程生命周期

  9. 线程池的实现原理
    1. java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。

    线程池原理

    1. public ThreadPoolExecutor(int corePoolSize,   //核心线程数 ,规定可运行的线程数
                                int maximumPoolSize, //最大线程数
                                long keepAliveTime, //存活时间
                                TimeUnit unit,  //存活时间的时间单位
                                BlockingQueue<Runnable> workQueue, //阻塞队列(用来保存等待被执行的任务) 存放任务的队列
                                ThreadFactory threadFactory,  //线程工厂,主要用来创建线程
                                RejectedExecutionHandler handler  //表示当拒绝处理任务时的策略,有以下四种取值
                               ) 
      

      注: 当线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

      ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

      ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

      ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

      ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

      当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

  10. 线程池的几种方式?应用场景

​ Executors类有一些静态方法可以创建线程池Executor。

newFixedThreadPool:创建固定长度的线程池

newCachedThreadPool:创建一个可缓存的线程池,自动回收空闲线程,自动扩展新线程

newSingleThreadExecutor:创建一个单线程来执行任务

newScheduledThreadPool:创建一个固定长度的线程池,可演示或定时执行任务

  1. 什么是死锁,如何预防?
  2. 死锁是指两个或两个以上的进程,因为资源的抢夺,而造成相互等待的情况,如果在无外力的情况下,他们无法推进下去,需要满足足四个条件:互斥条件,不剥夺条件,请求和保持条件,循环等待条件。

    1. 三种用于避免死锁的技术:
      加锁顺序(线程按照一定的顺序加锁)
      加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
      死锁检测

Java虚拟机篇

  1. Java内存区域
    1. 图摘自《码出高效》

      1. 程序计数器:当前线程所执行的字节码的行号指示器,通过改变计数器的值来选取下一条需要执行的字节码指令

      2. java本地栈:本地栈为虚拟机使用到的Native方法服务,即被Native修饰的方法。

      3. java虚拟机栈:虚拟机栈为java方法服务,存储局部变量表,操作数栈,动态链接,方法出口。局部变量表存放各种基本数据类型,对象引用。

      4. java堆区: 存放几乎所有的对象实例,被所有线程所共享的内存区域,也是Java虚拟机所管理的最大一块内存。垃圾回收机制主要是在着,也被称做“GC堆”。所以 Java 堆中还可以细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等。从内存分配的角度来看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。

      5. 元数据区(方法区):同样是被所有线程共享的区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

        1. JDK8的时候,永久代退出历史舞台,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中,方法区由本地内存的元空间实现

        2. 元数据区代替永久代,以前永久代的字符串常量转移到堆内存中其他内容移至元空间,元空间直接在本地内存分配。

        3. 运行时常量池
        4. 直接内存
      6. Java线程与内存 -《码出高效》

    2. 为什么要使用元空间取代永久代的实现?
      1. 字符串存在永久代中,容易出现性能问题和内存溢出。
      2. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
      3. 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
      4. 将 HotSpot 与 JRockit 合二为一。
    3. Java中的内存溢出是如何造成的
      1. 当无用对象不能被垃圾回收器收集的时,我们称之为内存泄露.
    4. JVM垃圾回收机制
      1. 当java项目在运行时,会有很多object被类加载到堆中,堆中分为新生代和老年代,新生代中有Eden、s1,s2 一开始对象在eden区,随着每次发生minor gc的时候 对象的 age+1 有的对象一直被调用所以就没有被回收,进入了s1,s2,然后s1和s2 不断地更换,当object的age到了15 就进入了 老年代,当object的age到了35的时候机会发生full gc 被回收、
    5. JVM为什么要回收
      1. 随着项目的运行,越来越多的对象被放到了堆栈中,而物理内存是有限的,最终导致电脑运行卡顿,所以对堆中没有引用的对象进行回收,来降低JVM虚拟机对物理内存的占用
    6. JVM如何判断对象是可以被回收?
      1. 引用计数:每当有需要引用对象的地方就对对象的计数+1,引用失效则-1,

        优点:实现简单、判断效率高。 缺点:难以解决对象之间的循环引用问题。

      2. 可达性分析算法:从一系列“GC Roots”对象作为起始点,从这些节点向下搜索,搜索走过的路径叫引用链。从GC Roots到该对象不可达,则该对象不可用。

        Java中GC Roots包括以下几种对象:
           a.虚拟机栈(帧栈中的本地变量表)中引用的对象
           b.方法区中静态属性引用的对象
           c.方法区中常量引用的对象
           d.本地方法栈中JNI引用的对象

    7. JVM GC如何回收对象?与其工作原理
      1. 标记-清除算法:标记所有要清除的对象,标记完成后统一清除,但是会产生大量不连续碎片
      2. 复制算法:将可用内存分为大小相等的两份,每次只使用一块。当一块内存用完了,就把还存活的对象复制到另外一块上,把使用过的内存空间清理掉。
      3. 标记-整理算法:标记完后,所有存活对象向一端移动,然后直接清理掉边界以外内存。
      4. 分代收集算法:把java堆分为新生代和老年代。新生代,每次垃圾收集都会有大批对象死去,少量存活,采用“复制”算法。老年代对象存活率高,可以使用“标记-清理”,“标记-整理”算法进行回收
    8. 垃圾收集器有哪些
      1. Serial(单线程)

        ​ Serial GC,它是最古老的垃圾收集器,“Serial”体现在其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态。当然,其单线程设计也意味着精简的 GC 实现,无需维护复杂的数据结构,初始化也简单,所以一直是 Client 模式下 JVM 的默认选项。
        从年代的角度,通常将其老年代实现单独称作 Serial Old,它采用了标记 - 整理(Mark-Compact)算法,区别于新生代的复制算法。
        Serial GC 的对应 JVM 参数是:

        -XX:+UseSerialGC

      2. ParNew(多线程)

        ​ 很明显是个新生代 GC 实现,它实际是 Serial GC 的多线程版本,最常见的应用场景是配合老年代的 CMS GC 工作,下面是对应参数:

        -XX:+UseConcMarkSweepGC -XX:+UseParNewGC

      3. CMS

        ​ 基于标记 - 清除(Mark-Sweep)算法,设计目标是尽量减少停顿时间,这一点对于 Web 等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用 CMS GC。但是,CMS 采用的标记 - 清除算法,存在着内存碎片化问题,所以难以避免在长时间运行等情况下发生 full GC,导致恶劣的停顿。另外,既然强调了并发(Concurrent),CMS 会占用更多 CPU 资源,并和用户线程争抢。

      4. Parrallel GC(并行),

           在早期 JDK 8 等版本中,它是 **server** 模式 JVM 的默认 GC 选择,也被称作是吞吐量优先的 GC。它的算法和 Serial GC 比较相似,尽管实现要复杂的多,其特点是新生代和老年代 GC 都是并行进行的,在常见的服务器环境中更加高效。开启选项是:
        

        -XX:+UseParallelGC

      5. G1 GC

        ​ 这是一种兼顾吞吐量和停顿时间的 GC 实现,是 Oracle JDK 9 以后的默认 GC 选项。G1 可以直观的设定停顿时间的目标,相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。
        G1 GC 仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当 Java 堆非常大的时候,G1 的优势更加明显。
        G1 吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时 CMS 已经在 JDK 9 中被标记为废弃(deprecated),所以 G1 GC 值得你深入掌握。

    9. JVM回收机制的优化
      1. 性能调优建议:

        1. 针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值;

        2. 年轻代和年老代将根据默认的比例(1:2)分配堆内存, 可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代。比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。

        3. 年轻代和年老代设置多大才算合理

          1)更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC

          2)更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率

          如何选择应该依赖应用程序对象生命周期的分布情况: 如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性。

        在抉择时应该根 据以下两点:

        (1)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 。

        (2)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间。

      2. 在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC 。

      3. 线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。

        理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。

    10. ClassLoader的功能和工作模式

      Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中,JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,loadClass使用双亲委派模式。

      ​ 1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
      ​ 2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全

锁机制

  1. 解释是一下什么是线程安全?举例说明一个线程不安全的例子。
    1. 线程安全是指在多线程的情况下,当一个线程访问该类的某个数据时,对该类进行保护,其他线程不能访问直到该线程访问完成,不会出现数据不一致或者数据污染
    2. 当线程A对对象的某个数据赋值为11,线程B却赋值为22,当线程A再次访问该对象发现赋值变化了。被污染了
  2. volatile

    volatile是一种稍弱的同步机制,把变量声明为volatile类型后,JVM会注意到这个变量是共享的,不会进行重排序,也不会缓存到寄存器等不可见的地方,所以读取volatile类型的变量会返回最新写入的值

    它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

  3. synchronized

    synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性

  4. Synchronized方法锁、对象锁、类锁区别
    1. 无论是修饰方法还是修饰代码块都是 对象锁,当一个线程访问一个带synchronized方法时,由于对象锁的存在,所有加synchronized的方法都不能被访问(前提是在多个线程调用的是同一个对象实例中的方法)
    2. 2无论是修饰静态方法还是锁定某个对象,都是 类锁.一个class其中的静态方法和静态变量在内存中只会加载和初始化一份,所以,一旦一个静态的方法被申明为synchronized,此类的所有的实例化对象在调用该方法时,共用同一把锁,称之为类锁。
  5. synchronized(隐式锁) 与 lock (显示锁)的区别

    所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。

    1. synchronized 是java的关键字,jvm来维护,lock是java的类jdk5后
    2. synchronized 无法判断锁的状态,lock可以判断锁的状态
    3. synchronized 可以自动释放锁,lock需要手动释放
    4. synchronized 的锁不可中断,lock的锁可以中断,轮询,定时
    5. synchronized锁适合代码少量的同步问题,Lock锁适合大量同步的代码的同步问题
  6. 读写锁和互斥锁
    1. 读写锁:允许多个读操作同时进行,但每次只允许一个写操作。

      读写锁的机制:
      * "读-读"不互斥
      * "读-写"互斥
      * "写-写"互斥

      即在任何时候必须保证:
      * 只有一个线程在写入;
      * 线程正在读取的时候,写入操作等待;
      * 线程正在写入的时候,其他线程的写入操作和读取操作都要等待

    2. 互斥锁:一个线程获得资源的使用权后就会将该资源加锁,使用完后会将其解锁。所以不能读/读,不能写/写,更不能读/写

  7. CAS乐观锁和悲观锁,行锁和表锁
    1. 悲观锁:顾名思义 总是假设最坏的情况,每次拿数据的时候总觉得别人会修改数据,所以拿到数据的时候就上锁,别人想拿到数据,就会堵塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。
    2. 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
    3. Compare and Swap ( CAS )。CAS是乐观锁技术,其主要步骤是冲突检测和数据更新。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

设计模式

  1. 设计模式的设计理念
    1. 为了重用代码、让代码更容易被他人理解、保证代码可靠性。
    2. 开闭原则:对扩展开放,对修改关闭。
    3. 里氏代换原则:实现子父类互相替换,即继承;
    4. 依赖倒转原则:针对接口编程,实现开闭原则的基础;
    5. 接口隔离原则:降低耦合度,接口单独设计,互相隔离;
    6. 迪米特法则,又称不知道原则:功能模块尽量独立;
    7. 单一职责原则:一个类只负责一项职责
    8. 合成复用原则:尽量使用聚合,组合,而不是继承;
  2. 单例模式-》饿汉模式和懒汉模式
    1. 饿汉模式

      /*
       *饿汉式
       *类加载到内存后,就实例化一个单例,Jvm保证线程安全
       *缺点:不管是否使用到了,类装载时就完成实例化
       *(话说 你不用你装载它干啥)
       */
      public class Mar01 {
          private static final Mar01 INSTANCE = new Mar01();
          private Mar01() { }
          public static Mar01 getInstance() { return INSTANCE; }
          public void m(){ System.out.println("m"); }
          public static void main(String[] args) {
              Mar01 mar01=Mar01.getInstance();
              Mar01 mar02=Mar01.getInstance();
              System.out.println(mar01==mar02);
          }
      }
      
      
    2. 懒汉模式

      /*
       *lazy loading 懒汉模式
       *  虽然达到了按需初始化 但是出现了线程不安全的问题
       *
       */
      
      class Mar01 {
          private static Mar01 INSTANCE;
      
          private Mar01() { };
          public static Mar01 getInstance() {b
              if (INSTANCE == null) {// 如果线程A判断INSTANCE==null  然后 线程B执行了下面代码 创建了Mar01 然后A判断通过后 会创建2个Mar01 造成线程不安全问题
                  try {
                      Thread.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  INSTANCE = new Mar01();
              }
              return INSTANCE;
          }
          public void m() { System.out.println("m"); }
          public static void main(String[] args) {
              for (int i = 0; i < 100; i++) {
                  new Thread(() -> {
                      System.out.println(Mar01.getInstance().hashCode());
                  }).start();
              }
          }
      
      }
      
    3. 完美解决方法 静态内部方法

      /*
       *静态内部类
       * jvm保证单例
       * 加载外部类,不会加载内部类,这样可以实现懒加载
       */
      class Mar01 {
          private Mar01() { };
          private static class  Mar01Holder {
              private  final static Mar01 INSTANCE=new Mar01();
          }
          public static Mar01 getInstance(){
              return  Mar01Holder.INSTANCE;
          }
          public void m() { System.out.println("m"); }
          public static void main(String[] args) {
              for (int i = 0; i < 100; i++) {
                  new Thread(() -> {
                      System.out.println(Mar01.getInstance().hashCode());
                  }).start();
              }
          }
      }
      

JAVA I/O篇

  1. NIO 和传统 IO 之间第一个最大的区别是
      1. IO 是面向流的,NIO 是面向缓冲区的。
    
    1. I/O主要有字符流和字节流组成,NIO主要有buffer和channel,selector(选择去)组成
  2. 阻塞IO与非阻塞IO的区别
    1. 阻塞性I/O是用户线程出现IO请求时,判断数据是否就绪,未就绪等待,用户线程阻塞,交出CPU,就绪后在执行,结束block状态。data = socket.read();

    2. 非阻塞性I/O是用户发送IO请求时,判断数据是否就绪并直接返回结果error,进行while循环 一直请求,直到准备好了,并且接收到用户请求才执行,也就是非阻塞I/O不交出CPU 从获得CPU一直到数据准备好了 完成I/O操作才释放

      1. while(true){
        	data = socket.read();
        	if(data!= error){
        		处理数据
        		break;
        	}
        }
        
    3. 多路复用I/O当用户发起I/O请求时,会有一个线程不断去轮询socket的状态,当socket真正有读写时间的时候,才会调用资源进行I/O读写操作在 Java NIO 中,是通过 selector.select()去查询每个通道是否有到达事件。

      1. 多路复用优点:比非阻塞效率要高的原因是因为 多路复用是使用内核去轮询socket 非阻塞是使用的用户线程去轮询
      2. 多路复用缺点:一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。
    4. 异步I/O

      当用户线程发起请求,内核接收到了 asynchronous read 之后立即返回结果,如果数据准备好了就返回状态 用户线程继续执行,不阻塞用户线程,直接使用数据(当完成后将结果拷贝给用户线程)

    5. buffer和channel

      1. buffer是是缓冲区,channel 是通道,

      2. 一般数据都是成channel 通道开始,可以读取buffer内的数据 也可以写入数据到buffer

      3. channel和I/O中的流比较相似,但是不同在于流是单向的 channel是双向。

        数据读取和写入操作图示

    6. 非直接缓冲区和直接缓冲区的区别
      1. 非直接缓冲区,通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
  3. 直接缓冲区,通过allocateDirect() 方法分配缓冲区,将缓冲区建立在物理内存中

  4. selector是什么?
    1. 选择区,用来监听多个注册的信道的连接打开和数据到达,实现单线程监控多个信道。
      2. image-20200710143039649

      1. Java NIO Channel通道和流非常相似,主要有以下几点区别:
        • 通道可以读也可以写,流一般来说是单向的(只能读或者写,所以之前我们用流进行IO操作的时候需要分别创建一个输入流和一个输出流)。
    • 通道可以异步读写。
      - 通道总是基于缓冲区Buffer来读写。

      1. Channel的几种实现

        1. FileChannel 用于文件数据读写

        2. DatagramChannel 主要用于DCP的读写

        3. SocketChannel 用于TCP数据读写,主要是客户端

        4. ServerSocketChannel 允许我们监听TCP请求,每个请求会创建会一个SocketChannel主要是服务器端

          如果要从写入缓冲区 读取数据需要调用

          buffer.filp()方法 将Buffer从写模式变为可读模式

          package filechannel;
          
          import java.io.IOException;
          import java.io.RandomAccessFile;
          import java.nio.ByteBuffer;
          import java.nio.channels.FileChannel;
          
          public class FileChannelTxt {
              public static void main(String args[]) throws IOException {
                  //1.创建一个RandomAccessFile(随机访问文件)对象,
                  RandomAccessFile raf=new RandomAccessFile("D:\\niodata.txt", "rw");
                  //通过RandomAccessFile对象的getChannel()方法。FileChannel是抽象类。
                  FileChannel inChannel=raf.getChannel();
                  
                  //2.创建一个读数据缓冲区对象
                  ByteBuffer buf=ByteBuffer.allocate(48);
                  //3.从通道中读取数据
                  int bytesRead = inChannel.read(buf);
                  //创建一个写数据缓冲区对象
                  ByteBuffer buf2=ByteBuffer.allocate(48);
                  //写入数据
                  buf2.put("filechannel test".getBytes());
                  buf2.flip();
                  inChannel.write(buf);
                  while (bytesRead != -1) {
          
                      System.out.println("Read " + bytesRead);
                      //Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式。
                      buf.flip();
                     //如果还有未读内容
                      while (buf.hasRemaining()) {
                          System.out.print((char) buf.get());
                      }
                      //清空缓存区
                      buf.clear();
                      bytesRead = inChannel.read(buf);
                  }
                  //关闭RandomAccessFile(随机访问文件)对象
                  raf.close();
              }
          }
          
          public abstract class FileChannel
              extends AbstractInterruptibleChannel
              implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
          1. 开启FileChannel
          

        因为fileChannel 是虚拟类 无法直接实现,需要通过InputStream,OutStream及RandoAccessFile 开启。
        2.从FileChannel读取数据/写入数据
        创建读取缓冲区将数据读取
        ByteBuffer buf=ByteBuffer.allocate(48);
        inChannel.read(buf1);
        创建写入缓冲区将数据写入
        ByteBuffer buf2=ByteBuffer.allocate(48);
        buf2.put(“filechannel test”.getBytes());//将这句话写入到文本中

           buf2.flip();
           inChannel.write(buf2);
        

        3.关闭通道
        channel.close();

        
        5. SocketChannel和ServerSocketChannel
        
        ```java
        客户端 开启SocketChannel
        1.通过SocketChannel连接到远程服务器
        2.创建读数据/写数据缓冲区对象来读取服务端数据或向服务端发送数据
        3.关闭SocketChannel    
        //1.通过SocketChannel的open()方法创建一个SocketChannel对象
         SocketChannel socketChannel = SocketChannel.open();
        //2.连接到远程服务器(连接此通道的socket)
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 3333));
        //3.创建缓冲区 读取写入 关闭
        
         服务端
        
        ​```
        
        ​```
        
        ​       1.通过ServerSocketChannel 绑定ip地址和端口号
        ​       2.通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
        ​       3.创建读数据/写数据缓冲区对象来读取客户端数据或向客户端发送数据
        
                  4. 关闭SocketChannel和ServerSocketChannel    
                     .通过ServerSocketChannel 的open()方法创建一个ServerSocketChannel对象,open方法的作用:打开套接字通道
                             ServerSocketChannel ssc = ServerSocketChannel.open();
                            //2.通过ServerSocketChannel绑定ip地址和port(端口号)
                            ssc.socket().bind(new InetSocketAddress("127.0.0.1", 3333));
                            //通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
                            SocketChannel socketChannel = ssc.accept();
        
        
        
        1. DatagramChannel的使用

          1. DataGramChannel,类似于java 网络编程的DatagramSocket类;使用UDP进行网络传输, UDP是无连接,面向数据报文段的协议,对传输的数据不保证安全与完整
      2.    //1.通过DatagramChannel的open()方法创建一个DatagramChannel对象
           DatagramChannel datagramChannel = DatagramChannel.open();
           //绑定一个port(端口)
           datagramChannel.bind(new InetSocketAddress(1234));
           ---------------------------------------
            因为是无连接的 无需建立连接 只需要知道地址就可以
           2.接收消息
           //先创建一个缓存区对象,
           ByteBuffer buf = ByteBuffer.allocate(48);
           buf.clear();
           //然后通过receive方法接收消息,这个方法返回一个SocketAddress对象,表示发送消息方的地址
           channel.receive(buf);    
           3.发送消息:
           //创建缓冲区
           ByteBuffer buf = ByteBuffer.allocate(48);
           buf.clear();
           //输入要发送的内容
           buf.put("datagramchannel".getBytes());
           //改为读模式
           buf.flip();
           发送的地址 返回发送成功的字节数
           int send = channel.send(buffer, new InetSocketAddress("localhost",1234));
        
      3. 通道之间的数据传输

        1. 在Java NIO中如果一个channel是FileChannel类型的,那么他可以直接把数据传输到另一个channel。
          - transferFrom() :transferFrom方法把数据从通道源传输到FileChannel
          - transferTo() :transferTo方法把FileChannel数据传输到另一个channel
      4. Scattering Reads

        1. “scattering read”是把数据从单个Channel写入到多个buffer,
        2. scattering read
      5. Gathering Writes

        1.  “gathering write”把多个buffer的数据写入到同一个channel中,
          

        Gathering Writes

数据库篇

  1. InnoDB和MyISAM的区别 和应用场景

    image-20200710143250846

    1. 应用场景
      1. MyISAM是非事务表,提供快速的检索和高效的存储,如果应用中有大量的Select查询,使用MyISAM 是非常合适的
      2. Innodb是事务处理程序,具有众多特性,包括ACID事务支持,如果应用中由很多的Insert和Update操作,建议使用Innodb
    2. MyISAM特点
      1. 执行读取速度快,
      2. 不占用大量内存
      3. 支持全局索引,
      4. 支持表锁
      5. 不支持事务和外键
    3. InnoDB的特点 =>InnoDB 底层存储结构为B+树
      1. 经常更新的表,适合处理多重并发的更新请求。
      2. 支持事务。
      3. 可以从灾难中恢复(通过 bin-log 日志等)。
      4. 外键约束。只有他支持外键。
      5. 支持自动增加列属性 auto_increment
  2. InnoDB的事务和日志
    1. InnoDB有错误日志,查询日志,慢查询日志,二进制日志,事务日志。
      错误日志:记录出错信息
      查询日志:记录所有对数据库请求的信息
      慢查询日志:设置一个阈值,运行时间超过该值的所有SQL语句都会被记录下来。
      二进制日志:记录对数据库执行更改的所有操作

    事务的原子性,一致性,隔离性,持久性。

    并发事务产生的原因

    • 脏读(dirty read):读到未提交更新数据,即读取到了脏数据;
    • 幻读(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录;
    • 不可重复读(unrepeatable read):对同一记录的两次读取不一致,因为另一事务对该记录做了修改;
  3. 事务4种隔离级别
    1. 串行化:不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;防止脏读,不可重读读,幻读
    2. 可重复读:防止脏读和不可重复读,不防幻读
    3. 读已提交:防止脏读,不防止不可重复读和幻读
    4. 读未提交:可能出现任何事务并发问题
  4. Sql优化
    1. 索引失效:
      1. sql优化就是在编写查询SQL语句的过程中尽量用上索引,避免索引失效,避免全表扫描。
      2. 应尽量避免在 where 子句中对字段进行 null 值判断
      3. 最佳左前缀法则:如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
      4. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
      5. 应尽量避免在 where 子句中使用!=或<>操作符,‘%模糊查询%’,字符串添加单引号
    2. 使用explan sql 对语句进行SQL语句优化
  5. 索引优化

    频繁作为查询条件的字段应该创建索引
    经常增删改的表不创建索引
    单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
    关联查询优化:小表驱动大表,大表join字段已经被索引
    order by关键字优化:尽量Index方式,避免FileSort排序;尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
    GROUP BY关键字优化:group by实质是先排序后进行分组,遵照索引建的最佳左前缀;where高于having,能写在where限定的条件就不要去having限定了。

  6. 索引是什么?
    1. 索引是排好序的快速高效查找数据的数据结构.索引往往以索引文件的形式存储的磁盘上
  7. 索引的分类
    1. 普通索引:仅加速查询

    2. 唯一索引:加速查询 + 列值唯一(可以有null)

    3. 主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个

    4. 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并

    5. 全文索引:对文本的内容进行分词,进行搜索

      注意:聚簇索引并不是一种单独的索引类型,而是一种数据存储方式,表示数据行和相邻的键值的存储在一起。数据行在磁盘的排列和索引排序保持一致。在查询的时候因为数据紧密相连的,不用从多个数据块提取,节约时间

  8. 什么时候应该使用索引?
    1. 表中的字段频繁被where 查询,建立索引
    2. 表中的外键,与其他表关联的建立索引
    3. order by group by的字段可以建立索引。
  9. 什么时候不建议使用索引?
    1. 表数据太少
    2. 频繁insert,update
    3. 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。
    4. 索引其实是用空间换时间的概念,所以要很良好两者之间的舍去。
  10. BTree和B+Tree的区别
  11. Mysql索引类型Btree和Hash的区别以及使用场景
     ![image-20200710153516144](https://imgconvert.csdnimg.cn/aHR0cDovL2RhZXIueGlhc2hlbmcud29yay9pbWFnZS0yMDIwMDcxMDE1MzUxNjE0NC5wbmc?x-oss-process=image/format,png)
    
     2. BTREE类型的索引,在存储的时候是存储的Kay-value的形式在链表上有顺序的存储。
    
     3. 为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?
    
        1. B+树的磁盘读写代价更低
    2. B+树的查询效率更加稳定
    
     4. 在B_TREE上查找x,现将x的关键字与根结点的n个关键字di逐个比较,然后做如下处理:
    
        - 若x.key==28,则查找成功返回;
    - 若x.key<28,则沿着指针p1所指的子树继续查找;
        - 若28<x.key<链表最大值,则沿着指针p2所指的子树继续查找;
        - 若x.key><链表最大值,则沿着指针后面所指的子树继续查找。
    
  12. 什么是show profile?
    1. 是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量
      诊断SQL,show profile cpu,block io for query
  13. 查询语句中select from where group by having order by的执行顺序
    1. from–where–group by–having–select–order by,

      from:需要从哪个数据表检索数据

      where:过滤表中数据的条件

      group by:如何将上面过滤出的数据分组

      having:对上面已经分组的数据进行过滤的条件

      select:查看结果集中的哪个列,或列的计算结果

      order by :按照什么样的顺序来查看返回的数据

    2. SQL Select语句完整的执行顺序【从DBMS使用者角度】:
        1、from子句组装来自不同数据源的数据;
        2、where子句基于指定的条件对记录行进行筛选;
        3、group by子句将数据划分为多个分组;
        4、使用聚集函数进行计算;
        5、使用having子句筛选分组;
        6、计算所有的表达式;
        7、使用order by对结果集进行排序。

Spring框架

  1. Spring管理如何创建自定义注解
    1. img
    2. 只要在定义接口的基础上再interface前面加上@符号,这时候接口就变成了注解的定义了。但是还不完全是完成了注解,还要加上上面几个关键字
    3. @ target表示该注解的作用域,值有TYPE, METHOD, CONSTRUCTOR, FIELD,我们常用field和method,表示作用在java bean的字段和作用在方法层面上。
    4. @retention表示注解类型保留时间的长短,它接收RetentionPolicy参数,可能的值有SOURCE, CLASS, 以及RUNTIME,我们常用runtime,表示
    5. @constraint表示注解的约束关系,其中有属性值validateBy这个属性开放出来给我么使用,目的是设定约束关系的实现类,这点与我们上面谈到的第2点有关
    6. @document表示该注解可以被javadoc等工具文档化
  2. Spring 如何创建对象?

    1. 利用构造函数创建对象

      1.  <bean class="com.mc.base.learn.spring.bean.Person" id="person">
                <constructor-arg name="id" value="123"></constructor-arg>
                <constructor-arg name="name" value="LiuChunfu"></constructor-arg>
            </bean>
        
    2. 通过静态方法创建对象

      1.    <!--静态的工厂方法核心是class+factory-method -->
            <bean id="person" class="com.mc.base.learn.spring.factory.PersonStaticFactory" factory-method="createPerson"></bean>
        
    3. 通过工厂方法创建对象

      1.   <!-- 实例工程方法需要先创建工厂实例,然后在创建所需对象的时候,将其赋值为factory-bean -->
            <bean id="personFactory" class="com.mc.base.learn.spring.factory.PersonFactory"></bean>
            <bean id="person2" factory-bean="personFactory" factory-method="createInstance"></bean>
        
  3. Spring 如何解决循环依赖的问题
    1. 三级缓存

      1. singletonFactories : 单例对象工厂的cache
      2. earlySingletonObjects :提前暴光的单例对象的Cache 。【用于检测循环引用,与singletonFactories互斥】
      3. singletonObjects:单例对象的cache
    2. 分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,

      Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory
      alue=“LiuChunfu”>

      
      
    3. 通过静态方法创建对象

      1.    <!--静态的工厂方法核心是class+factory-method -->
            <bean id="person" class="com.mc.base.learn.spring.factory.PersonStaticFactory" factory-method="createPerson"></bean>
        
    4. 通过工厂方法创建对象

      1.   <!-- 实例工程方法需要先创建工厂实例,然后在创建所需对象的时候,将其赋值为factory-bean -->
            <bean id="personFactory" class="com.mc.base.learn.spring.factory.PersonFactory"></bean>
            <bean id="person2" factory-bean="personFactory" factory-method="createInstance"></bean>
        
  4. Spring 如何解决循环依赖的问题
    1. 三级缓存

      1. singletonFactories : 单例对象工厂的cache
      2. earlySingletonObjects :提前暴光的单例对象的Cache 。【用于检测循环引用,与singletonFactories互斥】
      3. singletonObjects:单例对象的cache
    2. 分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,

      Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值