首页 > 技术文章 > 面试题收集--持续整理

dw89 2018-03-01 17:16 原文

一、hashcode相等的两个类一定相等吗?equals呢?反过来呢?
答:hashcode效率高,但是并不一定可靠,有时候不同的对象他们生成的hashcode也会一样。
  1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
  2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),
如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了




二、hashmap与hashtable区别?hashmap的底层实现原理?hash碰撞如何解决?
1、hashmap与hashtable区别
(1) 继承的父类不同。Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
(2) 线程安全性不同。Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。
(3) HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。
,Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
(4)Hashtable中,key和value都不允许出现null值;HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
(5)两个遍历方式的内部实现上不同。 Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
(6)hash值不同。 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

三、hashmap的实现原理?
 hashmap的组成是数组加链表形式,hashmap一般是初始化一个大小是16的Entry数组,数组存储了Entry类的对象。HashMap类有一个叫做Entry的内部类。这个Entry类包含了key-value作为实例变量。
每当往hashmap里面存放key-value对的时候,都会为它们实例化一个Entry对象,这个Entry对象就会存储在前面提到的Entry数组table中,而这个实例化的Entry对象的存放位置是由实例化对象key的hashcode()方法计算出来的hash值(来决定)。
hash值用来计算key在Entry数组的索引。
 当从hashmap中取值的时候,原理相同,你传递一个key从hashmap获取value的时候:
1、对key进行null检查。如果key是null,table[0]这个位置的元素将被返回。
2、key的hashcode()方法被调用,然后计算hash值。
3、indexFor(hash,table.length)用来计算要获取的Entry对象在table数组中的精确的位置,使用刚才计算的hash值。
4、在获取了table数组的索引之后,会迭代链表,调用equals()方法检查key的相等性,如果equals()方法返回true,get方法返回Entry对象的value,否则,返回null。

3、hash碰撞
如果存储对象的hashcode相同,就会发生碰撞,这时hashmap将会在碰撞索引上延伸链表形式来存储对象。
通过迭代链表的形式进行存储,大致如下:

如果在刚才计算出来的索引位置没有元素,直接把Entry对象放在那个索引上。
如果索引上有元素,然后会发生碰撞,进行迭代一直到Entry->next是null。当前的Entry对象变成链表的下一个节点。
如果我们再次放入同样的key会怎样呢?逻辑上,它应该替换老的value。事实上,它确实是这么做的。在迭代的过程中,会调用equals()方法来检查key的相等性(key.equals(k)),如果这个方法返回true,它就会用当前Entry的value来替换之前的value。

四、hashmap和treemap的区别?底层数据结构是什么?
两者都是线程非安全,key不可以重复,value允许重复,
HashMap:数组方式存储key/value,允许null作为key和value,不保证元素迭代顺序是按照插入时的顺序。
TreeMap:基于红黑二叉树的NavigableMap的实现,不允许null
TreeMap的结果是按照字母表的顺序进行存储的,迭代输出的时候就按排序顺序输出,HashMap则没有
HashMap是根据键的HashCode值存储数据,取得数据的顺序是完全随机的,HashMap取值的速度更快。
存入TreeMap的元素应当实现Comparable接口或者实现Comparator接口,会按照排序后的顺序迭代元素,存入元素的时候对元素进行自动排序,迭代输出的时候就按排序顺序输出
 
五、线程池的参数?底层实现?
线程池的实现ThreadPoolExecutor接口,设置好线程池的参数能极大提高效率。
1、corePoolSize 作用:默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
2、maxPoolSize 线程池最大线程数,它表示在线程池中最多能创建多少个线程;
 当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。
如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。
3、keepAliveTime
表示线程没有任务执行时最多保持多久时间会终止。
4、unit:参数    keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
5、threadFactory:线程工厂,主要用来创建线程;
6、workQueue
一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响
一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
PriorityBlockingQueue
7、handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

线程安全如何理解?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。
如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,
线程安全问题都是由全局变量及静态变量引起的。


cocurrentHashmap的特点?
因为:
HashMap缺点:
在并发编程过程中使用可能导致死循环,因为插入过程不是原子操作,
每个HashEntry是一个链表节点,很可能在插入的过程中,已经设置了后节点,实际还未插入,
最终反而插入在后节点之后,造成链中出现环,破坏了链表的性质,失去了尾节点,出现死循环。

HashTable缺点:
因为内部是采用synchronized来保证线程安全的,
但在线程竞争激烈的情况下HashTable的效率下降得很快因为synchronized关键字会造成代码块或方法成为为临界区(对同一个对象加互斥锁),
当一个线程访问临界区的代码时,其他线程也访问同一临界区时,
会进入阻塞或轮询状态。究其原因,实际上是有获取锁意向的线程的数目增加,
但是锁还是只有单个,导致大量的线程处于轮询或阻塞,
导致同一时间段有效执行的线程的增量远不及线程总体增量。

CocurrentHashMap是由Segment数组和HashEntry数组组成。

Segment是重入锁(ReentrantLock),作为一个数据段竞争锁,每个HashEntry一个链表结构的元素,
利用Hash算法得到索引确定归属的数据段,也就是对应到在修改时需要竞争获取的锁。

CocurrentHashMap利用锁分段技术增加了锁的数目,从而使争夺同一把锁的线程的数目得到控制。
锁分段技术就是对数据集进行分段,每段竞争一把锁,不同数据段的数据不存在锁竞争,
从而有效提高 高并发访问效率。CocurrentHashMap在get方法是无需加锁的,因为用到的共享变量都采用volatile关键字修饰,
保证共享变量在线程之间的可见性(每次读取都先同步缓存和内存,直接从内存中获取值,虽然不是原子操作,
但根据JAVA内存模型的happen before原则,对volatile字段的写入操作先于读操作,
能够保证不会脏读),volatile为了让变量提供线程之间的内存可见性,会禁止程序执行结果的重排序(导致缓存优化的效果降低)


CocurrentHashMap的操作
Segment的get操作是不需要加锁的。因为volatile修饰的变量保证了线程之间的可见性
Segment的put操作是需要加锁的,在插入时会先判断Segment里的HashEntry数组是否会超过容量(threshold),如果超过需要对数组扩容,翻一倍。然后在新的数组中重新hash,
为了高效,CocurrentHashMap只会对需要扩容的单个Segment进行扩容
CocurrentHashMap获取size的时候要统计Segments中的HashEntry的和,
如果不对他们都加锁的话,无法避免数据的修改造成的错误,但是如果都加锁的话,效率又很低。
所以CoccurentHashMap在实现的时候,巧妙地利用了在累加过程中发生变化的几率很小的客观条件,
在获取count时,不加锁的计算两次,如果两次不相同,在采用加锁的计算方法。
采用了一个高效率的剪枝防止很大概率地减少了不必要额加锁。



线程池的底层实现原理:
  刚开始都是在创建新的线程,达到核心线程数量5个后,新的任务进来后不再创建新的线程,而是将任务加入工作队列,
  任务队列到达上线5个后,新的任务又会创建新的普通线程,直到达到线程池最大的线程数量10个,后面的任务则根据配置的饱和策略来处理。
  我们这里没有具体配置,使用的是默认的配置AbortPolicy:直接抛出异常。
  当然,为了达到我需要的效果,上述线程处理的任务都是利用休眠导致线程没有释放!!!

六、synchronized和lock什么区别?
 synchronized缺点:
1)不能响应中断;
2)同一时刻不管是读还是写都只能有一个线程对共享资源操作,其他线程只能等待
3)锁的释放由虚拟机来完成,不用人工干预,不过此即使缺点也是优点,优点是不用担心会造成死锁,缺点是由可能获取到锁的线程阻塞之后其他线程会一直等待,性能不高。

而lock接口的提出就是为了完善synchronized的不完美的,首先lock是基于jdk层面实现的接口,和虚拟机层面不是一个概念;
其次对于lock对象中的多个方法的调用,可以灵活控制对共享资源变量的操作,不管是读操作还是写操作;

ReentrentLock对象和ReentrentReadWriteLock为我们日常开发中见到和用到比较多的两个类;
他们都是可重入的锁,即当同一线程获取到锁之后,他在不释放锁的情况下,可以再次获取到当前已经拿到的锁,只需标记获取到锁的次数加一即可;

那么lock和synchronized的区别对比如下:

1)synchronized 在成功完成功能或者抛出异常时,虚拟机会自动释放线程占有的锁;
而Lock对象在发生异常时,如果没有主动调用unLock()方法去释放锁,则锁对象会一直持有,因此使用Lock时需要在finally块中释放锁;
2)lock接口锁可以通过多种方法来尝试获取锁包括立即返回是否成功的tryLock(),以及一直尝试获取的lock()方法和尝试等待指定时间长度获取的方法,相对灵活了许多比synchronized;
3) 通过在读多,写少的高并发情况下,我们用ReentrantReadWriteLock分别获取读锁和写锁来提高系统的性能,因为读锁是共享锁

七、ThreadLocal是什么?底层如何实现?
 ThreadLocal设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。
ThreadLocal 就是把变量分成很多个拷贝,每个线程拥有一个。
这里没有所谓的最后的结果,每个线程单独操作自己的变量,和其他的变量没关系。
你就理解成都是各干各的,如果说真要用到跟最终结果有关系,还是老老实实用synchronized

八、volitile的工作原理
http://blog.csdn.net/ChaseRaod/article/details/77924862
1.volatile关键字的两层语义
  一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
  1)保证了不同线程对这个变量进行操作时的可见性,能同步更新到主存,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2)指令重排序会影响到多线程执行的正确性,那么我们就需要禁止重排序,使用该关键字能禁止进行指令重排序。

使用volatile可能导致的问题?如何解决?
举个简单的例子,比如下面的这段代码:
i = i + 1;
当线程执行这个语句时,会先从主存当中读取i的值,然后复制一份到高速缓存当中,然后CPU执行指令对i进行加1操作,然后将数据写入高速缓存,最后将高速缓存中i最新的值刷新到主存当中。
  这个代码在单线程中运行是没有任何问题的,但是在多线程中运行就会有问题了。在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存(对单核CPU来说,其实也会出现这种问题,只不过是以线程调度的形式来分别执行的)。本文我们以多核CPU为例。
  比如同时有2个线程执行这段代码,假如初始时i的值为0,那么我们希望两个线程执行完之后i的值变为2。但是事实会是这样吗?
可能存在下面一种情况:初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。
  最终结果i的值是1,而不是2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。


 也就是说,如果一个变量在多个CPU中都存在缓存(一般在多线程编程时才会出现),那么就可能存在缓存不一致的问题。
  为了解决缓存不一致性问题,通常来说有以下2种解决方法:
  1)通过在总线加LOCK#锁的方式,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),
       从而使得只能有一个CPU能使用这个变量的内存。
  2)通过缓存一致性协议
       最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。




九、JVM的内存模型、用过什么垃圾回收器?
1、内存模型
虚拟机栈:是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
Java堆:是被线程共享的一块内存区域,创建的对象和数组都保存在Java堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。
方法区:是线程共享的内存区域,用来存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
程序计数器:是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

2、垃圾收集面试题
http://www.importnew.com/19085.html
如何确定某个对象是“垃圾”?
在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行
如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,不可能被访问,那么这个对象就成为可被回收的对象了

典型的垃圾收集算法
在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收
1.Mark-Sweep(标记-清除)算法
 最容易最简单的算法,顾名思义,标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
 缺点:容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。

2、Copying(复制)算法
 解决标记清楚算法的缺陷,不容易出现内存碎片的问题
缺点:会牺牲内存为代价,能够使用的内存缩减到原来的一半

3、Mark-Compact(标记-整理)算法
 解决Copying算法的缺陷,充分利用内存空间。
完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存

4、Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。
它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。

垃圾收集器分类?
1、Serial收集器

 Serial收集器是一个单线程的收集器,采用复制算法,用于新生代的垃圾收集。
 它的优点是实现简单高效,但是缺点是会给用户带来停顿。

2、ParNew收集器

ParNew是Serial收集器的多线程版本,采用复制算法,用于新生代的垃圾收集。
特点:适用于多核cpu环境,在单核情况下性能往往不如Serial收集器,除了Serial收集器,目前只有它能和CMS收集器配合工作

3、Parallel Scavenge收集器

Parallel Scavenge收集器是新生代收集器,采用复制算法,主要是为了达到一个可控制的吞吐量(Throughput)。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。

 适用场景:后台运算而不需要太多交互的任务。
虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量
4、Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,用于老年代的垃圾收集。
 使用场景:
 1)Client模式。
 2)Server端,JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用。
 3)Server端,CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

5、Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,用于老年代的垃圾收集。

6、CMS收集器

CMS收集器(Concurrent Mark Sweep)是以获取最短回收停顿时间为目标的收集器。是基于标记-清除算法实现的。
CMS收集器的内存回收过程是与用户线程一起并发执行的,是hotspot虚拟机中第一款真正意义上的并发收集器,简单来说就是你可以一边制造垃圾,收集器在不打断你的情况下进行收集
以给用户带来较好的用户体验,

  优点:并发收集、低停顿。
  缺点:1、CPU资源敏感。
        2、无法处理浮动垃圾(Floating Garbage),即无法收集并发运行中产生的新的垃圾。
        3、容易产生空间碎片。

7、G1收集器

G1收集器(Garbage-First)将堆内存(新生代和老年代)划分成多个大小相等的独立区域(Region),根据各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的时间的经验值),优先回收价值最大的Region。

十、类的加载机制,都有哪些类的加载器,这些加载器都加载了什么文件?
1、什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

2、类的生命周期
1)、加载:查找并加载类的二进制数据
2)、验证:确保被加载的类的正确性
3)、准备:为类的静态变量分配内存,并将其初始化为默认值
4)、解析:把类中的符号引用转换为直接引用
5)、初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化

3、类加载器
1)、启动类加载器
2)、扩展类加载器
3)、应用程序类加载器


4、JVM类加载机制
1)、全盘负责
2)、父类委托
3)、缓存机制

5、类加载方式
1)、命令行启动应用时候由JVM初始化加载
2)、通过Class.forName()方法动态加载
3)、通过ClassLoader.loadClass()方法动态加载

十一、spring原理机制?java的反射是什么?aop是什么?
http://blog.csdn.net/nrain2/article/details/45459311
1、spring是什么,能做什么?
1)、Spring的目的:
让对象与对象 或者 模块与模块之间的关系没有通过代码来关联,都是通过配置类说明管理的,
spring根据这些配置,内部通过反射去动态的组装对象。

2)、首先spring是一个容器,只有在容器里的对象才会有spring所提供的服务和功能。

spring最主要有两个核心,一个是IOC(inversion of control),另一个是AOP(aspect oriented programming)。

先说一下IOC吧,IOC就是控制反转也可以叫做依赖注入。
控制权由本身转向容器,由容器根据配置文件去创建实例并创建各个实例之间的依赖关系,
动态注入,让对象的创建不用去new了,可以自动生产,其实这里边用的就是java的反射机制,通过反射在运行时动态的去创建、调用对象。

2、spring初始化
初始化过程做了哪些事呢?
http://blog.csdn.net/yangliu19920502/article/details/68945674
1)、ClassPathXmlApplicationContext为入口构造方法中有个refresh()方法用来初始化Spring
2)、解析XML并且初始化工厂类
3)、执行工厂后置处理器
4)、注册后置处理器
5)、默认注册一个bean名称为messageSource的bean用于国际化处理
6)、Spring事件体系包括三个组件:事件,事件监听器,事件广播器
7)、初始化一些特殊的bean,一般很少用
8)、初始化单例的bean,实例化BeanFactory中所有的bean
9)、发布上下文刷新事件,事件广播器负责将些事件广播到每个注册的事件监听器中,容器启动完成


3、java的反射是什么?
参考:http://blog.csdn.net/sinat_38259539/article/details/71799078
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.



4、aop是什么?它是怎么实现的?
http://blog.csdn.net/zhangliangzi/article/details/51648032
aop是什么?
这种在运行时,动态地将代码切入到类的指定方法、 指定位置上的编程思想就是面向切面的编程。
http://blog.csdn.net/csujiangyu/article/details/53455094
(1)aop实现的几种方式?
1)静态代理:
 简单说:在编译期,切面直接以字节码的形式编译到目标字节 码文件中
 AspectJ属于静态AOP,是在编译时进行增强,会在编译的时候将AOP逻辑织入到代码中,需要专有的编译器和织入器。
优点:被织入的类性能不受影响。
缺点:不够灵活

2)JDK动态代理:
简单说:在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。
实现原理是为被代理的业务接口生成代理类,将AOP逻辑写入到代理类中,在运行时动态织入AOP,使用反射执行织入的逻辑。
主要实现方式依赖java.lang.reflect包下的InvocationHandler和Proxy类。

3)动态代码字节生成
动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。
CGLib是动态代码字节生成的实现,它封装字节码生成工具Asm,原理是在运行期间目标字节码加载后,生成目标类的子类,
将切面逻辑加入到子类中,所以使用Cglib实现AOP不需要基于接口。


4)自定义类加载器
在运行前,目标加载前,将切面逻辑加到目标字节码中。
可以考虑javassist来实现。Javassist 是一个编辑字节码的框架,可以让你很简单地操作字节码。
它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。


十二、javaWeb 一次URL的请求过程
1.用户点击客户端页面即点击URL,URL含有域名和URI
2.域名解析成IP地址:
〇浏览器缓存→系统缓存→路由器缓存→ISP DNS缓存→从根域名服务器递归搜索
3.Tomcat服务器得到请求,根据URI找到Servlet(如果是静态资源走DefaultServlet,如果是jsp,会先生成jsp.java ,_jsp.classes走web.xml查找Servlet)
4.Tomcat服务器将请求解析成ServletRequest
5.Servlet根据ServletRequest解析参数,根据ServletResponse返回相应(Servlet中处理可能涉及数据库)
6.Tomcat服务器包装该响应,以Http响应的形式发送给Web浏览器
7.前台页面得到返回,展示出来
 

十三、springMVC 的工作原理和机制
https://www.cnblogs.com/baiduligang/p/4247164.html

1)、工作原理
1、客户端发出一个http请求给web服务器,web服务器对http请求进行解析,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),web容器将请求转交给DispatcherServlet.

2、DipatcherServlet接收到这个请求之后将根据请求的信息(包括URL、Http方法、请求报文头和请求参数Cookie等)以及HandlerMapping的配置找到处理请求的处理器(Handler)。

3-4、DispatcherServlet根据HandlerMapping找到对应的Handler,将处理权交给Handler(Handler将具体的处理进行封装),再由具体的HandlerAdapter对Handler进行具体的调用。

5、Handler对数据处理完成以后将返回一个ModelAndView()对象给DispatcherServlet。

6、Handler返回的ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet通过ViewResolver将逻辑视图转化为真正的视图View。

7、Dispatcher通过model解析出ModelAndView()中的参数进行解析最终展现出完整的view并返回给客户端。


十四、mysql建立索引有哪些原则?索引什么数据结构?B+tree和Btree什么区别?
1、唯一性。唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。例如,学生表中学号是具有唯一性的字段
2、为经常需要排序、分组和联合操作的字段建立索引。经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段
3、为常作为查询条件的字段建立索引。如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度
4、索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大,越多的索引,会使更新表变得很浪费时间
5、尽量使用数据量少的索引。对一个CHAR(100)类型的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多
6、尽量使用前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
7、删除不再使用或者很少使用的索引。表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
8、 最左前缀匹配原则,非常重要的原则
9、尽量选择区分度高的列作为索引。
10、索引列不能参与计算,保持列“干净”
11、尽量的扩展索引,不要新建索引。



索引的数据结构是平衡树
http://blog.csdn.net/define_danmu_primer/article/details/70757784

MyISAM使用B-Tree实现主键索引、唯一索引和非主键索引。
InnoDB中非主键索引使用的是B-Tree数据结构,而主键索引使用的是B+Tree。


索引为什么选用B树这种数据结构?
因为使用B树查找时,所用的磁盘IO操作次数比平衡二叉树更少,效率也更高。
为什么使用B树查找所用的磁盘IO操作次数比平衡二叉树更少?
大规模数据存储中,树节点存储的元素数量是有限的(如果元素数量非常多的话,查找就退化成节点内部的线性查找了),
这样导致二叉查找树结构由于树的高度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下。
那么我们就需要减少树的高度以提高查找效率。而平衡多路查找树结构B树就满足这样的要求。
B树的各种操作能使B树保持较低的高度,从而达到有效减少磁盘IO操作次数。

十五、mysql有哪些存储引擎?各自的特点是什么?
1、MyISAM存储引擎
不支持事务、也不支持外键,优势是访问速度快,对事务完整性没有要求或者以select,insert为主的应用基本上可以用这个引擎来创建表
支持3种不同的存储格式,分别是:静态表;动态表;压缩表
2、InnoDB存储引擎*
该存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比MyISAM引擎,写的处理效率会差一些,并且会占用更多的磁盘空间以保留数据和索引。
InnoDB存储引擎的特点:支持自动增长列,支持外键约束
3、MEMORY存储引擎
memory类型的表访问非常的快,因为它的数据是放在内存中的,并且默认使用HASH索引,但是一旦服务关闭,表中的数据就会丢失掉。
Memory类型的存储引擎主要用于哪些内容变化不频繁的代码表,或者作为统计操作的中间结果表,便于高效地对中间结果进行分析并得到最终的统计结果。
对存储引擎为memory的表进行更新操作要谨慎,因为数据并没有实际写入到磁盘中,所以一定要对下次重新启动服务后如何获得这些修改后的数据有所考虑。
4、MERGE存储引擎
Merge存储引擎是一组MyISAM表的组合,这些MyISAM表必须结构完全相同,merge表本身并没有数据,对merge类型的表可以进行查询,更新,删除操作,这些操作实际上是对内部的MyISAM表进行的。

十六:集群服务器中如何保持数据的一致性?
我们引入了消息队列进行异步数据同步,来实现数据的最终一致性。当然消息队列的各种异常也会造成数据不一致,所以我们又引入了实时监控服务,实时计算两个集群的数据差异,并进行一致性同步。

十七、高并发系统数据库层面该怎么设计?数据库锁有哪些类型?如何实现?


数据库锁大的层面来说可以分为乐观锁和悲观锁。可以看看
https://www.cnblogs.com/ismallboy/p/5574006.html
并发控制一般采用三种方法,分别是乐观锁和悲观锁以及时间戳
时间戳就是在数据库表中单独加一列时间戳,比如“TimeStamp”,每次读出来的时候,把该字段也读出来,当写回去的时候,把该字段加1,提交之前 ,跟数据库的该字段比较一次,如果比数据库的值大的话,就允许保存,否则不允许保存,
这种处理方法虽然不使用数据库系统提供的锁机制,
但是这种方法可以大大提高数据库处理的并发量,因为这种方法可以避免了长事务中的数据库加锁开销(操作员A 和操作员B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系 统整体性能表现。

以上悲观锁所说的加“锁”,其实分为几种锁,分别是:排它锁和共享锁,其中排它锁又称为写锁,共享锁又称为读锁。
共享锁和排它锁是具体的锁,是数据库机制上的锁

行级锁是一种排他锁,防止其他事务修改此行;在使用以下语句时,Oracle会自动应用行级锁:
INSERT、UPDATE、DELETE、SELECT … FOR UPDATE [OF columns] [WAIT n | NOWAIT];
SELECT … FOR UPDATE语句允许用户一次锁定多条记录进行更新
使用COMMIT或ROLLBACK语句释放锁

表级锁又分为5类:

行共享 (ROW SHARE) – 禁止排他锁定表
行排他(ROW EXCLUSIVE) – 禁止使用排他锁和共享锁
共享锁(SHARE) - 锁定表,对记录只读不写,多个用户可以同时在同一个表上应用此锁
共享行排他(SHARE ROW EXCLUSIVE) – 比共享锁更多的限制,禁止使用共享锁及更高的锁
排他(EXCLUSIVE) – 限制最强的表锁,仅允许其他用户查询该表的行。禁止修改和锁定表。


死锁:
就是我等你,你又等我,双方就会一直等待下去,比如:T1封锁了数据R1,正请求对R2封锁,而T2封住了R2,正请求封锁R1,
这样就会导致死锁,死锁这种没有完全解决的方法,只能尽量预防,预防的方法有:①一次封锁发,指的是一次性把所需要的数据全部封锁住,但是这样会扩大了封锁的范围,降低系统的并发度;②顺序封锁发,指的是事先对数据对象指定一个封锁顺序,要对数据进行封锁,只能按照规定的顺序来封锁,但是这个一般不大可能的。

十八、事物的特性
要想成为事务,必须满足:ACID(原子性,一致性,隔离性,持久性)四特性,事务是恢复和并发控制的基本单位。
原子性指的是事务是数据库的逻辑工作单位,事务中操作要么都做,要么都不做;
一致性指的是事务的执行结果必须是使数据库从一个一致性状态变大另一个一致性状态,一致性和原子性是密切相关的;
隔离性指的是一个事务执行不能被其他事务干扰;
持久性指的是一个事务一旦提交,他对数据库中数据的改变就是永久性的。

十九、redis
特点:
1、Key-Value类型的内存数据库,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存
2、支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据


3.使用redis有哪些好处?   
(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
(2) 支持丰富数据类型,支持string,list,set,sortedset,hash
(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

4.redis相比memcached有哪些优势?   
(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
(2) redis的速度比memcached快很多
(3) redis可以持久化其数据

5.Memcache与Redis的区别都有哪些?    
1)、存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,这样能保证数据的持久性。
2)、数据支持类型 Memcache对数据类型支持相对简单。 Redis有复杂的数据类型。
3)、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4)、Redis是单进程单线程的、memcahe是单进程多线程的

为什么单线程的redis比多线程的memched要快?
1、因为它是单进程单线程,使用这个的好处是:
   代码更清晰,处理逻辑更简单
   不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗
   不存在多进程或者多线程导致的切换而消耗CPU
2、其次,使用多路 I/O 复用模型
  (1) 采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求
  (2) 且Redis在内存中操作数据的速度非常快

多路 I/O 复用模型是利用select、poll、epoll可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,
就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作

 其他一些优秀的开源软件采用的模型
    多进程单线程模型:Nginx
    单进程多线程模型:Memcached
    单进程单线程:redis

14.redis持久化的几种方式

1、快照(snapshots)
  缺省情况情况下,Redis把数据快照存放在磁盘上的二进制文件中,文件名为dump.rdb。你可以配置Redis的持久化策略,例如数据集中每N秒钟有超过M次更新,就将数据写入磁盘;或者你可以手工调用命令SAVE或BGSAVE。
  工作原理
  . Redis forks.
  . 子进程开始将数据写到临时RDB文件中。
  . 当子进程完成写RDB文件,用新文件替换老文件。
  . 这种方式可以使Redis使用copy-on-write技术。
2、AOF
  快照模式并不十分健壮,当系统停止,或者无意中Redis被kill掉,最后写入Redis的数据就会丢失。这对某些应用也许不是大问题,但对于要求高可靠性的应用来说,
  Redis就不是一个合适的选择。
  Append-only文件模式是另一种选择。
  你可以在配置文件中打开AOF模式
3、虚拟内存方式
  当你的key很小而value很大时,使用VM的效果会比较好.因为这样节约的内存比较大.
  当你的key不小时,可以考虑使用一些非常方法将很大的key变成很大的value,比如你可以考虑将key,value组合成一个新的value.
  vm-max-threads这个参数,可以设置访问swap文件的线程数,设置最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的.可能会造成比较长时间的延迟,但是对数据完整性有很好的保证.

  自己测试的时候发现用虚拟内存性能也不错。如果数据量很大,可以考虑分布式或者其他数据库

15、http工作流程?
1.浏览器根据URL中的域名,通过DNS解析出目标网页的IP地址;
2、浏览器与网页所在服务器建立TCP连接;
3、浏览器发送HTTP请求报文,获取目标网页的文件;
4、服务器发送HTTP响应报文,将目标网页文件发送给浏览器;
5、释放TCP连接;
6、浏览器将网页的内容包括文本、图像、声音等显示呈现在用户计算机屏幕

16、

推荐阅读