面试题整理 2018

面试题整理

主要记录一下被问到的问题,方便以后使用,哈哈哈哈

1.AOP实现原理

  • AOP是什么
      AOP技术利用一种称为“横切”的技术,解剖封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,这样就能减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。
      AOP的实现主要是依靠动态代理,通过代理模式,对方法执行前后或执行异常进行对应的处理。
    切入点:这个可以简单的理解为在业务上有共同特点的方法,通过切面对这些方法执行一些相同的操作。
      AOP中spring会管理所有的代理对象,在调用某一个切入点的时候,spring会自动调用代理对象,在代理对象中调用你需要的方法。

如果想深入理解,可以看一下这几个文章
AOP设计基本原理
AOP实现原理(基于JDK和基于CGLIB)
探析Spring AOP(三):Spring AOP的底层实现原理

2.IOC实现原理

  控制反转(Inversion of Control,英文缩写为IoC)是框架的重要特征,如果程序员清楚框架和工具箱的差别,可以无视本术语。
  可以使用C语言设计框架,因此,IoC并非面向对象编程的专用术语。
  Spring的DI/依赖注入(Dependency Injection,简称DI)仅仅是一个工具箱,因此与IoC没有关系。

引入一个大牛的解释

1.1、IoC是什么
  Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,
控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  ●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  ●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

1.2、IoC能做什么
  IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

  其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

  IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

1.3、IoC和DI
  DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

  理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  ●谁依赖于谁:当然是应用程序依赖于IoC容器;

  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

  IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

  看过很多对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感觉,读完之后依然是一头雾水,感觉就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每一个字,读完之后给人一种豁然开朗的感觉。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。

3.Redis中的数据类型,及每种数据类型对应java中的数据类型,举例使用场景

  • String
      数据结构是简单的key-value类型,value其实不仅是String,也可以是数字.
      String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。
  • hash
      缓存对象时,主要有两种方式:
      1.将对象序列化后存储。但是序列化和反序列化会增加系统的开销,并且会引入一定的并发问题。
      2.将对象的id与属性名作为key,属性值作为value进行存储,但是当数据量较多时,会浪费很多的内存。
      redis中的hash可以很好的解决这个问题。
      hash的key-value中的value是一个hashmap,当存储一个对象时,可以将对象的属性名和属性值分别作为hashmap的key和value进行存储。这样在修改的时候可以直接通过key和value中的field修改对象的某一个属性值,不需要全部取出,修改后再全部存入。
      需要注意的是,当hash的hashmap成员较少时,redis为了节省内存,会以zipmap的方式存储(此特性可以对redis的内存进行优化),当值较多时才会采用真正的hashmap进行存储。
      hashmap的成员多少可以通过配置文件hash-zipmap-max-entries进行配置,1000时性能较好,超过1000后,cpu开销较大,注意redis的单线程,防止出现响应时间过长。

    内存优化这里可以看一下这篇文章:
    redis利用hash节约内存

  • list
      Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。
      Lists 就是链表,相信略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。Lists的另一个应用就是消息队列,
    可以利用Lists的PUSH操作,将任务存在Lists中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作Lists中某一段的api,你可以直接查询,删除Lists中某一段的元素。
    实现方式:
      Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

  • set
    set的使用场景也挺多的,例如粉丝的计算,共同好友等等。
    set的数据结构底层是hashmap,但是是value永远为null的key-value结构,这样便做到了set的唯一性。
    set的api中提供了交集并集等方法,使用上比较方便。
  • zset
    zset和set类似,是不过是添加了一个排序字段。

一张图片,看完你就能对redis的数据结构有一个大体的了解了
redis数据结构

4.Nginx优化

  • nginx简介
    Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
    注意点1:
    nginx是多进程的应用,主要有两种模式,单工作进程和多工作进程。
    单工作进程是一个master进程和一个worker进程(当然可能还有和cache加载进程和cache管理进程,但这不是主要的)。
    多工作进程是一个master进程和多个worker进程(也可能存在cache相关的进程)。
  • nginx性能优化及注意事项
    1.worker进程数量配置
      工作进程数量主要和cpu的核心数有关,一般情况下,工作进程数量不大于cpu数量。否则会导致上下文切换造成cpu的消耗。
      另外,此处可以再进一步优化,将工作进程和cpu进行绑定,避免cache失效和cpu切换的资源消耗。
      同时多工作进程会带来一个问题,那就是惊群问题。惊群问题主要是由于多个工作进程去竞争一个请求造成的。解决惊群问题比较简单,可以设置accept_mutex,默认是打开的。
      另外,针对一个请求,只会有一个工作进程去处理。这么做的好处是避免锁的开销,每个进程单独处理,不需要资源共享等。独立进程会降低风险,加入某一个工作进程挂掉了,那么在其他工作进程中的请求不会受到任何影响。master会重新创建进程。
      针对单个工作进程中最大的连接数,这个默认值是512,但是对现在的服务器来书,1024或者更高,是完全可以的。
    2.io优化
      nginx使用的瓶颈经常出现在io上,所以对io的优化至关重要。
      服务器内核版本的选择,建议是2.6+,这个版本默认支持aio(异步非阻塞io事件处理机制)。
      首先是日志,针对这一部分,可根据个人情况有选择性的关闭和这优化。
      Open_file_cache,这个可以对文件进行缓存,能有效的较低io操作,减少阻塞。
      Buffers
      配置Nginx缓存的大小是一个非常重要的事情. 如果缓存大小设置的太小, Nginx将不得不把上游(用英文upsteams会更好)的相应结果存放到临时的缓存文件里面,这将会同时增加IO的读写操作, 而且流量越大问题越多.
      client_body_buffer_size指令用来指定处理客户端请求的缓冲区大小,?这个代表了访问请求的body. 这是用来处理POST的数据,也就是通过提交表单,文件上传等请求的数据. 如果你需要处理很多大的POST请求的,你必须确保缓存区要设置的足够大.
      fastcgi_buffers?和?proxy_buffers?指令用来处理上流(upstream)的响应结果, 也就是PHP Apache等.它的概念其实和上面提到的差不多, 如果缓冲区不足够大数据将在返回给用户使用之前被保存到磁盘上. 注意Nginx将这个buffer数据同步的传输给客户端之前,有一个缓存上限, 保存到磁盘也同样受限. 这个上线是通过fastcgi_max_temp_file_size和proxy_max_temp_file_size来设置的. 另外对于代理的连接你也可以通过把proxy_buffering设置成off来彻底的关闭缓存.(通常这不是一个好办法).
      文本文件的压缩,例如css、js等,可以使用gzip进行压缩,能有效的降低io。
      系统open file需要在服务器进行调整,一般服务器都偏低,可以适当的调高。
    至于其他的一些配置上的优化,可以查看下面的文章
    nginx优化1
    nginx优化2

5.静态代理和动态代理

  • 静态代理
    静态代理主要用于对原有业务的补充(针对补充修改比较少的时候)。
    举例说明:
//公共接口
public interface Action {
    public void doSomething();
}
//原对象
public class RealObject implements Action {
    public void doSomething() {
        //doSomething
    }
}
//需求:在调用RealObject中doSomething方法前后分别打印日志,
//这时因为某些原因不能修改RealObject中的doSomething方法,此时可以使用静态代理
public class ProxyRealObject implements Action {
    private Action realObject;
    //获取原对象
    public ProxyRealObject(Action realObject){
        this.realObject=realObject;
    }
    //该处通过持有的原对象调用原对象的方法,同时在调用原方法的前后实现自己的需求
    public void doSomething() {
        log.info("打印日志");
        realObject.doSomething();
        log.info("打印日志");
    } 

}

静态代理的优势:扩展原功能,不侵入原代码。
静态代理的缺点:当有多个类需要进行类似的修改时,为每个类创建一个代理对象或者说一个代理对象实现多个action持有多个对象,前者太麻烦,后者会导致代理对象无限制的膨胀,都不合适。

  • 动态代理
//接口
public interface Action {
    void doSomething();
}

//接口实现类
public class RealObject implements Action {
    @Override
    public void doSomething() {
        //doSomething
        System.out.println("realObject doSomething");
    }
}

//动态代理对象,实现InvocationHandler接口
public class DynamicProxyHandler implements InvocationHandler {

    private Object cusProxy;

    public DynamicProxyHandler(Object proxy) {
        this.cusProxy = proxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法执行之前的操作
        System.out.println("动态代理方法执行前");
        Object object = method.invoke(cusProxy, args);
        System.out.println("动态代理方法执行后");
        //方法执行之后的操作
        return object;
    }
}

//动态代理使用
public class Test002 {
    public static void main(String[] args) {
        Action realObject = (Action) Proxy.newProxyInstance(
                Action.class.getClassLoader(),
                new Class[]{Action.class}, 
                new DynamicProxyHandler(new RealObject()));
        realObject.doSomething();
    }

}
执行结果如下:
动态代理方法执行前
realObject doSomething
动态代理方法执行后

6.springboot自动装配原理

7.消息队列原理及常见几种产品的比较

8.深拷贝浅拷贝

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值