zoukankan      html  css  js  c++  java
  • java面试题(上)

     
    1:  自我介绍
      自我介绍首先描述自己的基本情况,其次是描述自己的技术亮点,做过的亮点项目或产品。如果没有做过有技术亮点的事,每天都在做增删改查功能或重复性的工作,需要好好反思下,这样下去技术上没有多少增长。如果工作中就是做这个怎么办?可以考虑利用业余时间参与开源项目或自己做一些工具或框架。
    2:  HashMap怎么解决Hash冲突的
      1、HashMap的数据结构是:数组Node[](桶)与链表Node中有next Node.(继承map.entity:hashcode,key,value,next)
      2、在put元素时,先根据key的hashCode重新计算出hash值,然后再根据hash值找出这个元素在数组中的位置,如果数组的此处已经存放了数据,那么这个元素会以链表的形式保存,新的在链头,旧的在链尾(通过hash,key对比来判别两个节点是否相同);
      3、如果数组的此处没有存放数据,则直接将该元素保存在数组的此位置上。
      4、大于增长因子或一个链表超过8时,通过resize扩充一倍容量,前者是为了扩充容量,后者是为了将链表上的值通过hash取模放到别的桶上去。
      5、当列表长度超过8时则将桶转为红黑树(hashmap:当链表的长度超过8且桶的大小超过64=4*16)。
      ps:
        二叉树:
          1、每个节点最多只有两颗子树,即节点的度最大为2
          2、左子树和右子树是有顺序的,次序不能颠倒
          3、即使某个节点只有一个子树,也要区分左右子树
        二叉查询树(BST):
          1、节点的左子树所包含的节点的值都小于该节点的值。
          2、节点的右子树所包含的节点的值都大于等于该节点的值。
          3、左子树和右子树也都是二叉查询树
          4、优点:查询方便;缺点:以便会变瘸,如7-》6-》5-》4
        平衡二叉树:
          1、任何节点的左右子树的高度之差的绝对值最多是1
        红黑树:一种近似平衡二叉查找树,在进行插入和删除时通过特定操作保持二叉树的平衡,从而获得更高的查找性能。
          1、节点是红色或者黑色
          2、跟节点是黑色的
          3、每个叶子节点都是黑色的空节点
          4、每个红色节点的两个子节点都是黑色(根到叶子,不存在两个连续的红色节点)
          5、从任一节点到该节点的每个叶子节点的所有路径都包含相同数目的黑色节点
          6、通过着色和旋转(左旋/右旋)修正更新后的红黑树,插入的节点一定是红色的。
            a、如果父类是黑色则不影响
            b、如果父类是红色则要进行旋转并修改父类的颜色为黑色

     

        
    3:  ConcurrentHashMap怎么解决线程安全
      1、Segment分段锁功能,每一个Segment 都想的于小的hash table并且都有自己锁,只要修改不再同一个段上就不会引起并发问题。
      2、Segment继承了ReentrantLock,会根据hash获取指定的分段锁。
      3、1.8的分段实现是直接将锁加在段中第一个元素上。
     
    4:  常见的排序有没有了解过
    排序方法        平均情况        最好情况        最坏情况        辅助空间        稳定性
    冒泡排序         O(n^2)           O(n)              O(n^2)            O(1)                稳定
    选择排序         O(n^2)          O(n^2)            O(n^2)            O(1)              不稳定
    插入排序         O(n^2)           O(n)              O(n^2)            O(1)                稳定
    希尔排序O(n*log(n))~O(n^2) O(n^1.3)       O(n^2)            O(1)              不稳定
    堆排序          O(n*log(n))     O(n*log(n))    O(n*log(n))       O(1)              不稳定
    归并排序       O(n*log(n))     O(n*log(n))    O(n*log(n))       O(n)                稳定
    快速排序       O(n*log(n))     O(n*log(n))      O(n^2)            O(1)              不稳定
     
    冒泡排序经过优化以后,最好时间复杂度可以达到O(n)。设置一个标志位,如果有一趟比较中没有发生任何交换,可提前结束,因此在正序情况下,时间复杂度为O(n)。
    选择排序在最坏和最好情况下,都必须在剩余的序列中选择最小(大)的数,与已排好序的序列后一个位置元素做交换,依次最好和最坏时间复杂度均为O(n^2)。
    插入排序是在把已排好序的序列的后一个元素插入到前面已排好序(需要选择合适的位置)的序列中,在正序情况下时间复杂度为O(n)。
    堆是完全二叉树,因此树的深度一定是log(n)+1,最好和最坏时间复杂度均为O(n*log(n))。
    归并排序是将大数组分为两个小数组,依次递归,相当于二叉树,深度为log(n)+1,因此最好和最坏时间复杂度都是O(n*log(n))。
    快速排序在正序或逆序情况下,每次划分只得到比上一次划分少一个记录的子序列,用递归树画出来,是一棵斜树,此时需要n-1次递归,且第i次划分要经过n-i次关键字比较才能找到第i个记录,因此时间复杂度是sum_{i=1}^{n-1}(n-i)=n(n-1)/2,即O(n^2)。
     
    5:  一堆基本有序的数组,用哪种排序效率最高
      归并排序
     
    6:  JDK1.6到JDK1.8 GC上面最大做了什么变化
      1、去除了方法区,将常量、静态变量移到了堆中;将元数据移到了本地缓存中,称元空间。
      2、原来每个程序都有自己的永久代,现在移到本地缓存中,可以实现多个程序的共享,提高了内存的利用率。
      3、默认情况下,元空间的大小受内存限制。通过-XX:MetaspaceSize等设置大小
      好处:
        1、元空间可以动态增长了,不再是固定的了。
        2、提高了内存的利用率。
        3、它由元空间虚拟机管理,和原来的不同收集器回收相比,现在使用类加载器来判断过期的对象(类加载器标记不在存活则其加载的类也被释放)
      PS:元空间虚拟机负责元空间的分配,其采用的形式为组块分配。组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表。当一个类加载器需要组块时,它就会从这个全局的组块列表中获取并维持一个自己的组块列表。当一个类加载器不再存活,那么其持有的组块将会被释放,并返回给全局组块列表。类加载器持有的组块又会被分成多个块,每一个块存储一个单元的元信息。组块中的块是线性分配(指针碰撞分配形式)。组块分配自内存映射区域。这些全局的虚拟内存映射区域以链表形式连接,一旦某个虚拟内存映射区域清空,这部分内存就会返回给操作系统
     
    7:  CMS怎么进行垃圾收集的
      CMS(Concurrent Mark Sweep)是一种以获取最短回收停顿时间为目标的收集器,工作在老年代,基于“标记-清除”算法实现,整个过程分为以下4步:
    1. 初始标记:这个过程只是标记以下GC Roots能够直接关联的对象,但是仍然会Stop The World;
    2. 并发标记:进行GC Roots Tracing的过程,可以和用户线程一起工作。
    3. 重新标记:用于修正并发标记期间由于用户程序继续运行而导致标记产生变动的那部分记录,这个过程会暂停所有线程,但其停顿时间远比并发标记的时间短;
    4. 并发清理:可以和用户线程一起工作。
      CMS收集器的缺点:
    1. 对CPU资源比较敏感,在并发阶段,虽然不会导致用户线程停顿,但是会占用一部分线程资源,降低系统的总吞吐量。
    2. 无法处理浮动垃圾,在并发清理阶段,用户线程的运行依然会产生新的垃圾对象,这部分垃圾只能在下一次GC时收集。
    3. CMS是基于标记-清除算法实现的,意味着收集结束后会造成大量的内存碎片,可能导致出现老年代剩余空间很大,却无法找到足够大的连续空间分配当前对象,不得不提前触发一次Full GC。
     
      JDK1.5实现中,当老年代空间使用率达到68%时,就会触发CMS收集器,如果应用中老年代增长不是太快,可以通过-XX:CMSInitiatingOccupancyFraction参数提高触发百分比,从而降低内存回收次数提高系统性能。
      JDK1.6实现中,触发CMS收集器的阈值已经提升到92%,要是CMS运行期间预留的内存无法满足用户线程需要,会出现一次”Concurrent Mode Failure”失败,这是虚拟机会启动Serial Old收集器对老年代进行垃圾收集,当然,这样应用的停顿时间就更长了,所以这个阈值也不能设置的太高,如果导致了”Concurrent Mode Failure”失败,反而会降低性能,至于如何设置这个阈值,还得长时间的对老年代空间的使用情况进行监控。
     
     
    8:  G1怎么进行垃圾收集的
      G1(Garbage First)是JDK1.7提供的一个工作在新生代和老年代的收集器,基于“标记-整理”算法实现,在收集结束后可以避免内存碎片问题。
      G1优点:
    1. 并行与并发:充分利用多CPU来缩短Stop The World的停顿时间;
    2. 分代收集:不需要其他收集配合就可以管理整个Java堆,采用不同的方式处理新建的对象、已经存活一段时间和经历过多次GC的对象获取更好的收集效果;
    3. 空间整合:与CMS的”标记-清除”算法不同,G1在运行期间不会产生内存空间碎片,有利于应用的长时间运行,且分配大对象时,不会导致由于无法申请到足够大的连续内存而提前触发一次Full GC;
    4. 停顿预测:G1中可以建立可预测的停顿时间模型,能让使用者明确指定在M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
      
      使用G1收集器时,Java堆的内存布局与其他收集器有很大区别,整个Java堆会被划分为多个大小相等的独立区域Region,新生代和老年代不再是物理隔离了,都是一部分Region(不需要连续)的集合。G1会跟踪各个Region的垃圾收集情况(回收空间大小和回收消耗的时间),维护一个优先列表,根据允许的收集时间,优先回收价值最大的Region,避免在整个Java堆上进行全区域的垃圾回收,确保了G1收集器可以在有限的时间内尽可能收集更多的垃圾。
      不过问题来了:使用G1收集器,一个对象分配在某个Region中,可以和Java堆上任意的对象有引用关系,那么如何判定一个对象是否存活,是否需要扫描整个Java堆?其实这个问题在之前收集器中也存在,如果回收新生代的对象时,不得不同时扫描老年代的话,会大大降低Minor GC的效率。
      针对这种情况,虚拟机提供了一个解决方案:G1收集器中Region之间的对象引用关系和其他收集器中新生代与老年代之间的对象引用关系被保存在Remenbered Set数据结构中,用来避免全堆扫描。G1中每个Region都有一个对应的Remenbered Set(记录谁引用了它),当虚拟机发现程序对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于相同的Region中,如果不是,则通过CardTable把相关引用信息记录到被引用对象所属Region的Remenbered Set中。
      PS:回收过程
        1、初始化标记:G1 GC 对根进行标记
        2、根区域扫描:在初始标记的存活区扫描对老年代的引用,并标记被引用的对象
        3、并发标记:在整个堆中查找可访问的(存活的)对象
        4、最终标记:和GMS的重新标记相似
        5、筛选回收:对个Region的回收价值和成本进行派去,根据用户所期望的GC停顿时间制定回收计划。
       参见:https://www.cnblogs.com/ASPNET2008/p/6496481.html
    9:  G1相比于CMS有哪些优势
      1、回收算法不同:与CMS的”标记-清除”算法不同,G1可以看出是基于“复制”算法的,在运行期间不会产生内存空间碎片,有利于应用的长时间运行,且分配大对象时,不会导致由于无法申请到足够大的连续内存而提前触发一次Full GC;
     
     
    10: 哪些情况会导致Full GC
       1、full gc会将对中所有垃圾对象清理掉,perm区的被卸载的classloader中加载的类的数据;它是相对minor gc来说的,后者是eden去空间不足是触发,除了回收eden区不活动的对象,还会把老对象复制到old区。
          2、System.gc()方法的调用
          3、老年代代或永生区空间空间不足
          4、统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间
          5、CMS GC时出现promotion failed和concurrent mode failure
        a、promotion failed:在进行Minor GC时,Survivor Space放不下,对象只能放入老年代,而此时老年代也放不下造成的
        b、concurrent mode failure:执行CMS GC的过程中同时业务线程将对象放入老年代,而此时老年代空间不足,或者在做Minor GC的时候,新生代Survivor空间放不下,需要放入老年代,而老年代也放不下而产生的
          6、堆中分配很大的对象
      ps:和full gc、minor gc 并列的还有major gc,它专门用来清理老年代
    11: 新new的对象放在哪里
        1、对象内容和数组类型这些需要动态申请内存的存放在堆区。
        2、基本类型和对象引用存放栈区。
        3、堆区是在运行过程中会变化的
     
    12: 哪些东西放在栈区(虚拟机栈)
      1、栈区即虚拟机栈,是线程私有的。栈会为每个线程创建一个栈帧,每个方法从调用到执行完成的过程,就对应栈帧一个栈帧在虚拟机中的入栈到出栈的过程。
           2、栈帧中存储局部变量表,操作数栈,动态链接、方法出入口等信息。
        3、局部变量表存放基本数据类型、及对象引用类型和returnAddress类型(程序return后需要执行的指令引用,也是一种基本数据类型),这些数据也是操作过程中动态加载进来的。
        4、操作数栈,当对两个值进行算术运算时要先加载到这个栈中,结果值也是先放到这,结束时才放到局部变量表中。
      5、动态链接:方法字节码的引用
      6、方法出入口信息
      7、栈区的内容都是创建时就能确定的
      ps:
      1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制;
      1. 栈:存放基本类型 的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new出来的对象)或者常量池中(字符串常量对象存放的常量池中),局部变量【注意:(方法中的局部变量使用final修饰后,放在堆中,而不是栈中)】
      2.堆:存放使用new创建的对象,全局变量
      3. 静态域(方法区):存放静态成员(static定义的)
      4. 常量池(方法区):字符串常量和基本类型常量(public static final)。有时,在嵌入式系统中,常量本身会和其他部分分割离开(由于版权等其他原因),所以在这种情况下,可以选择将其放在ROM中 ;
      5. 非RAM存储:硬盘等永久存储空间
     
    13: 双亲委派模型, 有什么好处
      全盘负责和双亲委托加载,原则:由下向上询问加载,由上向下尝试加载。
      分类:引导类加载器、扩展类加载器、系统类加载器、用户自定义类加载器;线程上下文加载器
      双亲委派模型的优点:
      1、构造一种加载顺序,防止不可信类扮演可信任的类。  
      2、类重用,避免重复加载
    14: wait和sleep有什么区别
     
     1、来自不同的类:,sleep来自Thread类,和wait来自Object类,且sleep是Thread的静态类方法。
     2、对锁的操作:最主要是sleep方法没有释放锁,而wait方法释放了锁。
     3、唤醒方式不同:sleep自己醒了,wait需要同一个监视器上操作唤醒
     3、使用范围:wait,notify和notifyAll是操控对象监视器的,所以只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 
         synchronized(x){ 
            x.notify() 
           //或者wait() 
         }
     
    15: 线程池几个参数
     
     
    ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(
                    corePoolSize,// 核心线程数                          
                    maximumPoolSize, // 最大线程数                          
                    keepAliveTime, // 闲置线程存活时间                          
                    TimeUnit.MILLISECONDS,// 时间单位                         
                    new LinkedBlockingDeque<Runnable>(),// 线程队列                          
                    Executors.defaultThreadFactory(),// 线程工厂                          
                    new AbortPolicy()// 队列已满,而且当前线程数已经超过最大线程数时的异常处理策略                 
            );  
    线程池任务执行流程:
    1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
    2. 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
    3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
    4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
    5. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
    6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
    16: 怎么评估线程数大小 
      一、cup满载角度
        1、衡量标准有三个:cpu个数、等待时间、计算时间(主要是计算所在的比重P)、CPU的利用率、线程数T
        2、目的:防止CPU过载,也就是利用率超过100%
        3、使用的公式:T=C/P
        4、CUP计算时间:ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime(),计算时间可以通过执行CPU开始减结束时间得出
        5、等待时间:
          a、要预热省去加载类什么的时间(不计入统计范围),然后调用垃圾回收器
             b、固定时间范围内一直执行任务,最后拿中的事件减去CPU时间就是等待实现。但过程中要考虑线程切换等耗时
        6、线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程
      二、内存容量角度
        1、计算每个人物的内存大小,Runtime.getRuntime().totalMemory()
        ps:CPU是受操作系统调用的,BIO只会造成线程阻塞。
    17: 几个线程访问同一个东西,怎么保证安全
      一、如果都是读取:无所谓了
      二、如果一个写多个读
        1、volatile
        2、读写锁
      三、即读又写
        1、同步锁
        2、并发锁
      一般说来,确保线程安全的方法有这几个:竞争与原子操作、同步与锁、可重入、过度优化。
     
     18: Spring几个特点说下
      1--核心容器
        核心容器提供spring框架的基本功能,核心容器的主要组件是BeanFactory, 他是工厂模式的实现. 
        BeanFactory使用控制反转(IOC)模式将应用程序的配置和依赖性与实际的应用程序代码分开
      2--Spring上下文
        是一个配置文件,该配置文件向spring框架提供上下文信息
      3--Spring AOP
        通过配置管理特性,Spring AOP 模块直接将面向切面(方面)编程功能集成到spring框架中
      4--spring DAO
        JDBC DAO抽象层提供了有意义的已成层次结构, 可用该结构管理异常处理和不同数据库抛出的错误信息,极大的降低了异常代码数量
      5--Spring ORM
        spring框架插入了若干个ORM框架, 从而提供了ORM的对象工具,其中包括了Hibernate, Mybatis
      6--Spring Web
        web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供上下文
      7--Spring MVC
        该框架是一个全功能的构建web应用程序的MVC实现. 通过策略接口,MVC框架变成高度可配置的. MVC容纳了大量视图技术. 其中包括JSP、Velocity和POI
      Spring 框架的好处
        spring是最大的工厂
          spring负责业务逻辑组件的框架和生成, 并管理业务逻辑组件的生命周期 
        spring可以生产所有实例, 从控制器、 业务逻辑组件、 持久层组件
      Spring特点
        1--降低了组件之间的耦合性, 实现了软件各个层之间的解耦
        2--可以使用spring容器提供的服务, 如: 事务管理, 消息服务
        3--容器提供单例模式支持
        4--容器提供AOP技术, 利用它很容易实现权限拦截, 运行期监控
        5--容器提供了众多的辅助类, 能加快应用的开发(org.springframework.jdbc.core.JDBCTemplate 等)
        6--spring对主流的应用框架提供了集成支持, 例如: hibernate,JPA, Struts, Mybatis(IBatis)
        7--Spring属于低侵入式设计, 代码污染度极低
        8--独立于各种应用服务器
        9--spring的DI机制降低了业务对象替换的复杂性
        10--spring的高度开发性, 并不强制应用完全依赖于spring, 开发者可以自由选择spring的部分或者全部
    19: CGLib有没有了解过(CGlib是什么? )
      CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。 
      当然这些实际的功能是asm所提供的,asm又是什么?Java字节码操控框架,具体是什么大家可以上网查一查,毕竟我们这里所要讨论的是cglib, 
      cglib就是封装了asm,简化了asm的操作,实现了在运行期动态生成新的class。 
      可能大家还感觉不到它的强大,现在就告诉你。 
      实际上CGlib为spring aop提供了底层的一种实现;为hibernate使用cglib动态生成VO/PO (接口层对象)。 
      CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。
      CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
      CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
      CGLIB缺点:对于final方法,无法进行代理。
     
     20: Spring支持哪几种切片
      1.前置增强(@Before):org.springframework.aop.BeforeAdvice代表前置增强,因为Spring只支持方法级的增强,所以MethodBeforeAdvice是目前可的的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的;  
      2.后置增强(@After):org.springframework.aop.AfterReturningAdvice代表后增强,表示在目标方法执行后实施增强;
        a、在方法执行之后,还无法获取返回结果
      3.环绕增强(@Around):org.aopalliance.intercept.MethodInterceptor代表环绕增强,表示在目标方法执行前后实施增强;
         a、其中可以决定是否调用原来的方法
         b、整个请求处理链中只会调用一次
         c、常用于打印日志、设置缓存
      4.返回增强(@AfterRunning):是返回通知,可以获取返回结果
      5.异常抛出增强(@AfterThrowing):org.springframework.aop.ThrowsAdvice代表抛出异常增强,表示在目标方法抛出异常后实施增强;
      6.引介增强:org.springframework.aop.InteoductionInterceptor代表引介增强,表示在目标类中添加一些新的方法和属性;
        a、新的类实现DelegatingIntroductionInterceptor的invoke接口(这类和原类可以完全无关系)
        b、如下配置后applicationContext中获取getCar实例,则该对象即可转为car类型也可转为新定义类的类型
        c、1-5的都是目标方法范围内织入,而引介增强是直接在类级别上添加目标未实现的接口方法
      ps:
        a、这些增强接口都有一些方法,通过实现这些接口方法,在接口方法中这义横切逻辑,就可以将它们织入到目标类的方法的相应连接点的位置。
        b、增强执行顺序:环绕->前置->方法执行->环绕->后置->返回
     
    21: SpringBoot和Spring有什么区别
      Spring Boot是最近这几年才火起来的,那么它到底与Spring有啥区别呢?
      想了解区别,其实就是Spring Boot提供了哪些特征:
    1. 直接运行:Spring Boot可以建立独立的Spring应用程序;
    2. 简化部署:内嵌了如Tomcat,Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作了。
    3. 简化配置:POM,提供的POM可以简化Maven的配置;XML,无需再像Spring那样搞一堆繁琐的xml文件的配置;
    4. 可以自动配置Spring;
    5. 提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
    22: SpringBoot和Spring启动有什么区别
      1、IDEA启动 SpringBootApplication
      2、命令行启动首先将命令行位置跳转到当前项目的根目录下,再输入“mvn spring-boot:run”命令,初次操作maven需要下载插件等待几分钟。
      3、命令行编译为jar启动首先命令行在当前项目根目录运行编译命令“mvn install”,之后跳转到当前项目的target文件夹下(cd target)多出两个文件
     
     
    spring
    1. 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
    2. 其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
    3. 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
     
    23: Spring启动生命周期 
     
     
     
    1、Spring IoC容器允许BeanFactoryPostProcessor在容器实例化任何bean之前读取bean的定义(配置元数据),并可以修改它。如hsf中根据配置构造bean。
    2、BeanPostProcessor,我们想在Spring容器中完成bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理
       a、postProcessorBeforeInitailization方法是在bean实例化,依赖注入之后及自定义初始化方法(例如:配置文件中bean标签添加init-method属性指定Java类中初始化方法、@PostConstruct注解指定初始化方法(在BeanPostProcessor中执行),Java类实现InitailztingBean接口)之前调用
       b、后置处理器的postProcessorAfterInitailization方法是在bean实例化、依赖注入及自定义初始化方法之后调用
    3、InstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口,可以在Bean生命周期的另外两个时期提供扩展的回调接口,即实例化Bean之前(调用postProcessBeforeInstantiation方法)和实例化Bean之后(调用postProcessAfterInstantiation方法)
    4、BeanNameAware获取bean的名字
    5、BeanFactoryAware获取beanfactory的引用,beanfactory中一般用作调用getBean来立即实例化bean
    6、InitializingBean 设置完依赖后调用
    7、InstantiationAwareBeanPostProcessorAdapter是postProcessorBeforeInitailization的实现适配类,我们一般使用adapter对其来扩展。
    8、Constructor > @PostConstruct > InitializingBean > init-method ,反过来也一样,beanPostProcessor中执行@PreDestroy
    9、BeanFactoryAware后有个ApplicationContextAware
    相关连接:
    https://www.cnblogs.com/zrtqsk/p/3735273.html
    https://www.cnblogs.com/april-chen/p/8182631.html
     
    24: Spring注解@Resource和@Autowired区别对比  => 优先级不一样
      1、@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。 
      2、@Autowired默认按类型装配(这个注解属于Spring),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下: 
      @Autowired() @Qualifier("baseDao")     
      private BaseDao baseDao;    
       3、@Resource(这个注解属于J2EE),默认按照名称进行装配,名称可以通过name属性进行指定, 
      如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
      @Resource(name="baseDao")     
      private BaseDao baseDao;    
      我喜欢用 @Resource注解在字段上,且这个注解是属于J2EE的,减少了与spring的耦合。最重要的这样代码看起就比较优雅。
     
     
    25: spring @service @controller @componet 三者区别
      @controller用来定义控制层的组件 
           @service用来定义业务层的组件
           @repository用来定义持久层的组件
           @ component用来定义不在上述范围内的一般性组件
    上面组件的名称默认是类名的首字母小写,如果要重命名,则这样@controller("beanName")
    当在spring中配置了<context:annotation-config/> 和<context:component-scan base-package="*">时,上述四种注解的组件都会由spring容器来创建为bean并由自己来管理.
    那么创建了上面这些组件后,又是如何来注入的呢,这时就由@autowired来配置了。
    只需要在private的属性上加上@autowired就可以自动把接口属性的实现类的bean注入,注意不需要setter,getter方法
    上面如果一个接口属性有两个实现类,怎么办,这时就要用@qualifier来特别说明要注入哪个bean了。
     
        26: Http和Https协议有什么区别,证书了解不
      HTTPS和HTTP的区别:
        a、定义:
          http:是超文本传输协议,是客户端和服务端请求和应答的标准,过程中明文传输。
          http:是安全版的http,以安全传输为目标,具体就是http中加入ssl层,机密传输。
        b、具体使用:
          http使用简单,http则需要身份认证,需要用到数字证书
          http默认使用80端口,https则使用443
        c、证书
           是一串身份信息按照指定的格式排列后,包含证书颁发机构进行数字签名的数据。常见的数字证书格  式X.509 V3,存储到文件上一般一种为不含私钥的DER格式,以cer或者crt为文件后缀名,另一种为包含私钥的PKCS格式,以pfx或者p12为后缀。
           数字证书中包括(不是全部):
        (1)数字证书持有者身份信息 网站就包括所对应的URL或IP
        (2)该证书拥有者对应的该证书公钥
        (3)证书颁发者信息
        (4)证书颁发者用私钥对证书持有者身份信息和公钥的签名 使用的摘要和签名算法
       PS:
        优点
          https可认证用户和服务,确保发送正确客户机和服务器
          https协议可以确保数据传输过程中的安全性和完整性。
          https构建的网站在各大搜索引擎中排名更高
        缺点:
          增加资源消耗:https协议握手阶段比较费时,页面加载时间延长50%,增加10%到20%的耗电。
          增加成本:ssl证书需要钱,ssl证书需要绑定IP
        握手:(4步)
          1、浏览器说“你好,我是浏览器”,将直接的加密算法和一个随机值发送服务器。
          2、服务器说“你好,我是服务器”,从客户端提供的机密算法中选择一种,并添加一个自己生成的随机值和自己的证书一起发送浏览器端。此阶段服务端可以要求客户端提供证书。
          3、客户端收到证书后,先从ca校验该证书的合法性,并从证书中取出服务端公钥,然后生成一个随机值,用服务端公钥加密后传给服务端。
          4、服务端使用私钥对这个随机值进行解密,然后将三次的随机值拼到一起作为以后沟通的加密密钥。
          5、此时的客户端也会将三次随机值拼成一个密钥。
         使用:
          1、org.springframework.web.client.RestTemplate 封装了一个http/https的请求操作       
          2、先构造一个SSLContext,其中设置相信策略TrustStrategy默认是true.       
          3、构造一个SSL的连接工厂:SSLConnectionSocketFactory
          4、将注册到注册中心中(Registry),并基于此构建一个客户端连池(PoolingHttpClientConnectionManager)
          5、构造一个org.springframework.http.client.ClientHttpRequestFactory
          6、将请求工厂注入到resTemplate中
        27: 介绍下Redis设计实现
    Redis是基于内存、可持久化的日志型、Key-Value数据库 高性能存储系统,
     
      
     
     
        28: Redis的细节源码看过没有
    1. 支持5种数据结构
    支持strings, hashes, lists, sets, sorted sets 
    string是很好的存储方式,用来做计数存储。sets用于建立索引库非常棒;
    2. K-V 存储 vs K-V 缓存
    新浪微博目前使用的98%都是持久化的应用,2%的是缓存,用到了600+服务器 
    Redis中持久化的应用和非持久化的方式不会差别很大: 
    非持久化的为8-9万tps,那么持久化在7-8万tps左右; 
    当使用持久化时,需要考虑到持久化和写性能的配比,也就是要考虑redis使用的内存大小和硬盘写的速率的比例计算;
    3. 社区活跃
    Redis目前有3万多行代码, 代码写的精简,有很多巧妙的实现,作者有技术洁癖 
    Redis的社区活跃度很高,这是衡量开源软件质量的重要指标,开源软件的初期一般都没有商业技术服务支持,如果没有活跃社区做支撑,一旦发生问题都无处求救;
    Redis基本原理
    redis持久化(aof) append online file: 
    写log(aof), 到一定程度再和内存合并. 追加再追加, 顺序写磁盘, 对性能影响非常小
    1. 单实例单进程
    Redis使用的是单进程,所以在配置时,一个实例只会用到一个CPU; 
    在配置时,如果需要让CPU使用率最大化,可以配置Redis实例数对应CPU数, Redis实例数对应端口数(8核Cpu, 8个实例, 8个端口), 以提高并发: 
    单机测试时, 单条数据在200字节, 测试的结果为8~9万tps;
    2. Replication
    过程: 数据写到master–>master存储到slave的rdb中–>slave加载rdb到内存。 
    存储点(save point): 当网络中断了, 连上之后, 继续传. 
    Master-slave下第一次同步是全传,后面是增量同步;、
    3. 数据一致性
    长期运行后多个结点之间存在不一致的可能性; 
    开发两个工具程序: 
    1.对于数据量大的数据,会周期性的全量检查; 
    2.实时的检查增量数据,是否具有一致性;
    对于主库未及时同步从库导致的不一致,称之为延时问题; 
    对于一致性要求不是那么严格的场景,我们只需要要保证最终一致性即可; 
    对于延时问题,需要根据业务场景特点分析,从应用层面增加策略来解决这个问题; 
    例如: 
    1.新注册的用户,必须先查询主库; 
    2.注册成功之后,需要等待3s之后跳转,后台此时就是在做数据同步。
        29: Redis分布式缓存
    1.架构设计
    由于redis是单点,项目中需要使用,必须自己实现分布式。基本架构图如下所示:
     
     
    2.分布式实现
    通过key做一致性哈希,实现key对应redis结点的分布。
    一致性哈希的实现:
    l        hash值计算:通过支持MD5与MurmurHash两种计算方式,默认是采用MurmurHash,高效的hash计算。
    l        一致性的实现:通过java的TreeMap来模拟环状结构,实现均匀分布
    3.client的选择
    对于jedis修改的主要是分区模块的修改,使其支持了跟据BufferKey进行分区,跟据不同的redis结点信息,可以初始化不同的ShardInfo,同时也修改了JedisPool的底层实现,使其连接pool池支持跟据key,value的构造方法,跟据不同ShardInfos,创建不同的jedis连接客户端,达到分区的效果,供应用层调用
    4.模块的说明
    l        脏数据处理模块,处理失败执行的缓存操作。
    l        屏蔽监控模块,对于jedis操作的异常监控,当某结点出现异常可控制redis结点的切除等操作。
    整个分布式模块通过hornetq,来切除异常redis结点。对于新结点的增加,也可以通过reload方法实现增加。(此模块对于新增结点也可以很方便实现)
    对于以上分布式架构的实现满足了项目的需求。另外使用中对于一些比较重要用途的缓存数据可以单独设置一些redis结点,设定特定的优先级。另外对于缓存接口的设计,也可以跟据需求,实现基本接口与一些特殊逻辑接口。对于cas相关操作,以及一些事物操作可以通过其watch机制来实现。(参考我以前写的redis事物介绍
        30: 线程在频繁的Full GC 怎么排查
    我们知道Full GC的触发条件大致情况有以下几种情况: 
    1. 程序执行了System.gc() //建议jvm执行fullgc,并不一定会执行 
    2. 执行了jmap -histo:live pid命令 //这个会立即触发fullgc 
    3. 在执行minor gc的时候进行的一系列检查
    a、执行Minor GC的时候,JVM会检查老年代中最大连续可用空间是否大于了当前新生代所有对象的总大小。b、如果大于,则直接执行Minor GC(这个时候执行是没有风险的)。c、如果小于了,JVM会检查是否开启了空间分配担保机制,如果没有开启则直接改为执行Full GC。d、如果开启了,则JVM会检查老年代中最大连续可用空间是否大于了历次晋升到老年代中的平均大小,如果小于则执行改为执行Full GC。e、如果大于则会执行Minor GC,如果Minor GC执行失败则会执行Full GC
    a、使用了大对象 //大对象会直接进入老年代
    b、在程序中长期持有了对象的引用 //对象年龄达到指定阈值也会进入老年代
    对于我们的情况,可以初步排除a,b两种情况,最有可能是d和e这两种情况。为了进一步排查原因,我们在线上开启了 -XX:+HeapDumpBeforeFullGC。
    JVM在执行dump操作的时候是会发生stop the word事件的,也就是说此时所有的用户线程都会暂停运行。 为了在此期间也能对外正常提供服务,建议采用分布式部署,并采用合适的负载均衡算法
     
     
    1)FULL GC前后Java堆大小有变化;经研究发现是由于Java应用JVM参数XMS设置为默认值,在我们的系统环境下,Hotspot的Xms默认值为50M(-Xms默认是物理内存的1/64);每次GC时,JVM会根据各种条件调节Java堆的大小,Java堆的取值范围为[Xms, Xmx]。根据以上分析,修改Xms值与Xmx相等,这样就不会因为所使用的Java堆不够用而进行调节,经过测试后发现FULL GC次数从四位数减少至个位数。
     
    2)关键词“System”让我想到了System.gc调用,System.gc调用只是建议JVM执行年老代GC,而年老代GC触发FULL GC,JVM会根据系统条件决定是否执行FULL GC,正因为系统条件不好判断,所以很难构造System.gc调用触发FULL GC,几经周折终于成功,当System.gc触发FULL  GC时都会有关键词“(System)”,而 JVM自动触发的FULL GC却不带关键词“(System)”,可以断定是Java应用存在“System.gc”代码。经过本次测试我也发现System.gc的真正含义,通俗言之,“System.gc” 就是FULL GC触发的最后一根稻草。 
     
    从本次分析中,我们可以得出如下的经验: 
    1)Java应用的jvm参数Xms与Xmx保持一致,避免因所使用的Java堆内存不够导致频繁full gc以及full gc中因动态调节Java堆大小而耗费延长其周期。 
    2)建议不要调用System.gc或者Runtime.getRuntime().gc,否则本次调用可能会成为“压死骆驼的最后一根稻草”。当然我们可以通过设置jvm参数禁止这种调用生效,但是除非特别有把握该参数有必要添加,否则不推荐这么设置。
        31: JVM一些工具,jps, jmap
     -v 输出传递给JVM的参数
    $> jps -v
    23789 BossMain
    28802 Jps -Denv.class.path=/data/aoxj/bossbi/twsecurity/java/trustwork140.jar:/data/aoxj/bossbi/twsecurity/java/:/data/aoxj/bossbi/twsecurity/java/twcmcc.jar:/data/aoxj/jdk15/lib/rt.jar:/data/aoxj/jdk15/lib/tools.jar -Dapplication.home=/data/aoxj/jdk15 -Xms8m
    23651 Resin -Xss1m -Dresin.home=/data/aoxj/resin -Dserver.root=/data/aoxj/resin -Djava.util.logging.manager=com.caucho.log.LogManagerImpl -Djavax.management.builder.initial=com.caucho.jmx.MBeanServerBuilderImpl
     
    jmap
    JVM Memory Map命令用于生成heap dump文件,如果不使用这个命令,还可以使用-XX:+HeapDumpOnOutOfMemoryError参数来让虚拟机出现OOM的时候自动生成dump文件。 jmap不仅能生成dump文件,还可以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。【内存分析】
      
     
    32: 海量日志数据,提取出某日访问百度次数最多的那个IP?

    此题,在我之前的一篇文章算法里头有所提到,当时给出的方案是:IP的数目还是有限的,最多2^32个,所以可以考虑使用hash将ip直接存入内存,然后进行统计。

      再详细介绍下此方案:首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2^32个IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。  

       
    34:  垃圾回收的原理
         优点:a.不需要考虑内存管理, b.可以有效的防止内存泄漏,有效的利用可使用的内存, c.由于有垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"
         原理:垃圾回收器是作为一个单独的低级别的线程运行,在不可知的情况下对内存堆中已死亡的或者长期没有使用的对象回收,但是不能实时的对某一对象或者所有对象进行垃圾回收。
         垃圾回收机制:分代复制垃圾回收、标记垃圾回收、增量垃圾回收
        35:  你写过Java的Web系统
     
        36:  简单介绍一下你的项目
     
        37:  两个有序的数组,合成一个有序的数组,怎么合并效率高 
    归并排序
        38:  淘宝的登陆页面,怎么保证他安全

    使用哈希加盐法来为密码加密
          解决的办法是将密码加密后再存储进数据库,比较常用的加密方法是使用哈希函数(Hash Function)。哈希函数的具体定义,大家可以在网上或者相关书籍中查阅到,简单地说,它的特性如下:  
         (1)原始密码经哈希函数计算后得到一个哈希值  
         (2)改变原始密码,哈希函数计算出的哈希值也会相应改变 
         (3) 同样的密码,哈希值也是相同的 
         (4) 哈希函数是单向、不可逆的。也就是说从哈希值,你无法推算出原始的密码是多少 
        最简单、常见的破解方式当属字典破解(Dictionary Attack)和暴力破解(Brute Force Attack)方式。这两种方法说白了就是猜密码。

        字典破解和暴力破解都是效率比较低的破解方式。如果你知道了数据库中密码的哈希值,你就可以采用一种更高效的破解方式,查表法(Lookup Tables)。还有一些方法,比如逆向查表法(Reverse Look     up Tables)、彩虹表(Rainbow Tables)等,都和查表法大同小异。现在我们来看一下查表法的原理。

         查表法不像字典破解和暴力破解那样猜密码,它首先将一些比较常用的密码的哈希值算好,然后建立一张表,当然密码越多,这张表就越大。当你知道某个密码的哈希值时,你只需要在你建立好的表中查找       该哈希值,如果找到了,你就知道对应的密码了。

          从上面的查表法可以看出,即便是将原始密码加密后的哈希值存储在数据库中依然是不够安全的。那么有什么好的办法来解决这个问题呢?答案是加盐。

          盐(Salt)是什么?就是一个随机生成的字符串。我们将盐与原始密码连接(concat)在一起(放在前面或后面都可以),然后将concat后的字符串加密。采用这种方式加密密码,查表法就不灵了(因为盐是随机生成的)。 

         单单使用哈希函数来为密码加密是不够的,需要为密码加盐来提高安全性,盐的长度不能过短,并且盐的产生应该是随机的。

     
        39:  你有最新半年用户的订单,每天的用户订单量有上亿,预测下未来一周哪些商品最容易被购买
    数据建模-分析
       40: 你有啥问题
    “入职后有没有培训活动?”
    “公司对我的期望是什么?”
    “这个部门或团队有多少人?主要是负责哪方面的?”
     
    1、synchronized关键字原理?
        原理:synchronized底层是通过一个monitor的对象阻塞和获取。
        对代码同步:指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。 
        对方法同步:常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。
        重量级锁:Mutex Lock 监视器锁monitor本质就是依赖于底层的操作系统的Mutex Lock来实现的。
        
     
    2、hashMap底层实现。
    • HashMap 的基本组成成员
            首先,HashMap 是 Map 的一个实现类,它代表的是一种键值对的数据存储形式。Key 不允许重复出现,Value 随意。jdk 8 之前,其内部是由数组+链表来实现的,而 jdk 8 对于链表长度超过 8 的链表将转储为红黑树。大致的数据存储形式如下:
        
      从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
       源码如下:
    Java代码  
    1. /** 
    2.  * The table, resized as necessary. Length MUST Always be a power of two. 
    3.  */  
    4. transient Entry[] table;  
    5.   
    6. static class Entry<K,V> implements Map.Entry<K,V> {  
    7.     final K key;  
    8.     V value;  
    9.     Entry<K,V> next;  
    10.     final int hash;  
    11.     ……  
    12. }  
       可以看出,Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。
     
    • put 方法的具体实现
    Java代码  
    1. public V put(K key, V value) {  
    2.     // HashMap允许存放null键和null值。  
    3.     // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。  
    4.     if (key == null)  
    5.         return putForNullKey(value);  
    6.     // 根据key的keyCode重新计算hash值。  
    7.     int hash = hash(key.hashCode());  
    8.     // 搜索指定hash值在对应table中的索引。  
    9.     int i = indexFor(hash, table.length);  
    10.     // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。  
    11.     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
    12.         Object k;  
    13.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
    14.             V oldValue = e.value;  
    15.             e.value = value;  
    16.             e.recordAccess(this);  
    17.             return oldValue;  
    18.         }  
    19.     }  
    20.     // 如果i索引处的Entry为null,表明此处还没有Entry。  
    21.     modCount++;  
    22.     // 将key、value添加到i索引处。  
    23.     addEntry(hash, key, value, i);  
    24.     return null;  
    25. }  
          当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
        
        
     
    • remove 方法的具体实现。
            采用迭代器遍历,不仅适用于HashMap,对其它类型的容器同样适用。
       采用这种方法的遍历,可以用下文提及的方式安全地对HashMap内的元素进行修改,并不会对后续的删除操作造成影响。
      如果使用foreach遍历方法删除HashMap中的元素,Java很有可能会在运行时抛出异常。
        为什么呢?
        1、使用iterator迭代删除时没有问题的,在每一次迭代时都会调用hasNext()方法判断是否有下一个,是允许集合中数据增加和减少的。
        2、使用forEach删除时,会报错ConcurrentModificationException,因为在forEach遍历时,是不允许map元素进行删除和增加。
        所以,遍历删除map集合中元素时,必须使用迭代iterator
     
     
     
     
    • 为什么时HashMap的容量总是2的n次方?
        如果不是2的2次幂,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!而当数组长度为16时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1,这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了高位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。
        
        
        
     
    • 其他一些基本方法的基本介绍
     
       HashMap 包含如下几个构造器:
       HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。
       HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。
       HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。
       HashMap的基础构造器HashMap(int initialCapacity, float loadFactor)带有两个参数,它们是初始容量initialCapacity和加载因子loadFactor。
       initialCapacity:HashMap的最大容量,即为底层数组的长度。
       loadFactor:负载因子loadFactor定义为:散列表的实际元素数目(n)/ 散列表的容量(m)。
     
     
    • 什么是红黑树
    红黑树本质上是一种二叉查找树,但它在二叉查找树的基础上额外添加了一个标记(颜色),同时具有一定的规则。这些规则使红黑树保证了一种平衡,插入、删除、查找的最坏时间复杂度都为 O(logn)。
     
     
     
    3、TCP与UDP的区别
      1、基于连接与无连接
      2、TCP要求系统资源较多,UDP较少; 
      3、UDP程序结构较简单 
      4、流模式(TCP)与数据报模式(UDP); 
      5、TCP保证数据正确性,UDP可能丢包 
      6、TCP保证数据顺序,UDP不保证 
     
     
    4、TCP三次握手说一下。
     
    简单说,让双方都证实对方能发收。
    知道对方能收是因为收到对方的因为收到而发的回应。
    具体:
    1:A发,B收, B知道A能发
    2:B发,A收, A知道B能发收
    3:A发,B收, B知道A能收
     
    5、看你项目用到线程池,说一下线程池工作原理,任务拒接策略有哪几种?
    一个线程从被提交(submit)到执行共经历以下流程:
    • 线程池判断核心线程池里是的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程
    • 线程池判断工作队列是否已满。如果工作队列没有满,则将新提交的任务储存在这个工作队列里。如果工作队列满了,则进入下一个流程。
    • 线程池判断其内部线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已满了,则交给饱和策略来处理这个任务。
     
    任务拒接策略?
     有4种内置的实现策略和一个用户自定义拒绝策略。
    AbortPolicy       为java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try catch,否则程序会直接退出。 
    DiscardPolicy        直接抛弃,任务不执行,空方法 。
    DiscardOldestPolicy   从队 列里面抛弃head的一个任务,并再次execute 此task。
    CallerRunsPolicy        在调用execute的线程里面执行此command,会阻塞入口 。 
    用户自定义拒绝策略   实现RejectedExecutionHandler,并自己定义策略模式。
     
    再次需要注意的是,ThreadPoolExecutor.submit() 函数,此方法内部调用的execute方法,并把execute执行完后的结果给返回,但如果任务并没有执行的话(被拒绝了),则submit返回的future.get()会一直等到。
    future 内部其实还是一个runnable,并把command给封装了下,当command执行完后,future会返回一个值。
     
     
    6、进程和线程的区别?
    进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
     
    1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
    2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
    3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
    4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
     
     
    7、ArrayList与LinkedList的区别?
      1、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 (LinkedList是双向链表,有next也有previous)
      2、对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
      3、对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 
     
     
     
    8、线程安全与非线程安全集合说一下,底层怎么实现的(hashmap,concurrenthashmap)?
         Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。
         ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment,默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁的几率,提高并发效率。
        
     
    9、Hashtable、ConcurrentHashMap、TreeMap、HashMap的key,value都是不为空的吗?
     
    HashMap的key和value都允许为空,treeMap的value允许为空。
     
    10、单例模式  
    •  有几种实现方式 ?
            5种  
    1.饿汉模式(调用效率高,但是不能延时加载):
    2.懒汉模式(调用效率不高,但是能延时加载):
    3.双重检测锁模式(由于JVM底层模型原因,偶尔会出问题,不建议使用):
    4.静态内部类式(线程安全,调用效率高,可以延时加载):
    5.枚举类(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用):
    如何选用:
    -单例对象 占用资源少,不需要延时加载,枚举 好于 饿汉
    -单例对象 占用资源多,需要延时加载,静态内部类 好于 懒汉式 
     
    • 单例模线程安全吗?  
      不安全 。
    • 一般如何保证它线程安全 ?  
          double-check 双重检查锁定  。
    • 修饰符 volatile  有什么作用 ?
            能保证被它修饰的成员变量可以被多个线程正确的处理。Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。它在某些情况下比synchronized的开销更小
     
    11、 判断一个字符在一个字符串中出现的次数  ?
      StringUtils.countMatches(str, t);
     
    12、HashMap是不是有序的?
     
       不是有序的.
     
    • 有没有有顺序的Map实现类? 
    有TreeMap和LinkedHashMap。
     
    • TreeMap和LinkedHashMap是如何保证它的顺序的?
              LinkedHashMap 是根据元素增加或者访问的先后顺序进行排序,而 TreeMap是基于元素的固有顺序 (由 Comparator 或者 Comparable 确定)。
                                     
     
    • 哪个的有序实现比较好?
          TreeMap TreeMap则实现了 SortedMap 接口。          
    • 你觉得还有没有比它更好或者更高效的实现方式?
               参照TreeMap的value排序,我们一样的也可以实现HashMap的排序。
     
    13、实现所有的线程一起等待某个事件的发生,当某个事件发生时,所有线程一起开始往下执行的话,有什么好的办法吗?
     
    栅栏(Java的并发包中的CyclicBarrier)  CountDownLatch  CyclicBarrier  Semaphore
    • CountDownLatch (N个线程数量count减为0 主程序或一组程序开始执行)
            CountDownLatch是一个计数器闭锁,主要的功能就是通过await()方法来阻塞住当前线程,然后等待计数器减少到0了,再唤起这些线程继续执行。 
            这个类里主要有两个方法,一个是向下减计数器的方法:countdown(),如果取得当前的状态为0,说明这个锁已经结束,直接返回false;
            如果没有结束,然后去设置计数器减1,如果compareAndSetState不成功,则继续循环执行。 而其中的一直等待计数器归零的方法是await()。 
    • CyclicBarrier(N个线程,他们之间任何一个没有完成,所有的线程都必须等待) 
            CyclicBarrier是加计数方式,计数达到构造方法中参数指定的值时释放所有等待的线程。
     
    • Semaphore(Semaphore 是只允许一定数量的线程同时执行一段任务。)
                Semaphore,每次semaphore.acquire(),获取一个资源,每次semaphore.acquire(n),获取n个资源,
                当达到semaphore 指定资源数量时就不能再访问线程处于阻塞,必须等其它线程释放资源,semaphore.relase()每次资源一个资源,
                semaphore.relase(n)每次资源n个资源。
                
    你知道它的实现原理吗?
    继续问,你还知道其它的实现方式吗?
     
    继续问,你觉得这些方式里哪个方式更好?
     
    如果让你来写的话,你觉得还有比它更好的实现方式吗?
     
    14、IO包和NIO包 熟悉吗?
    • NIO模型 其中的selector 职责和实现原理
    传统的socket IO中,需要为每个连接创建一个线程,当并发的连接数量非常巨大时,线程所占用的栈内存和CPU线程切换的开销将非常巨大。使用NIO,不再需要为每个线程创建单独的线程,可以用一个含有限数量线程的线程池,甚至一个线程来为任意数量的连接服务。由于线程数量小于连接数量,所以每个线程进行IO操作时就不能阻塞,如果阻塞的话,有些连接就得不到处理,NIO提供了这种非阻塞的能力。
    1、增加了一个角色,要有一个专门负责收集客人需求的人。NIO里对应的就是Selector。
    2、由阻塞服务方式改为非阻塞服务了,客人吃着的时候服务员不用一直侯在客人旁边了。传统的IO操作,比如read(),当没有数据可读的时候,线程一直阻塞被占用,直到数据到来。NIO中没有数据可读时,read()会立即返回0,线程不会阻塞。
         NIO中,客户端创建一个连接后,先要将连接注册到Selector,相当于客人进入餐厅后,告诉前台你要用餐,前台会告诉你你的桌号是几号,然后你就可能到那张桌子坐下了,SelectionKey就是桌号。当某一桌需要服务时,前台就记录哪一桌需要什么服务,比如1号桌要点菜,2号桌要结帐,服务员从前台取一条记录,根据记录提供服务,完了再来取下一条。这样服务的时间就被最有效的利用起来了。
    • NIO的核心是什么 (Selector)
    Selector类是NIO的核心类,Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。
      与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑。
     
    15、虚拟机JVM 组成部分
        
    程序计数器
    指示当前程序执行到了哪一行,执行JAVA方法时纪录正在执行的虚拟机字节码指令地址;执行本地方法时,计数器值为undefined
    虚拟机栈
    用于执行JAVA方法。栈帧存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。程序执行时栈帧入栈;执行完成后栈帧出栈
    本地方法栈
    用于执行本地方法,其它和虚拟机栈类似
    着重说一下虚拟机栈中的局部变量表,里面存放了三个信息:
    • 各种基本数据类型(boolean、byte、char、short、int、float、long、double)
    • 对象引用(reference)
    • returnAddress地址
    这个returnAddress和程序计数器有什么区别?前者是指示JVM的指令执行到哪一行,后者则是你的代码执行到哪一行。
    私有内存区伴随着线程的产生而产生,一旦线程中止,私有内存区也会自动消除,因此讨论的内存回收主要是针对共享内存区。
    JAVA堆
    既然GC主要发生在堆内存中,这部分我们会对堆内存进行比较详细的描述。
    堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。堆是应用程序在运行期请求操作系统分配给自己的向高地址扩展的数据结构,是不连续的内存区域。用一句话总结堆的作用:程序运行时动态申请某个大小的内存空间。 
     
    新生代:刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1。S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中。
    • 为什么新生代要分出两个survivor区?
            设置两个Survivor区最大的好处就是解决了碎片化
    老年代:如果某个对象经历了几次垃圾回收之后还存活,就会被存放到老年代中。老年代的空间一般比新生代大。
     
    GC名称
    介绍
    Minor GC
    发生在新生代,频率高,速度快(大部分对象活不过一次Minor GC)
    Major GC
    发生在老年代,速度慢
    Full GC
    清理整个堆空间
    • java中垃圾回收机制?
    JAVA 并没有给我们提供明确的代码来标注一块内存并将其回收。或许你会说,我们可以将相关对象设为 null 或者用 System.gc()。然而,后者将会严重影响代码的性能,因为一般每一次显式的调用 system.gc() 都会停止所有响应,去检查内存中是否有可回收的对象。这会对程序的正常运行造成极大的威胁。另外,调用该方法并不能保证 JVM 立即进行垃圾回收,仅仅是通知 JVM 要进行垃圾回收了,具体回收与否完全由 JVM 决定。这样做是费力不讨好。
     
    • 垃圾回收算法概述
        1、追踪回收算法(tracing collector) 
        从根结点开始遍历对象的应用图。同时标记遍历到的对象。遍历完成后,没有被标记的对象就是目前未被引用,可以被回收。
        2、压缩回收算法(Compacting Collector) 
        把堆中活动的对象集中移动到堆的一端,就会在堆的另一端流出很大的空闲区域。这种处理简化了消除碎片的工作,但可能带来性能的损失。
        3、复制回收算法(Coping Collector) 
    把堆均分成两个大小相同的区域,只使用其中的一个区域,直到该区域消耗完。此时垃圾回收器终端程序的执行,通过遍历把所有活动的对象复制到另一个区域,复制过程中它们是紧挨着布置的,这样也可以达到消除内存碎片的目的。复制结束后程序会继续运行,直到该区域被用完。 
    但是,这种方法有两个缺陷:
    对于指定大小的堆,需要两倍大小的内存空间,
    需要中断正在执行的程序,降低了执行效率
        4、按代回收算法(Generational Collector) 
        为什么要按代进行回收?这是因为不同对象生命周期不同,每次回收都要遍历所有存活对象,对于整个堆内存进行回收无疑浪费了大量时间,对症下药可以提高垃圾回收的效率。主要思路是:把堆分成若搞个子堆,每个子堆视为一代,算法在运行的过程中优先收集“年幼”的对象,如果某个对象经过多次回收仍然“存活”,就移动到高一级的堆,减少对其扫描次数。
    •     垃圾回收器有哪些?
        串行回收器(serial collector)
        并行回收器
        CMS回收器
        G1回收器
        
        
        JAVA性能优化
        真正影响JAVA程序性能的,就是碎片化。碎片是JAVA堆内存中的空闲空间,可能是TLAB剩余空间,也可能是被释放掉的具有较长生命周期的小对象占用的空间。
    1. 减少new对象。每次new对象之后,都要开辟新的内存空间。这些对象不被引用之后,还要回收掉。因此,如果最大限度地合理重用对象,或者使用基本数据类型替代对象,都有助于节省内存;
    2. 多使用局部变量,减少使用静态变量。局部变量被创建在栈中,存取速度快。静态变量则是在堆内存;
    3. 避免使用finalize,该方法会给GC增添很大的负担;
    4. 如果是单线程,尽量使用非多线程安全的,因为线程安全来自于同步机制,同步机制会降低性能。例如,单线程程序,能使用HashMap,就不要用HashTable。同理,尽量减少使用synchronized
    5. 用移位符号替代乘除号。eg:a*8应该写作a<<3
    6. 对于经常反复使用的对象使用缓存;
    7. 尽量使用基本类型而不是包装类型,尽量使用一维数组而不是二维数组;
    8. 尽量使用final修饰符,final表示不可修改,访问效率高
    9. 单线程情况下(或者是针对于局部变量),字符串尽量使用StringBuilder,比StringBuffer要快;
    10. String为什么慢?因为String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。如果不能保证线程安全,尽量使用StringBuffer来连接字符串。这里需要注意的是,StringBuffer的默认缓存容量是16个字符,如果超过16,apend方法调用私有的expandCapacity()方法,来保证足够的缓存容量。因此,如果可以预设StringBuffer的容量,避免append再去扩展容量。如果可以保证线程安全,就是用StringBuilder。
     
     
     
    16、ArrayList遍历时正确删除元素?
     
    删除元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
     
    17、对一个List的进行subList后,原有list进行增、删、改,再操作subList会怎样?
     
         子 List 的元素和原 List 中的后一部分是重合的, 而子 List 还在遍历过程中时, 向原 List 中新增元素, 这样给子 List 的遍历过程造成了干扰甚至困扰, 于是就抛出了并发修改异常将会抛出java.util.ConcurrentModificationException
     
     
    18、web应用安全问题?
        1、跨站脚本攻击(CSS or XSS, Cross Site Scripting) 
         方案:输入或输出时对其进行字符过滤或转义处理。
      2、SQL注入攻击(SQL injection)
    方案:输入输出都是过滤、合法性检查和长度限制等通用方法。
      3、远程命令执行(Code execution,个人觉得译成代码执行并不确切) 
         方案:严格限制运行Web服务的用户权限。
      4、目录遍历(Directory traversal) 
        方案:1、同样是限制Web应用在服务器上的运行  2、进行严格的输入验证,控制用户输入非法路径。
      5、文件包含(File inclusion)
        方案:对文件来源进行审查
      6、脚本代码暴露(Script source code disclosure) 
      7、Http请求头的额外的回车换行符注入(CRLF injection/HTTP response splitting)
      8、跨帧脚本攻击(Cross Frame Scripting)
      9、PHP代码注入(PHP code injection)
      10、XPath injection
      11、Cookie篡改(Cookie manipulation)
      12、URL重定向(URL redirection)
      13、Blind SQL/XPath injection for numeric/String inputs
      14、Google Hacking
    15、表单、AJAX提交必须执行CSRF安全过滤。
    16、URL外部重定向传入的目标地址必须执行白名单过滤。
     
    19、简单介绍下spring的ioc和aop?
    •  控制反转(Inversion of Control,英文缩写为IOC);
        ioc就是典型的工厂模式,通过sessionfactory去注入实例。依赖注入    。
      自己实现用什么方式?    反射原理  
    其实就是通过解析xml文件,通过反射创建出我们所需要的bean,再将这些bean挨个放到集合中,然后对外提供一个getBean()方法,以便我们获得这bean。
    通俗来讲就如同婚姻介绍所,只需要告诉它找个什么样的女朋友,然后婚介就会按照我们的要求,提供一个mm,如果婚介给我们的人选不符合要求,我们就会抛出异常。
     
    •  面向切面编程(Aspect Oriented Programming,英文缩写为AOP)
        AOP就是典型的代理模式的体现。 实现拦截器  日志 统一权限校验 。
        spring的IoC容器是spring的核心,spring AOP是spring框架的重要组成部分。
        
     实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码.简单点解释,比方说你想在你的biz层所有类中都加上一个打印‘你好’的功能,这时就可以用aop思想来做.你先写个类写个类方法,方法经实现打印‘你好’,然后Ioc这个类 ref=“biz.*”让每个类都注入即可实现。
     
    20、并发问题 
    丢失更新  用户A把6改成2  用户B把2改成6  则用户A丢失了更新
    脏读问题  用户A,B 看到的都是6  用户B把6改为2  则用户A读的值仍然是6
     
    21、乐观锁  悲观锁
     
    悲观  屏蔽一切违反数据操作完整性
    乐观  只是在提交的时候检查是否违反数据完整性
     
    22、sql优化
      • 复杂sql避免模糊匹配
      • 索引问题 唯一索引  和普通索引
      • 复杂操作
      • 在可以使用UNION ALL的语句里,使用了UNION  
      • 字段长度小于5000用varchar,超过用TEXT,独立一张表,用主键来对应。
      • 在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引。
     
    24、介绍下使用的持久层框架? 为什么要选择这个(有什么好处)?
    MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。
    MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
     
    25、mybatis  $和#的区别
      1、效果来看:SELECT * FROM USER WHERE ID = #{id} /${id} ,从效果上来看都可以替换成功,只不过前置默认会把传入的参数当成字符串处理(它还可以通过,jdbcType=Number等指明类型),后者只是简单的替换(不加‘’号)。
      2、概念上:${}是一个动态SQL编译,#{}是预编译
      3、功能上:#{}因为是预编译,所以可以防止SQL注入,但${}不能。
      4、使用上:#{}常用来处理参数传入,${}用来处理动态SQL构造,如select * from ${} 
      5、原则:尽可能的使用#{}
      PS:
      1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id". 
            2. $将传入的数据直接显示生成在sql中。如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id,  如果传入的值是id,则解析成的sql为order by id. 
            3. #方式能够很大程度防止sql注入。 
            4.$方式无法防止Sql注入。 
            5.$方式一般用于传入数据库对象,例如传入表名.  
            6.一般能用#的就别用$. 
    26、HashMap和Hashtable有什么区别?
         HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
         HashMap允许键和值是null,而Hashtable不允许键或者值是null。
         Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
         HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
         一般认为Hashtable是一个遗留的类。
    27、hashCode()和equals()方法的重要性体现在什么地方?
     
        通过hashCode和equals方法保证元素的唯一性,当重写equals方法时,必须重写hashCode方法,因为如果不重写这两个方法,就会默认使用Object的方法,一般是不相同的,所以就会导致存储了重复值,与hashset、hashmap等性质冲突。
     
     
    28、Vector、ArrayList和LinkedList有什么区别?
     
    ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
     
    ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
     
    相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
     
    LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
     
    也可以参考ArrayList vs. LinkedList。 
     
     
    29、数据库事务及隔离级别说一下。
    •     数据库事务的几个特性:
        原子性(Atomicity )、一致性( Consistency )、隔离性或独立性( Isolation)和持久性(Durabilily),简称就是ACID。
    •     数据库事务怎么保证一致性?
        数据库进行任何写入操作的时候都是要先写日志的,同样的道理,我们在执行事务的时候数据库首先会记录下这个事务的redo操作日志,然后才开始真正操作数据库,
        在操作之前,首先会把日志文件写入磁盘,那么当突然断电的时候,即使操作没有完成,在重新启动数据库时候,数据库会根据当前数据的情况进行undo回滚或者是redo前滚,
        这样就保证了数据的强一致性。
    •     数据库隔离级别:
        ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
      ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
      ③ Read committed (读已提交):可避免脏读的发生。
      ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
     
    30、synchronized和lock区别,可重入锁与非可重入锁的区别
    •     synchronized和lock区别:
    1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
    2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
    3. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
    4. 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
    5. Lock可以提高多个线程进行读操作的效率。
    6. 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
    •     可重入锁与非可重入锁的区别:
         可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
    在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁。可重入锁最大的作用是避免死锁。
     
     
    31、aop代理模式
    AOP 全称 Aspect Oriented Programming,面向切面编程,和 OOP 一样也是一种编程思想。AOP 出现的原因是为了解决 OOP 在处理 侵入性业务上的不足。
    代理模式分为静态代理和动态代理两种。
    静态代理:通常用于对原有业务逻辑的扩充。创建一个代理类实现和方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。
    动态代理:动态代理底层是使用反射实现的,是在程序运行期间动态的创建接口的实现。
     
    32、jdk1.8新特性
    1. 速度更快 – 红黑树 
    HashMap中的红黑树
    HashMap中链长度大于8时采取红黑树的结构存储。
    红黑树,除了添加,效率高于链表结构。
     
    2. 代码更少 – Lambda 
      • Lambda表达式的基础语法:Java8引入了一个新的操作符“->”,该操作符成为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分
      • 左侧:Lambda表达式的参数列表 
      • 右侧:Lambda表达式中所需执行的功能,即Lambda体。
    3. 强大的Stream API – Stream 
    一系列流水线式的中间操作。
    流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
    注意: 
    ①Stream自己不会存储元素。 
    ②Stream不会改变源对象。相反,会返回持有新结果的新Stream。 
    ③Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
     
    4. 便于并行 – Parallel 
            在必要的情况下,将一个大任务进行必要的拆分Fork成若干个小任务,再将小任务的运算结果进行Join汇总。
    5. 最大化减少空指针异常 – Optional
             是一个容器类,代表一个值存在或不存在,原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常。
    6、ConcurrentHashMap
      • Jdk1.7时隔壁级别CocnurrentLevel(锁分段机制)默认为16。
      • JDK1.8采取了CAS算法
      • Jdk1.8没有永久区,取而代之的是MetaSpace元空间,用的是物理内存。
     
    33、java的4种引用 强软弱虚
      强引用        
        a、new一个对象,强引用不会被GC回收。
        b、默认的就是强引用
      软引用(SoftReference)、
        1、内存不足的时候会被回收
        2、会影响对象的回收
      弱引用(WeakReference)        
        1、当引用的对象被回收时,reference的引用为null
        2、它不影响对象的回收
      弱引用与软引用的区别在于:
        只具有弱引用的对象拥有更短暂的生命周期。        
        在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
      虚引用(PhantomReference)        
        虚引用主要用来跟踪对象被垃圾回收器回收的活动。
      虚引用与软引用和弱引用的一个区别在于:
        虚引用必须和引用队列 (ReferenceQueue)联合使用。        
        当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中
      PS:
        1、在对应引用A设置成null调用gc时并不会马上回收,若有对A对象的引用则更不会回收了
        2、这些引用会保存着对象设置时的值,所以并不是只持有引用。
        3、threadlocal中的entity就是使用了weakreference
     
    34、分布式服务 解决了哪些问题,自己设计一个分布式框架 会用到哪些技术?
            需要拆分应用进行服务化,以提高开发效率,调优性能,节省关键竞争资源
            当服务越来越多时,服务的URL地址信息就会爆炸式增长,配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。 
            当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。 
            接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?等等…
                
            用到哪些技术: dubbo(分布式框架), zookeeper(开源的分布式协调服务) ,redis(缓存), ssdb, nsq, nginx(负载均衡), Kafka,hessian ,RPC,netty。
                
    35、Zookeeper服务的注册和发现?
            1. init获取Zookeeper的服务注册信息,并缓存在service_repos
            2. get_service_repos方法获取实例变量service_repos
            3. get_service_endpoint根据init构建好的service_repos,以及lb_strategy提供的负载均衡策略返回某个服务的URL地址
            4. update_service_repos通过Zookeeper的Watcher机制来实时更新本地缓存service_repos
            5. heartbeat_monitor是一个心跳检测线程,用来进行服务提供者的健康存活检测,如果出现问题,将该服务提供者从该服务的提供者列表中移除;
            反之,则加入到服务的提供者列表中LoadBalanceStrategy定义了根据服务提供者的信息返回对应的服务Host和IP,即决定由那台主机+端口来提供服务。
     
    36、主流的分布式框架?
     dubbo,dubbox,spring-cloudfinaglethrift
     
    37、redis的原理 和存储结构   持久化和非持久
     
        Redis存储机制分成两种Snapshot和AOF。无论是那种机制,Redis都是将数据存储在内存中。
        Snapshot工作原理: 是将数据先存储在内存,然后当数据累计达到某些设定的伐值的时候,就会触发一次DUMP操作,将变化的数据一次性写入数据文件(RDB文件)。
        AOF 工作原理: 是将数据也是先存在内存,但是在存储的时候会使用调用fsync来完成对本次写操作的日志记录,这个日志揭露文件其实是一个基于Redis网络交互协议的文本文件。
        
    38、类加载过程
    JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。
     
    1. 虚拟机在首次加载Java类时,会对静态代码块、静态成员变量、静态方法进行一次初始化(静态间按顺序执行)。
    2. 只有在调用new方法时才会创建类的实例。
    3. 类实例创建过程:父子继承关系,先父类再子类。父类的静态->子类的静态->父类的初始化块->父类的构造方法->子类的初始化块->子类的构造方法
    4. 类实例销毁时候:首先销毁子类部分,再销毁父类部分。
     PS:
      1、加载:类加载器加载类文件
      2、验证:确保class文件结构没有被串改
      3、准备:为类的讲台变量分配内存,将其初始化默认值。
      4、解析:常量池内的符号引用转为直接引用,将法中对其他方法的应用缓存方法区中对内存地址的引用。
     
     
    39、String,StringBuffer,StringBuilder有什么不同?
      1、线程安全方面:stringbuffer是线程安全的,其他都不是。
      2、速度:stringbuilder是最快的。
      3、内存空间:string每次操作都会创建一个新对象(另string常量池优化),其他都是可变类(有缓存会自增容量)
    40、String和StringBuffer的实现?
    41、Volatile关键字作用?除了保证数据可见性,还有其他什么使用方式?  
      1、作用主要有两个:保持内存的可见性,防止指令重排序
      2、保持内存可见性:通过集中原子操作完成工作内存和主内存的交互 
        a、unlock:作用于主内存,解除独占状态。
        b、read:作用主内存,把一个变量的值从主内存传输到线程的工作内存。
        c、load:作用于工作内存,把read操作传过来的变量值放入工作内存的变量副本中。
        d、use:作用工作内存,把工作内存当中的一个变量值传给执行引擎。
        e、assign:作用工作内存,把一个从执行引擎接收到的值赋值给工作内存的变量。
        f、store:作用于工作内存的变量,把工作内存的一个变量的值传送到主内存中。
        g、write:作用于主内存的变量,把store操作传来的变量的值放入主内存的变量中。
        h、lock:作用于主内存,把变量标识为线程独占状态。
        valatile的特殊规则:
          a、read、load、use动作必须连续出现 :每次读取前必须先从主内存刷新最新的值。
          b、assign、store、write动作必须连续出现 : 每次写入后必须立即同步回主内存当中。
      3、防止指令重排序:编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理重排序。
        a、valitile写操作:storestore 写操作 storeload
        b、volatile读操作: loadload 读操作 loadstore
       PS:
        1、内存屏障:解决指令重排序问题,多CPU的情形下可以强制同步cpu中缓存不一致的情况。
        2、缓存一致性原则:
        3、锁、内存屏障与一致性
          a、只有一个cpu是,cup只会从自己的缓存中读取数据,加入缓存丢失,从主内存读取数据到缓存中,最终内存数据都是一致的。
          b、多核情况下,每个CPU都有自己的缓存,需要读到最新数据就要解决某CPU更新了缓存端但未写回内存,其他cpu看不到的问题。
          c、此时就引入了缓存一致性协议(作用域缓存行):
            modifiled(该cup已经独占了该缓存段,并做了修改,其他核心读取时,此数据必须刷入主存),
            exclusive(数据已经读入cacheline,并且只有该CPU拥有它,可以直接修改数据)
            shared(多个cpu共享某内存的数据,当cpu需要修改数据时,需要提交RFO请求获取数据的独占全,即进入exclusive状态才能进行修改)
            invalid(share 状态下,当前cpu同意了其他cpu申请写的时候,变成该状态)
            即:当前cpu起始状态(shared),发起rfo,(被接收后当前cpu变成exclusive,其他接收的cpu变成invalid),当前cpu修改了数据就变成modified(这个状态要求读取当前缓存段前,当前数据必须同步到主存)
          d、问题:如果当前cpu忙,没接收别的cpu的RFO,则那个发起请求的cpu就无事可做了,这就降低了性能。
          e、为了解决上述问题,添加了两个类似缓存的东西,Store buffer和invalidate queue。
            这样当cpu需要写缓存行时就将写指令丢入storebuff,去干别的事,等RFO受到回应时,该指令才执行。
            cup收到其他核心的RFO指令后,会立即回应但相应的失效操作指令(较忙时)会放入到invalidate queue中
          f、这套机制实现了异步,带了性能提升的同时也带来了问题,在发去RFO请求的cpu执行写时(先读store buffer,再读缓存)只对当前CPU可见(其他cpu可能没有执行自己invalidate queue中的失效指令)。
          g、为了解决这个问题,引入了读写屏障。写屏障保证写屏障前在所有store buffer的指令都真正的写入到缓存,读屏障保证在读屏障前所有invalidate queue中所有的无效化指令都执行。这保证了不同核心上,缓存的强同步。
          h、在锁的实现上,一般lock都会加入读屏障,保证后续代码可以读到别的cpu未回写的缓存数据(应该是做缓存失效吧?),而unlock会加入写屏障,将所有未回写的缓存回写(确保到modified状态吧)。
        4、内存屏障保证缓存的一致性
      详参:http://gocode.cc/project/9/article/128
    42、ThreadLocal做什么的?如何使用?源码是如何实现的?get()方法?  
      1、并发背景下,通过将相关对象封闭到执行线程中来解决并发问题,是一种以空间换时间的做法。
      2、具体实现就是为当前执行线程维护一个ThreadLocalMap对象(维护到执行线程中,每个线程创建一个),其中维护一个entity对象数组(继承weakReference),其中threadlocal对象为key,设置的对象为value,以实现thread中维护多个threadLocal对象
      3、一般在类中使用final static 修饰
      4、get方法使用当前threadlocal对象实例从当前执行线程的threadlocalmap中获取存放的对象。
      5、线程的threadLocalMap中之索引维护一个entity数组,是因为一个线程可以持有多个threadlocal
      PS:
        1、threadLocalMap中维护的entity继承了weakReference,功能就是当threadlocal被回收后就变成null->value
        2、threadLocal在get时会清除key为null的entity
      
    43、ConcurrentHashMap源码?JDK1.6,1.7,1.8中分别有什么不同?
      1、负载因子0.75,默认容量16,当大于16*0.75时扩容一倍。
       2、1.6和1.7无太大差别,只是在new hashmap时1.6确实开辟了内存空间,1.7采用的是懒汉式,在put时才构造。
       3、1.7对待并发采用的是基于分片+链表数组结构,要经过两次hash碰撞,分片是用的是可重入锁;1.8则摒弃了分片直接使用synchronized加到桶中第一个元素上,cas用于交换元素。
       4、1.8对待增长,当链表长度大于8时但桶大小小于64时扩充容量,大于64时将桶转为红黑树操作。
      PS:
        1、node中的value和next都用volatile修饰。
        2、hash碰撞就是两个对象的key的hashcode一样,这时候如何获取他的value。
        3、1.8中的spread方法对hash做了扩展,将高16位和当前hash做异或操作,解决选择桶的下标时总是与低4位运算,造成的表的长度较小问题,从而减少系统的开销(hash碰撞情况)
        4、桶的大小超过64时,使用红黑树也是当发生较大碰撞时降低冲突的考虑。
    44、分布式程序调用链
      全链路性能监控从整体维度到局部维度展示各项指标,将跨应用的所有调用链性能信息集中展现,可方便度量整体和局部性能,并且方便找到故障产生的源头,生产上可极大缩短故障排除时间。
    Google Dapper
      1、背景:随着微服务的应用,业务调用链越来越复杂,一个请求可能涉及到几十个服务的系统服务,涉及到多个团队的业务系统。当遇到问题需要定位时,也会产生一系列麻烦。
      2、解决方案:通过调用链,把一次请求调用过程串联起来,实现对请求路径的监控,便于快速定位。
      3、调用链显示内容:各个调用环节的性能分析(如各API使用时间、使用堆栈)、调用个环节依赖关系还原、SQL打印、IP显示。
      4、通用框架:google的Dapper,淘宝的鹰眼,京东的九头蛇。
      5、调用链原理:
        a、请求生成一个全局TranceId,通过TraceId可以串联起整个调用链,一个tranceId代表一次请求。
        b、除了TranceId,还需要SpanId记录调用的父子关系,span是自己生成,透传子调用成为parentId
        c、一个没有parentId的span是调用链入口
        e、这个调用过程中每个请求都要透传tranceId和spanId
        f、要查看某次完整的调用链只要根据TranceId查出所有调用记录,然后通过parentId和spanId组织起整个调用父子关系。
      PS:具体参见
        https://blog.csdn.net/Damon__Wang/article/details/81782911
        https://blog.csdn.net/Damon__Wang/article/details/82051631
     
    45、生产环境如何定位内存溢出?CPU使用率过高?Linux命令?
     1、内存溢出的常见情况分几种:堆溢出(java heap space),PermGen space(方法区),不能创建本地线程(unable to create new native thread),回收执行了太长时间、超过限制(GC overhead limit exceeded)。初步判断区域
        2、关键还在于分析dump文件。这个可以提前设置+HeapDumpOnOutOfMemeryError,或jmp
        3、使用Jprofile打开dump文件,这里可以看到大对象和具体的引用
     4、当然结合gc的日志更好(-XX:+PrintGC)
     
    46、Netty 
        Netty 是一个基于NIO的客户、服务器端编程框架。
      Netty是什么?
        1)本质:JBoss做的一个Jar包
        2)目的:快速开发高性能、高可靠性的网络服务器和客户端程序
        3)优点:提供异步的、事件驱动的网络应用程序框架和工具
        4)特点:
          a、并发高
          b、传输快:领拷贝,使用直接缓冲区
          c、封装好:比较简洁,使用链式调用
        通俗的说:一个好使的处理Socket的东东
     
    47、kafka 事务 性能
    48、内存屏障 
        Java内存模型中volatile变量在写操作之后会插入一个store屏障,在读操作之前会插入一个load屏障。一个类的final字段会在初始化后插入一个store屏障,来确保final字段在构造函数初始化完成并可被使用时可见。
     PS:
      1、为了提高性能,处理器设计了多级缓存,cpu的缓存和共享的缓存。cpu把处理结果发到缓存后就可以做其他处理了,但这也造成了可见性问题。
      2、缓存操作是分成缓存行,缓存一致性原则(MESI)通过定义独占、共享、修改、失效等缓存行的状态来协调多个处理器对其的操作。(内存级别)当共享的同一缓存端的数据发生变化时其他cpu都会得到通知。
      3、为了杜绝内存不一致的情况(如指令重排序,cpu和编译阶段都会),又引入了内存屏障来确保一致性。内存屏障分为LoadLoad屏障,LoadStore屏障,StoreStore屏障,StoreLoad屏障,都是确保后一个操作前前一个操作必须完成(指令在中间,分割对应的操作指令,确定前后的一个关系)。(指令级别)
      4、lock前缀指令具备内存屏障功能(load&store)的的CUP指令,执行时锁住子系统来确保执行顺序,甚至跨多个CPU。
      5、JVM中,除了内存屏障,还使用先行发生原则来确保指令的前后关系。如对象锁释放必须先于加锁发生,start先于thread内所有指令执行前发生。
      6、cas操作可以理解为是lock指令(锁着内存)+系统cas指令来实现的。
      7、
     
    49、redis面试题

      1、Redis有哪些数据结构?

           a、基本类型:字符串,数值         
        b、集合:字典Hash、列表List、集合Set、有序集合SortedSet,链表      
        c、其他:如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、   Pub/Sub。Redis Module,像BloomFilter,RedisSearch,Redis-ML。

      2、使用过Redis分布式锁么,它是什么回事?

          a、先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

      3、如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

          a、这个锁就永远得不到释放了。
       b、set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的! 如redis的lua脚本

      4、假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

          a、使用keys指令可以扫出指定模式的key列表。

           5、如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

             a、redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。         
        b、这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,         
        c、在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

      6、使用过Redis做异步队列么,你是怎么用的?

          a、一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

      7、可不可以不用sleep呢?

          a、list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

      8、能不能生产一次消费多次呢?

          a、使用pub/sub主题订阅者模式,可以实现1:N的消息队列。publish/subscribe

         9、pub/sub有什么缺点?

          a、在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。(消息是即时的,不做存储)

      10、redis如何实现延时队列?

          a、使用sortedset和string,数据作为拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。 
       b、string中uuid作为key,data作为value存放数据。
       c、sortedset中,string中的uuid作为key,时间戳作为value存储。获取的时候使用zrangebyscore排序回去最早的数据。
       d、应对的场景就是:并发对数据库更新时,锁表会导致效率低,使用这种延迟操作可以解决这种效率低的问题。

      11、如果有大量的key需要设置同一时间过期,一般需要注意什么?

          a、如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。
       b、一般需要在时间上加一个随机值,使得过期时间分散一些。(过期时间=固定时间+随机值)

      12、Redis如何做持久化的?

          a、bgsave做镜像全量持久化,aof做增量持久化。     
       b、因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。    
       c、 在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

       13、如果突然机器掉电会怎样?

          a、取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。     
       b、但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

      14、bgsave的原理是什么?

          a、fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,     
       b、子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

      15、Pipeline有什么好处,为什么要用pipeline?

          a、可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。     
       b、使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。

      16、Redis的同步机制了解么?

          a、Redis可以使用主从同步,从从同步。     
       b、第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,     复制节点接受完成后将rdb镜像加载到内存。     
       c、加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程

      17、是否使用过Redis集群,集群的原理是什么?

          a、Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。    
       b、 Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
        
    50、动态代理有几种,Jdk与Cglib区别
       1、实现方式:
        a、jdk通过反射机制生成一个实现代理接口(参数中interfaces里所有接口且继承了Proxy的代理类)的匿名类,在调用具体方法前调用invokeHandler的invoke处理
          a.1,Proxy.newProxyInstance(classloader,target.getclass.getInterfaces(),target(implemants InvocationHandler)
          a.2、生成一个实现了参数中interfaces里所有接口且继承了Proxy的代理类的字节码,然后用参数中的classloader加载这个代理类。
          a.3、使用代理类父类的构造函数Proxy(invocationHandler)来创建一个代理类实例,将我们自定义的InvocationHandler的子类传入。
          a.4、返回这个代理类的实例。
        b、cglib利用ASM开源包,直接修改代理类class的字节码生成子类来重写其方法。
       2、目标类的限制:
        a、jdk只能正对实现了接口的类
        b、cglib针对有误实现接口的都行
        c、cglib不能重写final类或方法
       3、性能:jdk是越来越快
      ps:
        1、Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能
        2、spring中的应用:<aop:aspectj-autoproxy proxy-target-class="true"/>:cglib
    51、数据库三大范式
        1、字段不可以再分:也就是说一个列名下的值只能是一种类型,如号码中既有手机号、座机号等都不行
      保证字段的原子性,也是关系型数据库的标准(面向对象)
      2、有主键,非主键字段依赖主键(有一个主题):一个主键代表一条记录,只能有一个主题;也就是说一条记录中不能即存在学生的记录信息也存在课程的主题信息
      唯一性
      3、非主键字段不能相互依赖(都是平级的):每列都与主键有直接关系,不存在传递依赖。
    52、左连接和右连接说一下,内连接呢
      1、左连接:使用left join on,匹配时匹配表中没能匹配上的也显示,驱动表中只显示匹配的上的
      2、右连接:使用right join on,效果和左连接相反
      3、内链接:使用(inner) join on ,匹配表和驱动表都只显示匹配的上的。
      4、优化:都需要优化驱动表
      5、性能:左右关联比内关联要好一点。
    53、数据库索引有几种
    54、数据库引擎你认识几种,innodb 和myisam 区别,你的项目用到哪个引擎
     

      PS:

        1、指数据库事务正确执行的四个基本要素:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)    
        2、数据库引擎是用于存储、处理和保护数据的核心服务。利用数据库引擎可控制访问权限并快速处理事务,从而满足企业内大多数需要处理大量数据的应用程序的要求。
        3、查询数据库支持的引擎:show engines
        4、查询数据库使用的引擎:show variables like '%storage_engine%';
    55、若hashcode方法永远返回1会产生什么结果
      1、首先、编程时就坚持修改了equals就要修改hashchode要求
      2、hashcode在对象的对比和一些集合类中会被用到,如hashmap
      3、hashcode的存在也是一种优化程序的体现,如hashmap中的桶,如果所有的hashcode相同,那hashcode会构成一个线性表,导致性能降级
      
      
      PS:为什么选择31做乘数     
        1、不大不小:为了避免hash重复需要选择一个大一点的质数做乘数,否则就会导致hash值重复的较多,100以上的数乘下来容易超过int的范围。
           2、31,33,37,39,41中31可以被jvm优化,作为位移计算,这种很高效。
     
    56、Error与RuntimeException的区别
      二者的不同之处:
        Exception:
          1.可以是可被控制(checked) 或不可控制的(unchecked)
          2.表示一个由程序员导致的错误 
          3.应该在应用程序级被处理
        Error:
          1.总是不可控制的(unchecked)
          2.经常用来用于表示系统错误或低层资源的错误
          3.如何可能的话,应该在系统级被捕捉
     
    57、引用计数法与GC Root可达性分析法区别
      1、引用计数:类似给对象添加一个计数器,当对象被引用的时候就在自己的计数器上加一,当某个引用的对象被回收后。引用为零的对象就会被回收
        优点:简单、高效
        缺点:相互引用不能被识别(a.instance=c,b.instance=a,这两项都不再被使用,但却无法回收)
      2、可达性分析:从根开始遍历他的引用,当某个对象到根不可达时则对象该引用可以回收
        根节点:方法区的常量、静态变量
            虚拟机栈区的变量列表的引用
            本地方法栈中的引用
            本地方法栈中引用的对象
      PS:
        1、五大分区:
            
          a、程序计数器:记录当前执行程序的位置,改变计数器的值来确定执行下一条指令,如循环、分支、方法跳转    
          b、虚拟机栈:每个线程都会创建一个虚拟机栈,通过压栈出栈的方式执行方法调用。分局部变量表、操作数栈、动态链接、方法出口等。
          c、本地方法栈:native方法
          d、堆:存放对象实例
          e、方法区:用于存放已被虚拟机加载的类信息,常量,静态变量等数据。
          f、直接内存:并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。

        2、各区域的使用

          

          

    58、双亲委派机制说一下
      1、某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
      2、全盘责任委托:一个类如果被某个类加载器加载,那么除非指定别的加载器,否则这个类关联的类也有这个类加载器加载。
      3、由下向上询问是否加载,由上向下尝试加载。
      4、线程上下文加载器:针对java的spi情况,也就是引导类加载的类型需要使用二方包的情况,如jdk
    59、算法题:找出一个数组中第100个小的数字(堆思想解决)  
    60、看你项目用到策略模式和工厂模式,说一下区别
      1、比喻:去必胜客吃披萨,工厂模式关注的是最终能吃到披萨,策略模式关注在关注的是披萨是如何做的。
        a、用途不同
          工厂是创建型模型,他的作用是创建对象
          策略是行为型模型,他的作用是让一个对象在许多行为中选择一种行为。
        b、关注点不同
          一个关注对象的创建
          一个关注行为的封装
        c、解决不同的问题
          工厂模式,它接受指令,创建出符合要求的实例。它主要解决的是资源的统一分配,将对象的创建独立出来,让对象的创建和具体的使用客户无关。
          策略模式,它为了解决策略的切换与扩展,让策略模式的变化独立于使用策略模式的用户。
        d、工厂相当于黑盒子,策略相当于白盒子
      PS:
        1、设计模式有三种类型:创建型(解决:对象的创建和具体的使用解耦),行为型(描述了对象和类的模式,以及它们之间的通信模式)
      ,组合型(解决怎样组装现有的类,设计他们的交互方式,从而达到实现一定的功能的目的)。
        2、项目中:缓存的使用,定义一个公用的缓存操作页面,在缓存工厂中通过配置缓存类别获取具体的缓存实现,redis,tair
        3、简述:
        

        

        

     4、工厂模式有三种:简单工厂,工厂方法,抽象工厂。
        a、简单工厂(静态工厂):将类的实例化转交一个工厂,具体的行为由子类决定(即如何构造该实例);解决的问题:怎么构造一个对象有工厂内部决定,如是否设置某个属性等,用于隐藏实现细节。如计算器,获取单例等
        b、工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。不同的对象使用不同的工厂
        c、抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。解决的问题:切换数据库
      5、策略模式:它定义了算法家族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户
    61、模板方法模式
      定义:一个模板结构,将具体内容延迟到子类去实现。
      解决的问题:
        1、将复用性高的代码抽取到抽象父类中,具体的操作在继承的子类中定义。
        2、父类调用子类操作,子类扩展不同的行为,这样即实现了控制反转也符合开闭原则。
      使用场景
        试卷,apache velocity
    62、开闭原则懂吗,说一下
    1、设计模式中提到的一个概念。
    2、目的指导我们如何建立一个稳定的、灵活的系统。
    3、开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改是封闭的。(可以新增、尽可能少的修改)
     
    63、NIO说一下你的理解
      1、NIO也就是非阻塞IO,相对于BIO而言,当然也有AIO
      2、打个比喻水工接水的比方:BIO就是多个多个水笼统,每个水龙头都有一个接水工,每个接水工只有接到水才才会做后续处理;NIO是为每个水龙头添加了一个水缸且只有一个水工负责查看接到水刚,当某个水缸接满水,水工就先处理这个水缸的后续处理。
      3、这个水缸就缓冲区buffer,水工就是选择器Select,水工就看查看某个水龙头时就等于连接了通道channel。
      4、具体的实现时,所有的通道都注册到选择器中,选择器轮循查看通道中的数据是否准备就绪,而读出和写入都是直接到了缓冲区。
      5、这个地方提供了一个新的概念:直接缓冲区,绕过内核地址空间(系统空间),直接将数据的物理地址映射到用户地址空间(JVM)。
      ps:之前的数据拷贝都是先写到物理内存,然后再拷贝到jvm中。
    64、AtomicInteger底层原理
      1、AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。
      2、其中存储的value使用了volatile修饰,操作时使用cas无锁算法。
      ps:
        1、具体实现就是使用Unsafe。
        2、它有如下功能:内存管理(分配、释放内存),非常规的对象实例化(无需调用构造器),操作类、对象、变量(指针偏移获取),数组操作(指针偏移),多线程同步(对象锁机制,cas操作),线程挂起与恢复,内存屏障(loadFence、storeFence、fullFence)
        
    65、CAS机制是什么?有什么缺点,会出现什么问题?
    CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
    CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B,若预期值A和内存值V相同就把内存值修改成新值B
    CAS的缺点:
    1.CPU开销较大
    在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
    2.不能保证代码块的原子性
    CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
    3.ABA问题
    这是CAS机制最大的问题所在。

      PS:

      什么是ABA问题?

     引用原书的话:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令就可能出现这种问题,在CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作,在某些算法中,如果V的值首先由A变为B,再由B变为A,那么CAS将会操作成功。
        • 怎么避免ABA问题?
    Java中提供了java.util.concurrent.atomic中AtomicStampedReference和AtomicMarkableReference来解决ABA问题。 
    • 底层实现?
    多CPU的情况下的cas操作是CPU提供的支持。
    1、这和volatile的底层实现是相同的
    2、底层:这个读取、对比以及设置的操作私用lock前缀保证唯一性。
    66、本地缓存过期策略怎么设置,一致性怎么保证?
      一、一致性(如下是主动的情况)
        1、当数据时效性要求比较高时,需要保证缓存与数据库保存一致,而且需要保证缓存节点和副本中的数据也要保存一致,不能出现差异现象。
        2、这就比较依赖缓存过期和更新策略,一般会在数据发生更改时,主动更新缓存中的数据或者移除对应的缓存。
        3、一般的缓存使用
               方案一(先更新缓存,再更新数据库):      
          1、是不可用的      
          2、首先库存是以数据库为准的,如果缓存更新完成但数据库未更新完成且库存少于缓存则会造成负库存。
          3、若缓存更新成功,数据库更新失败则缓存一直都是脏数据。
        方案二(新更新数据库,再更新缓存)
          1、不可取
          2、如果两个线程并发执行,会存在A更新数据库,B更新数据库,B更新缓存,A更新缓存(网络原因),则数据库中的就是脏数据。
          3、针对那种依赖前值计算后更新的场景,无疑是浪费性能。
        方案三(先删除缓存,再更新数据库:更新数据库失败对业务也没什么影响)
          1、待优化
          2、存在A删除了缓存,B发现缓存不存在从数据库查询到旧值写到了缓存,A将新值写入到数据库:此时缓存和数据库不一致。
          3、可以采用延时双删策略:先淘汰缓存,再写入数据库,休眠1秒,再次淘汰缓存。此时可以确保上述B的写入被删除。(休眠是为了确保读请求结束,写请求可以删除对请求造成的脏数据)
          4、第二次删除可以采用新线程来做以避免降低吞吐。
          5、第二次删除失败了的解决方案详见方案四
        方案四(先更新数据库,再删除缓存)
          1、待优化
          2、缓存刚好失效,A查询数据库得到旧值,b将新值写入数据库并删除缓存,A将旧值写入缓存(若b的写入数据库操作要足够端以至于B删除缓存早于A写入缓存发生:概率低)
          3、2的解决方案:异步延时删除,缓存设置有效时间
          4、针对缓存更新失败的解决方案:
            

          删除失败后,将删除key的消息发送到消息队列,重试删除直到成功(这对业务代码有侵入)

        

            使用mysql的中间件如Canal,单启一个独立的程序去处理。

      二、过期策略
        1、缓存过期策略大致分两种:可以通过过期时间来控制内容过期的情况和无法通过过期时间来控制内容过期的情况。
        2、可以通过过期时间来控制内容过期的情况
             a、设置绝对过期时间(秒杀商品)。
             3、无法通过过期时间来控制过期的情况
            a、设置滑动过期(针对时效性不强的):在读取缓存的时候将该缓存项的过期时间在当前时间的基础上延后指定长度的事件。(如文章有评论就延长过期时间的情况)
      
      ps:
        1、缓存满了,从缓存中移除数据的策略
          a、新进先出算法
          b、最久未使用算法
          c、最少使用算法
        2、缓存并发问题
          a、缓存过期后将尝试从后端数据库获取数据,当数据获取到更新完成这段会有多个线程到数据库后去数据,对数据库造成极大的冲击,甚至导致血崩。
          b、此时就要加锁,到后台数据库请求数据要先尝试获取锁,未获取锁的线程只能等待。
          c、针对缓存过期也要选择一个范围内随机过期,不能全部集中到某个时间段。
        3、缓存穿透(不存在的key,缓存不起作用)
          a、查询一个不存在的数据,由于缓存是不被命中被动写的(如果没有就查数据库),并且出于容错考虑,如果存储层查不到数据就不写入缓存,这将导致每次都要查询存储层查询,失去了缓存的意义。在流量大的时候,可能db就挂掉了。
          b、缓存空对象,过期时间段,不超过五分钟。
          c、单独过滤处理:将对应数据为空的key进行统一存放
          d、布隆过滤器:将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉
        4、缓存颠簸:缓存节点故障导致,使用hash算法解决
        5、缓存雪崩
          a、缓存采用了相同的过期时间,导致缓存再同一时刻同时失效,DB瞬间压力过大崩溃。
          b、在缓存失效的基础上添加1-5分钟的随机值
          c、从应用架构角度,我们可以通过限流、降级、熔断等手段来降低影响,也可以通过多级缓存来避免这种灾难
        6、缓存击穿(一个存在的key,在缓存过期的一刻,同时有大量的请求)
          a、使用互斥锁:使用setnx设置值,成功消息返回的才去查询数据库(成功后设置有效期)
        7、缓存无底洞现象
        8、其他
          a、目前主流的数据库、缓存、Nosql、搜索中间件等技术栈中,都支持“分片”技术,来满足“高性能、高并发、高可用、可扩展”等要求
          b、命中:可以直接通过缓存获取到需要的数据。
     
      三、cache的使用
      四、掌医的实现
      五、商品秒杀逻辑
  • 相关阅读:
    demo16-打印名字
    demo15-获取标签里面的值
    bufferedReader 读取文件第一行第一个字符丢失问题
    Python的逻辑控制true/false和循环
    python中的对文件的读写
    Python初学习:简单的练习题
    VMware安装的Linux系统忘记密码 怎么修改root密码
    ZooKeeper伪分布式集群安装及使用
    Jersey实现跨服务器上传图片:UniformInterfaceException:403 Forbidden
    Spring中的一些面试题
  • 原文地址:https://www.cnblogs.com/ws563573095/p/10117924.html
Copyright © 2011-2022 走看看