JAVA面试复习题

基础篇

***final, finally, finalize 的区别***

  final关键字,代表着不可变,可以保证他们在使用的过程中不被修改。

    final修饰的数据,代表着初始化后永不可变,只能读取
    final修饰的方法,代表任何继承类都没法进行方法重写
    final修饰的类,代表不能派生子类,无法被继承

  finally意味一定会被执行的块,通常与try块和catch块一起使用,无论异常是否发生,都会执行finally的清理操作

  finallize是Object类的方法,JVM GC在确定这个对象没有被引用时,打算从内存中清除该对象前,该方法一定会被调用,且仅被调用一次


***重载和重写的区别***

  重载:多个同名函数存在,有不同类型参数或不同个数参数,返回值类型和访问权限大小,抛出的异常不同,不能算重载

  重写:子类方法对父类方法的一种重新定义和继承


***说说反射的用途及实现***

  指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象

  获取class对象的方式来使用反射
    通过对象Object的getClass()方法
    通过类的静态class属性
    通过Class类的静态方法forName(String className)


***抽象类和接口有什么区别***

  interface是接口,abstract class是抽象类。
  抽象类中可以拥有任意范围的成员数据,可以定义非抽象方法。
  接口中只能拥有静态的不能修改的成员数据,同时所有的方法必须是抽象的。


***equals和==有什么区别***

  "”常用来比较基本数据类型,8种基本数据类型有byte、short、long、double、char、int、float、boolean,因为变量直接存储的就是他们的值,所以用""去比较,比较的就是他们的值。
  但是复合数据类型用“==”比较的是他的堆内存地址。

  “equals”对于复合数据类型比较的也是它的堆内存地址(不能作用于基本数据类型的变量)。
  实际项目中常用来比较String字符串的内容相等是因为重写了equals方法,使其比较的是存储对象的内容是否相等。


***各种内部类的区别***

  成员内部类:普通内部类,与外部对象存在联系。需依附外部对象方能实例化
  静态内部类:与外部对象不存在联系。可直接实例化
  匿名内部类:无名,继承或实现那个类
  局部内部类:方法体内或作用域内可以使用



作用域 public,protected,缺省,private的区别

范围 private 缺省 protected public
同类
同包
子类
全局



***Object的方法有哪些***

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


***String和StringBuffer、StringBuilder的区别***

  一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
  String类中使用字符数组保存字符串,因为有final修饰,所以string是不可变的。
  StringBUilder和StringBuffer的公共父类是:AbstracStringBuilder类,这两种对象都是可变的。

  String中对象是不可变得,也可以理解为常量,显然是线程安全的。
  StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
  StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。



***可变与不可变化性***

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



hashCode的作用

  
hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。

考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?

当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值.如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,然后存进去。


***hashCode和equals方法的关系***

  在重写equals方法的同时,必须重写hashCode方法。
  对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等;
  如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同;
  如果两个对象的hashcode值不等,则equals方法得到的结果必定为false;
  如果两个对象的hashcode值相等,则equals方法得到的结果未知。


***HTTP 请求的 GET 与 POST 方式的区别***

   GET POST
缓存 能被缓存 不能缓存
编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。
历史 参数保留在浏览器历史中。 参数不会保存在浏览器历史中。
对数据长度的限制 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。 无限制。
对数据类型的限制 只允许 ASCII 字符。 没有限制。也允许二进制数据。
安全性 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET ! POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。
可见性 数据在 URL 中对所有人都是可见的。 数据不会显示在 URL 中。



***session 与 cookie 区别***

  1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
  2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
考虑到安全应当使用session。
  3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用COOKIE。
  4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
  5、所以个人建议:将登陆信息等重要信息存放为SESSION。其他信息如果需要保留,可以放在COOKIE中


集合篇

***List 和 Set 区别***

  List和Set都属于Collection父类的

List:
   1.可以允许重复的对象。
   2.可以插入多个null元素。
   3.是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。

Set:

   1.不允许重复对象
   2. 无序容器,你无法保证每个元素的存储顺序,TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序。
   3. 只允许一个 null 元素

***List 和 Map 区别***

   List是Collection的子接口。
   Map不是collection的子接口或者实现类。Map是一个接口。

   Map 的 每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
   Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。


***Arraylist 与 LinkedList 区别***

  ArrayList本质是数组,擅长于访问元素,中间插入元素和移除元素较慢
  LinkedList底层是链表,元素之间存在链条。擅长于插入元素,随机访问较慢

***ArrayList 与 Vector 区别***

1、Vector是线程安全的,ArrayList不是线程安全的。
2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
Vector和ArrayList一样,都继承自List,

***HashSet,LinkedHashSet,TreeSet的区别***

  HashSet:快速查找,使用散列,配合hascode,实现不重复。无序
  LinkedHashSet:使用散列,用链表维护插入顺序
  TreeSet:实现了SortedSet 接口,元素存储在红-黑树数据结构,根据其 compare() 和 compareTo() 的定义进行排序的有序容器。

  散列:任意长度的输入通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。

***HashMap 的工作原理***

  HashMap本质是数组加链表。基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到数组下标位置来放到Entry数组来储存值对象。当get()对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。

  因为hashcode相同,所以它们的数组下标位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。两个对象就算hashcode相同,但是它们可能并不相等。
这里写图片描述

***HashMap和Hashtable的区别***

  HashTable的方法是同步的,在方法的前面都有synchronized来同步,所以HashTable是线程安全的。HashMap是线程不安全的。但是HashTable的锁是全表锁,多线程访问,只有一个线程能访问,其他线程阻塞。

  HashTable不允许null值(key和value都不可以) ,HashMap允许null值(key和value都可以)。

  HashTable使用Enumeration进行遍历,HashMap使用Iterator进行遍历

  Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现

***HashSet 和 HashMap 区别***

  HashSet底层是采用HashMap实现的:
  HashMap实现了Map接口 HashSet实现了Set接口,Set实现Collection接口
  HashMap储存键值对 HashSet仅仅存储对象
  HashMap中使用键对象来计算hashcode值 HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false

***ConcurrentHashMap 的工作原理***

  ConcurrentHashMap 的加锁粒度要比HashTable更细一点。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

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

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

***HashMap 和 ConcurrentHashMap 的区别***

  HashMap和concurrentHashMap都是基于散列,数组和链表
  最大的区别就是线程安全问题,ConcurrentHashMap有分段锁的机制。当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

***CopyOnWriteArrayList和CopyOnWriteArraySet的工作原理***

  CopyOnWriteArraySet是基于CopyOnWriteArrayList实现的,只有add的方法稍微有些不同,因为CopyOnWriteArraySet是Set也就是不能有重复的元素,故在CopyOnWriteArraySet中用了addIfAbsent(e)这样的方法。

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

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

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



线程篇

***启动一个线程是用run()还是start()?***

  start()方法是启动(即开辟)一个线程的方法,因此线程的启动必须通过此方法,
  而run()方法,只是Thread类的一个方法,它本身并不能开辟线程。


***实现多线程的几种方式***

  继承Thread和实现Runnable接口来实现多线程

  两者的区别:
  a,一个类只能继承一个父类,存在局限;一个类中可以实现多个接口。

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

***什么叫守护线程,用什么方法实现守护线程***

  守护线程是运行在后台的一种特殊进程。当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
  Thread.setDeamon()方法实现守护线程

***如何停止一个线程?***

  使用interrupt方法中断线程。

***sleep() 、join()、yield()、wait()有什么区别***

  sleep()将运行状态的线程转为其他阻塞状态。,线程进入其他阻塞(睡眠)状态,期间对对象的锁并不会解开。会在指定时间结束后转为就绪状态。是Thread类的方法。不考虑优先级问题 。在指定的时间内不可能再次获得执行机会 。

  wait()该线程放弃对该对象的锁,线程进入等待阻塞状态,期间对对象的锁会解开。必须在经过其他线程调用notify()或notifyAll()方法唤醒后,转到锁池,等到得到对象的锁后,才会转为就绪状态 。是Object类的方法,和notify()和notifyAll()需要在synchronized(object)同步代码块中才能使用

  yield()暂停当前正在执行的线程对象,放入就绪状态,从就绪状态中重新调出相同或更高优先级的线程。在让步后,可能会再次获得执行机会 ,会考虑优先级问题 ,会直接转为就绪状态

  join()方法,在当前线程中调用另一个线程的join()方法,使当前前程进入其他阻塞状态,直到另一个线程运行结束,当前线程才从其他阻塞状态转为就绪状态

***线程的生命周期***

  新建,就绪,运行,阻塞(等待阻塞,同步阻塞,其他阻塞),死亡

  新建状态:新创建的一个线程对象

  就绪状态:线程创建后,其他线程调用该线程对象的start()方法。将该线程放入可运行线程池中,等待CPU调度。(线程调用调用start()方法事实上不是把线程转换为运行状态,而是从新建状态转换为就绪状态)

  运行状态:就绪状态中的线程获得CPU调度,开始该线程程序执行

  阻塞状态:是线程因为某些原因放弃了CPU使用权,暂时停止运行。只用当程序重新进入就绪状态,才有机会转为运行状态。阻塞状态分为三种:
    (1) 等待阻塞:指运行的线程执行了wait()方法,当前线程会放弃对该对象的锁,JVM将该线程放入等待池中(wait会释放该线程所持有的锁)
    (2) 同步阻塞:指运行的线程在获取对象的同步锁时,该锁被其他线程占用,JVM会将该线程放入锁池
    (3)其他阻塞:运行的线程执行sleep()或join()方法,进入阻塞状态,等待CPU调度,线程重新进入就绪状态(sleep不会释放该线程所持有的锁)

  死亡状态:该线程执行完了,或者调用了某些方法退出了run()方法,结束了生命周期
这里写图片描述

***讲讲线程池的实现原理***

***线程池的几种方式***

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

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

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

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

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

***什么是死锁?如何预防和解决死锁问题***

  指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。需要满足四个条件:互斥条件,不剥夺条件,请求和保持条件,循环等待条件。

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


Java虚拟机篇

***Java内存区域***

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

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

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

  运行时常量池:方法区的一部分,存放各种常量和字符引用

  直接内存:NIO中引入了基于通道和缓冲区的IO方式,可以使用Native函数库直接分配对外内存,然后通过一个存储在Java堆中的DirectByteBufer对象作为这块内存的引用进行操作。

***Java中的内存溢出是如何造成的***

  当无用对象不能被垃圾回收器收集的时,我们称之为内存泄露,


***jvm gc如何判断对象是否需要回收,有哪几种方式***

  引用计数:给对象添加一个引用计数其,每有一个地方引用这个对象,计数器值加1,每有一个引用失效则减1。
    优点:实现简单、判断效率高。 缺点:难以解决对象之间的循环引用问题。

  可达性分析算法:从一系列“GC Roots”对象作为起始点,从这些节点向下搜索,搜索走过的路径叫引用链。从GC Roots到该对象不可达,则该对象不可用。
    Java中GC Roots包括以下几种对象:
    a.虚拟机栈(帧栈中的本地变量表)中引用的对象
    b.方法区中静态属性引用的对象
    c.方法区中常量引用的对象
    d.本地方法栈中JNI引用的对象

***gc的概念,如果A和B对象循环引用,是否可以被GC?***

  GC里边在JVM其中是使用的ROOT算法,即可达性分析算法,那么可以被回收。如果是引用计数法,那么将不能被回收

***JVM GC如何回收对象?与其工作原理***

  标记-清除算法:标记所有要回收的对象,标记完成后统一回收被标记的对象。但是会产生大量不连续的内存碎片

  复制算法:将可用内存按容量分为大小相等的两块,每次只使用一块。当一块内存用完了,就把还存货的对象复制到另外一块上,把使用过的内存空间清理掉

  标记-整理算法:标记完后,所有存活对象向一端移动,然后直接清理掉边界以外内存

  分代收集算法:把java堆分为新生代和老年代。新生代,每次垃圾收集都会有大批对象死去,少量存活,采用“复制”算法。老年代对象存活率高,可以使用“标记-清理”,“标记-整理”算法进行回收

***垃圾收集器有哪些?***

  Serial,ParNew,CMS,G1

***ClassLoader的功能和工作模式***

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


锁机制

***解释是一下什么是线程安全?举例说明一个线程不安全的例子。***

  线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

  线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

***volatile ***

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

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

***synchronize***

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

***synchronized 与 lock 的区别***

  1.synchronized是java内置关键字,lock是java类
  2.synchronized无法判断锁的状态,lock可以判断锁的状态
  3.synchronized会自动释放锁,lock锁需要手动释放
  4.synchronized的锁不可中断。lock可中断,可定时,可轮询
  5.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

***读写锁和互斥锁***

  读写锁:允许多个读操作同时进行,但每次只允许一个写操作。

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

***CAS乐观锁和悲观锁,行锁和表锁***

  悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

  乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

  Compare and Swap ( CAS )。CAS是乐观锁技术,其主要步骤是冲突检测和数据更新。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。


设计模式

***设计模式的设计理念***

为了重用代码、让代码更容易被他人理解、保证代码可靠性。

开闭原则:对扩展开放,对修改关闭。

里氏代换原则:实现子父类互相替换,即继承;

依赖倒转原则:针对接口编程,实现开闭原则的基础;

接口隔离原则:降低耦合度,接口单独设计,互相隔离;

迪米特法则,又称不知道原则:功能模块尽量独立;

单一职责原则:一个类只负责一项职责

合成复用原则:尽量使用聚合,组合,而不是继承;

***各模式设计类图和核心原理***

***各模式的区别***

***动态代理和静态代理的区别***


##nio篇 ##

***nio与bio的区别***

IO NIO
面向流(Stream Oriented) 面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
(无) 选择器(Selectors)

传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,

该线程在此期间不 能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会 阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理, 当服务器端需要处理大量客户端时,性能急剧下降。

Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数 据可用时,该线程可以进行其他任务。
线程通常将非阻塞 IO 的空闲时 间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入 和输出通道。

***channel和buffer是什么***

Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。

channel表示打开到 IO 设备(例如:文件、套接字)的连接,类似流,但又有些不同:
通道是双向的,可以向通道读取数据,也可以向通道写数据。但是流的读写是单向。
通道的数据是不负责存储,而是先读取或者写入一个Buffer中,只负责负责缓冲区中数据的传输

***非直接缓冲区和直接缓冲区的区别***

非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率

***selector是什么***

选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。选择器可以监听的事件类型 读 写 连接 接收


数据库篇

***InnoDB和MyISAM的区别 和应用场景***

   MyISAM InnoDB
主外键 不支持 支持
事务 不支持 支持
行表锁 支持表锁 支持行锁
缓存 只缓存索引,不缓存真实数据 不仅缓存索引,而且缓存真实数据,对内存要求较高,内存大小对性能有决定性影响
全文索引 支持 不支持
表空间
关注点 性能 事务

  应用场景:
1).MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。

2).InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。

全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用【分词技术】等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。
全文索引用match+against方式查询:
SELECT * FROM article WHERE MATCH(title,content) AGAINST (‘查询字符串’);
mysql5.6.4以前只有Myisam支持,5.6.4版本以后innodb才支持,但是官方版本不支持中文分词,需要第三方分词插件。
5.7以后官方支持中文分词。


***InnoDB的事务和日志***

  InnoDB有错误日志,查询日志,慢查询日志,二进制日志,事务日志。
错误日志:记录出错信息
查询日志:记录所有对数据库请求的信息
慢查询日志:设置一个阈值,运行时间超过该值的所有SQL语句都会被记录下来。
二进制日志:记录对数据库执行更改的所有操作

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

并发事务可能产生的问题:
  
l 脏读(dirty read):读到未提交更新数据,即读取到了脏数据;
l 不可重复读(unrepeatable read):对同一记录的两次读取不一致,因为另一事务对该记录做了修改;
l 幻读(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录;

  事务4种隔离级别:
串行化:不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;防止脏读,不可重读读,幻读
可重复读:防止脏读和不可重复读,不防幻读
读已提交:防止脏读,不防止不可重复读和幻读
读未提交:可能出现任何事务并发问题

***sql优化 ***

sql优化就是在编写查询SQL语句的过程中尽量用上索引,避免索引失效,避免全表扫描。可以用explain去检测分析SQL语句的执行情况。

索引失效:
全值匹配我最爱,
最佳左前缀法则:如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
存储引擎不能使用索引中范围条件右边的列(范围条件右边的索引将会失效)
尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
is not null 也无法使用索引,但是is null是可以使用索引的
like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描的操作
字符串不加单引号索引失效
少用or,用它来连接时会索引失效

全值匹配我最爱,最左前缀要遵守
带头大哥不能死,中间兄弟不能断
索引列上少计算,范围之后全失效
Like百分写最右,覆盖索引不写*
不等空值还有or,索引失效要少用
Var引号不能丢

***什么是索引?索引的分类有哪些?什么是B-tree?索引的原理?***
	索引是排好序的快速高效查找数据的数据结构.索引往往以索引文件的形式存储的磁盘上

索引分类:簇居索引和非簇居索引。唯一索引和非唯一索引,单值索引和复合索引,主键索引

索引,如果没有特别指明,都是指B-tree。MySQL中的BTree实际上是B+Tree的技术。是一种多路搜索树的数据结构。
这里写图片描述
一颗b树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示)

真实的数据存在于叶子节点,非叶子节点不存储真实的数据,只存储指引搜索方向的数据项。

**BTree和B+Tree的区别**

B+TREE 第二级的 数据并不能直接取出来,只作索引使用。在内存有限的情况下,查询效率高于 B-TREE
B-TREE 第二级可以直接取出来,树形结构比较重,在内存无限大的时候有优势。

思考:为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?

  1. B+树的磁盘读写代价更低
  2. B+树的查询效率更加稳定

**聚簇索引**

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式,表示数据行和相邻的键值的存储在一起。数据行在磁盘的排列和索引排序保持一致。
聚簇索引将数据存入索引叶子页面上。对于 InnoDB 引擎来说,叶子页面不再存该行对应的地址,而是直接存储数据:
这里写图片描述

非聚簇索引的叶子页面上不仅存储了主键id 还存储着 数据存储的地址信息
这里写图片描述

聚簇索引的好处:
按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的io操作。
聚簇索引的限制:
对于mysql数据库目前只有innodb数据引擎支持聚簇索引,而Myisam并不支持聚簇索引。
由于数据物理存储排序方式只能有一种,所以每个Mysql的表只能有一个聚簇索引。一般情况下就是该表的主键。
为了充分利用聚簇索引的聚簇的特性,所以innodb表的主键列尽量选用有序的顺序id,而不建议用无序的id,比如uuid这种。(参考聚簇索引的好处。)

***什么时候应该使用索引,什么时候不应该使用索引***

什么时候应该使用索引?
频繁作为查询条件的字段应该创建索引(where 后面的语句)
查询中与其它表关联的字段,外键关系建立索引
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
查询中统计或者分组字段

什么时候不应该使用索引?
表记录太少
经常增删改的表,因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件
Where条件里用不到的字段不创建索引
数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。

***MySQL 索引使用的注意事项***

虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT,UPDATE和DELETE。因为更新表时,mysql不仅要保存数据,还要保存一下索引文件
建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在要给大表上建了多种组合索引,索引文件会膨胀很宽
避免索引失效,避免全表扫描

***索引优化***

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

***什么是explain?有什么用?***

EXPLAIN关键字可以模拟优化器执行SQL查询语句,知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈

Id:表示查询中执行select子句或操作表的顺序
select_type:查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询
table:显示这一行的数据是关于哪张表的
type:访问类型
system>const>eq_ref>ref>range>index>ALL
system:表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计
const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快,如将主键置于where列表中,MySQL就能将该查询转换为一个常量
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
ref:非唯一性索引扫描,返回匹配某个单独值的所有行.
本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,
它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
Range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引
一般就是在你的where语句中出现了between、<、>、in等的查询
这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
Index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。
(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
All:Full Table Scan,将遍历全表以找到匹配的行
possible_keys:显示可能应用在这张表中的索引,一个或多个。
Key:实际使用的索引。如果为NULL,则没有使用索引。查询中若使用了覆盖索引,则该索引和查询的select字段重叠
key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。key_len字段能够帮你检查是否充分的利用上了索引
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值
rows:列显示MySQL认为它执行查询时必须检查的行数。
Extra:
Using filesort:说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”
Using temporary:使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
USING index:表示相应的select操作中使用了覆盖索引(Covering Index),
如果同时出现using where,表明索引被用来执行索引键值的查找;
如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找。
Using where:表明使用了where过滤

***什么是show profile?***

是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量
诊断SQL,show profile cpu,block io for query n

读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞 lock table 表名字1 read(write),表名字2 read(write),其它;

InnoDB是基于索引来完成行锁
SELECT … LOCK IN SHARE MODE;
select… for update


框架篇

***Spring IOC和DI的理解和区别***

IoC Inverse of Control 反转控制的概念,就是将原本在程序中手动创建UserService对象的控制权,交由Spring框架管理,简单说,就是创建UserService对象控制权被反转到了Spring框架

DI:Dependency Injection 依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件

区别:
IoC 控制反转,指将对象的创建权,反转到Spring容器 , DI 依赖注入,指Spring创建对象的过程中,将对象依赖属性通过配置进行注入

***BeanFactory 接口和 ApplicationContext 接口有什么区别 ?***

①ApplicationContext 接口继承BeanFactory接口,Spring核心工厂是BeanFactory ,BeanFactory采取延迟加载,第一次getBean时才会初始化Bean, ApplicationContext是会在加载配置文件时初始化Bean。
②ApplicationContext是对BeanFactory扩展,它可以进行国际化处理、事件传递和bean自动装配以及各种不同应用层的Context实现
开发中基本都在使用ApplicationContext, web项目使用WebApplicationContext ,很少用到BeanFactory

***spring的生命周期?***

1.对象实例化
2.注入属性
3.如果存在类实现 BeanPostProcessor(后处理Bean) ,执行postProcessBeforeInitialization,
4.初始化方法
5.如果存在类实现 BeanPostProcessor(处理Bean) ,执行postProcessAfterInitialization
6.可以使用
7.销毁方法

***spring的Bean的作用域?***

singleton

当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。

prototype

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

request

在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用 域仅在基于web的Spring ApplicationContext情形下有效。

session

在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

global session

在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于 web的Spring ApplicationContext情形下有效。

***什么是AOP,AOP的作用是什么?***

面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足,除了类(classes)以外,AOP提供了切面。切面对关注点进行模块化,例如横切多个类型和对象的事务管理

***Spring AOP里面的几个名词***

***Spring AOP 通知有哪些类型?***

***SpringMVC与Struts2的主要区别?***

①springmvc的入口是一个servlet即前端控制器,而struts2入口是一个filter过虑器。
②springmvc是基于方法开发,传递参数是通过方法形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。
③Struts采用值栈存储请求和响应的数据,通过OGNL存取数据, springmvc通过参数解析器是将request对象内容进行解析成方法形参,将响应数据和页面封装成ModelAndView对象,最后又将模型数据通过request对象传输到页面。 Jsp视图解析器默认使用jstl。

***SpringMVC的工作流程***

这里写图片描述

.

MyBatis与Hibernate有哪些不同?

Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。

Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。

Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的缺点是学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

***使用MyBatis的mapper接口调用时有哪些要求?***

① Mapper接口方法名和mapper.xml中定义的每个sql的id相同
② Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
③ Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
④ Mapper.xml文件中的namespace即是mapper接口的类路径。


redis篇

***redis的数据结构***

string,list,hash,set,zset

***redis如何持久化***

RDB和AOF
RDB:
RDB即redis database。redis会在从配置文件中(redis.conf)读取数据,然后按照配置文件中指定的时间间隔,指定的触发条件,然后将内存中的数据集快照写入磁盘,即为snapshot快照。恢复时将快照文件直接读取到内存中。

运行流程:
Redis会单独创建(fork)一个子进程来进行持久化,会先将内存中的所有数据写入到 一个临时文件中。
整个过程中,主进程是不进行任何IO操作的,继续处理client请求。
待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。

RDB持久化机制每次快照持久化都是将内存数据完整写入到磁盘一次,完全覆盖

aof即Append Only File。相关配置在redis的Append Only File模块中有。AOF持久化机制是以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件!!

追加操作,与RDB全部数据重新覆盖不同
运行流程:
redis启动之初会读取日志文件中的写命令在内存中重新构造数据。换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。所以这也是AOF在数据恢复上不如RDB快的原因

***redis为什么是单线程的?***

因为redis是完全基于内存的,避免线程的上下文切换和消耗CPU,也不用去考虑锁的问题,所以单线程比多线程更加合适。限制redis速度的会是内存和网络,而不是CPU

***redis的过期策略以及内存淘汰机制***

redis采用的是定期删除+惰性删除策略解决数据过期。
定期删除,redis默认每个100ms检查,随机抽取进行检查,是否有过期的key,有过期key则删除。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
惰性删除,就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

redis.conf中有一行配置,该配置就是配内存淘汰策略的

maxmemory-policy volatile-lru

1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。
2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。
3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。
4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐
5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐
6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。

***redis和数据库双写一致性问题***

双写的一致性,只能保证最终一致性,如果对数据有强一致性需求,不能放缓存。

更新策略:
先更新数据库,再删缓存

***缓存降级,缓存穿透,缓存雪崩,缓存预热,缓存更新问题***

缓存穿透:用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,请求就这样绕过缓存直接查数据库。导致所有的请求都怼到数据库上,从而数据库连接异常。

缓存雪崩:同一时刻出现大面积的缓存过期,新缓存未到期间,所有原本应该访问缓存的请求都去查询数据库了,从而导致数据库连接异常。

缓存预热:系统上线后,将相关的缓存数据直接加载到缓存系统。

缓存更新:除了缓存服务器自带的缓存失效策略之外(Redis默认的有6种内存淘汰策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰。即定期删除和惰性删除

缓存降级:当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

解决方案:
缓存穿透:
1.穿透现象是由于缓存没有用户需要的数据,那么查到缓存数据为空,仍然把这个空结果放入缓存,设置较短的过期时间。第二次访问缓存就有值了,就不会访问数据库了。当这个缓存过期了,异步更新缓存。

缓存雪崩:
1.在原本的缓存失效时间,加上一个随机值,避免集体失效;
2.双缓存,设置缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设置失效时间。两个缓存都做缓存预热操作。从缓存A读数据,有则直接返回。A没有数据,从B读数据,直接返回,并且启动一个异步更新线程,线程同时更新缓存A和缓存B

缓存预热:
1.写一个缓存刷新页面,上线手动操作
2.数据量不大,项目启动时自动进行加载
3.定时刷新缓存

缓存降级:
1降级的最终目的是保证核心服务可用。可以参考日志级别设置预案,以一般,警告,错误,严重错误等等级别。Redis出现问题,不去数据库查询,而是直接返回默认值给用户。


消息队列篇

***消息队列的使用场景***

解耦、异步、削峰
解耦:传统模式系统间耦合性太强,新增系统接入,需要修改代码。将消息写入消息队列,需要消息的系统自己从消息队列中订阅,从而系统A不需要做任何修改。

异步:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

***消息的幂等性解决思路***

造成重复消费原因其实都是类似的,在于回馈机制。正常情况下,消费者在消费消息时候,消费完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。

1.建立一个消息表,拿到这个消息做数据库的insert操作。给这个消息做一个唯一主键(primary key)或者唯一约束,那么就算出现重复消费的情况,就会导致主键冲突。
2.准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将 < id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。

***消息的重发补偿解决思路***

Rocketmq提供了消息重试机制

***如何保证消息的有序性***

RocketMQ采用局部顺序一致性的机制,实现了单个队列中消息的有序性,使用FIFO顺序提供有序消息。把一组消息存放在同一个队列,然后由消息进行逐一消费。
全局顺序时使用一个queue,局部顺序时多个queue并行消费

***自己如何实现消息队列***

这里写图片描述

①生产者生产消息,并发送消息到消息队列Broker
②消息队列Broker将消息进行本地IO存储到本地磁盘中
③消息队列Broker返回确认通知ACK包给生产者(在RocketMQ中,即返回CONSUME_SUCCESS成功标志)

以上是一种TCP短连接,即方块1的部分。RocketMQ采用Netty作为底层,是一种请求/响应的方式的连接

至于方块2,可以是短连接,也可以是长连接。
长连接的话是最常用的,即RocketMQ的Push Consumer,JMS规范中最主要的就是这种
短连接即Pull Consumer方式,采用一请求一响应的方式从本地磁盘中定时拉取消息进行消费

④ Push :消息队列Broker将信息推送给消费者进行消费,因为他们之间一直保持长连接状态,所以即时。

④ Pull:消费者定时从本地磁盘中查看是否未消费的消息。如果有,将其拉回来进行消费。保持短连接,请求响应形式。

发布了58 篇原创文章 · 获赞 8 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_32020035/article/details/82685879