美文网首页
Java 校招面试

Java 校招面试

作者: 寒沧 | 来源:发表于2018-12-06 16:13 被阅读40次

    Java 校招面试


    Spring IOC

    IOC 是 Inversion of Control 的缩写,多数书籍翻译成“控制反转”。

    IOC 理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。

    软件系统在没有引入 IOC 容器之前,如图 1 所示,对象 A 依赖于对象 B,那么对象 A 在初始化或者运行到某一点的时候,自己必须主动去创建对象 B 或者使用已经创建的对象 B。无论是创建还是使用对象 B,控制权都在自己手上。

    <center> 此处输入图片的描述 此处输入图片的描述

    </center>

    软件系统在引入 IOC 容器之后,这种情形就完全改变了,如图所示,由于 IOC 容器的加入,对象 A 与对象 B 之间失去了直接联系,所以,当对象 A 运行到需要对象 B 的时候,IOC 容器会主动创建一个对象 B 注入到对象 A 需要的地方。

    <center> 此处输入图片的描述 此处输入图片的描述 </center>

    通过前后的对比,我们不难看出来:对象 A 获得依赖对象 B 的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

    DI 依赖注入

    2004 年,Martin Fowler 探讨了同一个问题,既然 IOC 是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由 IOC 容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现 IOC 的方法:注入。所谓依赖注入,就是由 IOC 容器在运行期间,动态地将某种依赖关系注入到对象之中。

    IOC 和 DI 的区别

    理解以上概念需要搞清以下问题:

    1. 参与者都有谁?
        一般有三个参与者。 1)是某个对象;2)是 IOC/DI 的容器;3)某个对象的外部资源。
        其中 1)某个对象指的是任意的,普通的 Java 对象;2)IOC/DI 容器指的是指用于实现 IOC/DI 功能的框架程序;3)对象的完毕资源指的是对象所需要的,但是需要从外部获取的统称为资源;比如一个对象的属性为另外一个对象,或者是对象需要的是一个文件资源等等。
    2. 依赖: 谁依赖于谁?为什么需要依赖?
        对象依赖于 IOC/DI 的容器。 因为对象需要 IOC 来提供对象所需要的外部资源。
    3. 注入:谁注入谁?到底注入什么?
        IOC/DI 容器注入某个对象。 注入某个对象所需要的外部资源。
    4. 控制反转:谁控制谁?控制了什么?为什么叫反转(有反转就应该有正转)?
        是 IOC 容器控制对象,主要是控制了对象实例的创建。
        反转是针对正向而言,正向是针对常规下的应用程序而言的。正规应用程序下,如果要在 A 里面使用 C,则会直接创建 C 的对象。,也就是说,是在 A 类中主动去获取所需要的外部资源 C,这种情况被称为正向的。
        那么什么是反向呢?就是 A 类不再主动去获取 C,而是被动等待,等待 IoC/DI 的容器获取一个 C 的实例,然后反向的注入到 A 类中。
    5. 依赖注入和控制反转是同一概念么?
        依赖注入和控制反转是对同一件事情的不同描述,从某个方面讲,就是它们描述的角度不同。
        依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源;
        而控制反转是从容器的角度在描述。描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

    Spring AOP

    AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充。它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。

    所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

    AOP 常见的使用场景

    1. Authentication 权限
    2. Caching 缓存
    3. Context passing 内容传递
    4. Error handling 错误处理
    5. Lazy loading 懒加载
    6. Debugging  调试
    7. logging, tracing, profiling and monitoring 记录跟踪 优化 校准
    8. Performance optimization 性能优化
    9. Persistence  持久化
    10. Resource pooling 资源池
    11. Synchronization 同步
    12. Transactions 事务

    实现原理

    Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

    Spring AOP 中的动态代理方法主要有两种:

    1. JDK 动态代理:JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。
    2. CGLIB 动态代理:如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。

    参考资料

    1. Spring AOP 的实现原理

    Spring Bean 的初始化流程

    SpringBean 初始化.png-379.2kBSpringBean 初始化.png-379.2kB
    1. Spring 对 bean 进行实例化;
    2. Spring 将值和 bean 的引用注入到 bean 对应的属性中;
    3. 如果 bean 实现了 BeanNameAware 接口,Spring 将 bean 的 ID 传递给 setBeanName() 方法;
    4. 如果 bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 方法,将 BeanFactory 容器实例传入;
    5. 如果 bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext() 方法,将 bean 所在的应用上下文的引用传入进来;
    6. 如果 bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessBeforeInitialization() 方法;
    7. 如果 bean 实现了 InitializingBean 接口,Spring 将调用它们的 after-PropertiesSet() 方法。类似地,如果 bean 使用 init-method 声明了初始化方法,该方法也会被调用;
    8. 如果 bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 post-ProcessAfterInitialization() 方法;
    9. 此时,bean 已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
    10. 如果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destroy() 接口方法。同样,如果 bean 使用 destroy-method 声明了销毁方法,该方法也会被调用。

    Java 读取一个文件, 有哪些方法, 考虑性能, 用哪一个类

    文件读写主要有以下集中常用的方法:

    1. 字节读写(InputStream/OutputStream)
    2. 字符读取(FileReader/FileWriter)
    3. 行读取(BufferedReader/BufferedWriter)

    通过测试 ,可以发现,就写数据而言,BufferedOutputStream耗时是最短的,而性能FileWriter最差;读数据方面,BufferedInputStream性能最好,而FileReader性能最差劲。

    Java OOM

    为什么为发生 OOM

    原因一般出现为以下两点:

    1. 分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的 VM 参数指定)太少。
    2. 应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。

    其对应的两个术语为:

    1. 内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
    2. 内存溢出:申请的内存超出了 JVM 能提供的内存大小,此时称之为溢出。

    解决方法

    1. 修改内存引用,常用的有软引用、强化引用、弱引用
    2. 在内存中加载图片时直接在内存中作处理,如边界压缩
    3. 动态回收内存
    4. 自定义堆内存大小

    参考资料

    1. https://blog.csdn.net/osle123/article/details/52756433
    2. Java 中常见 OOM 的场景及解决方法
    3. 什么是 java OOM?如何分析及解决 oom 问题?

    Java

    HashMap 是怎么扩容的,为什么是 2 的幂

    HashMap 中,length 为 2 的幂次方,h &(length-1)等同于求模运算 h%length

    HashMap 采用这种非常规设计,主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap 定位哈希桶索引位置时,也加入了高位参与运算的过程。

    操作流程图

    此处输入图片的描述 此处输入图片的描述

    参考资料

    1. HashMap 的扩容是怎样扩容的,为什么是 2 的 N 次幂的大小?

    Java 为什么获取不到函数参数名称

    在 Java 1.7 以前,编译生成的字节码中并不会包含方法的参数信息。因此无法获取到方法的参数名称信息。

    但是在 Java 1.8 以后,开始在 class 中保存参数名,并且增加了对应的类Parameter。使用的示例代码如下:

     public static List<String> getParameterNameJava8(Class clazz, String methodName){
        List<String> paramterList = new ArrayList<>();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (methodName.equals(method.getName())) {
                Parameter[] params = method.getParameters();
                for(Parameter parameter : params){
                    paramterList.add(parameter.getName());
                }
    
            }
        }
    
        return paramterList;
    }
    

    如果编译等级低于 1.8,则得到的参数名依旧为无效的参数名,例如 arg0、arg1……

    同时,想要保留参数名也需要通过修改编译选项 javac -parameters 进行打开,默认是关闭的。

    idea 设置保留参数名:

    在 preferences-》Java Compiler-> 设置模块字节码版本 1.8,Javac Options 中的 Additional command line parameters: -parameters

    参考资料

    1. Java——通过反射获取函数参数名称

    JVM 常用命令

    1. jinfo:可以输出并修改运行时的 java 进程的 opts。
    2. jps:与 unix 上的 ps 类似,用来显示本地的 java 进程,可以查看本地运行着几个 java 程序,并显示他们的进程号。
    3. jstat:一个极强的监视 VM 内存工具。可以用来监视 VM 内存内的各种堆和非堆的大小及其内存使用量。
    4. jmap:打印出某个 java 进程(使用 pid)内存内的所有 ' 对象 ' 的情况(如:产生那些对象,及其数量)。
    5. jconsole:一个 java GUI 监视工具,可以以图表化的形式显示各种数据。并可通过远程连接监视远程的服务器 VM。

    详细:在使用这些工具前,先用 JPS 命令获取当前的每个 JVM 进程号,然后选择要查看的 JVM。

    参考资料

    1. 查看 jvm 常用命令

    二叉树有什么优点

    二叉树 -> 二叉排序树,二叉排序树是一种比较有用的折衷方案。

    数据的搜索比较方便,可以直接使用下标,但是删除或者插入元素比较麻烦。
    而链表插入和删除元素很快,但是查找很慢。

    而二叉排序树会兼有上面两个的好处,有序二叉树天然具有对数查找效率;二叉树天然具有链表特征。在处理大批量的动态数据比较有用。但是删除节点方法相对复杂。

    参考资料:

    1. 二叉树的优点和缺点
    2. 分析线性表、二叉平衡树和哈希表存储数据时各自的优劣。
    3. 二叉树的优势

    HTTP 的服务器端和客户端能双向通信吗

    HTTP 协议是基于TCP/IP的,从协议角度来说是和服务器端具有双向的通信能力。

    但是使用 HTTP 协议的 WEB 服务器不能主动推送信息给客户浏览器。现在可以使用WebSocket协议进行替代。或者使用SSE协议让服务器端给客户端进行信息的推送。

    原因是:HTTP主要是给web 服务使用的协议,对于 web 服务来说,并发的数量最为重要。如果采用 TCP 的有状态协议,那么用户只要打开浏览器,则与服务器的HTTP连接就不会切断。但是服务器所能同时开启的连接数是有限制的。因此为了服务器的性能着想,基于HTTP的服务器端并不能和客户端进行双向通信。

    参考资料:

    1. http 是基于 tcp/ip 的,那么它也是可以具有和服务器双向通信的能力的。我们为什么不升级 http,搞个 http x.1
    2. HTTP 是否可以实现双向通讯?

    GET 和 POST 的区别

    <table class="dataintable">
    <tbody><tr>
    <th style="width:20%;"> </th>
    <th>GET</th>
    <th>POST</th>
    </tr>

    <tr>
    <td> 后退按钮/刷新 </td>
    <td> 无害 </td>
    <td> 数据会被重新提交(浏览器应该告知用户数据会被重新提交)。</td>
    </tr>

    <tr>
    <td> 书签 </td>
    <td> 可收藏为书签 </td>
    <td> 不可收藏为书签 </td>
    </tr>

    <tr>
    <td> 缓存 </td>
    <td> 能被缓存 </td>
    <td> 不能缓存 </td>
    </tr>

    <tr>
    <td> 编码类型 </td>
    <td>application/x-www-form-urlencoded</td>
    <td>application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。</td>
    </tr>

    <tr>
    <td> 历史 </td>
    <td> 参数保留在浏览器历史中。</td>
    <td> 参数不会保存在浏览器历史中。</td>
    </tr>

    <tr>
    <td> 对数据长度的限制 </td>
    <td> 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。</td>
    <td> 无限制。</td>
    </tr>

    <tr>
    <td> 对数据类型的限制 </td>
    <td> 只允许 ASCII 字符。</td>
    <td> 没有限制。也允许二进制数据。</td>
    </tr>

    <tr>
    <td> 安全性 </td>
    <td><p> 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。</p>
    <p> 在发送密码或其他敏感信息时绝不要使用 GET !</p></td>
    <td>POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。</td>
    </tr>

    <tr>
    <td> 可见性 </td>
    <td> 数据在 URL 中对所有人都是可见的。</td>
    <td> 数据不会显示在 URL 中。</td>
    </tr>
    </tbody></table>

    参考链接

    1. HTTP 方法:GET 对比 POST

    Session 和 Cookie 的区别

    1. Session: Session 是用于唯一标志客户信息的对象,容器通过浏览器端传送的 cookie 中的 jsessionid 来区分不同的客户对象,所以 Session 依赖于 cookie。是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中。
    2. Cookie: cookie 是在服务器端创建,然后服务器发送到浏览器端,存储在浏览器边,当再次请求服务器时候,浏览器会将该域中的相关 cookie 发送到服务器端。是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现 Session 的一种方式。

    区别

    1. cookie 数据存储在客户端上, session 数据存储在服务器上。
    2. cookie 数据由于存储在客户端上并不是十分安全,别人可以通过分析存放在本地的 cookie 进行 cookie 欺骗。因此考虑到安全问题应当使用 session。
    3. session 会在一定时间上保存在服务器中。当访问增多时,会大量占用服务器的性能。如果为了考虑减少服务器的压力,应当使用 cookie。
    4. 单个 cookie 的保存数据大小不能超过 4k,很多浏览器限制一个站点最多保存 20 个 cookie。

    因此,可以考虑将登陆等重要的信息保存在 session 中,其他相对不中要的数据可以存储在 cookie 中。

    参考链接:

    1. cookie 和 session 的区别详解
    2. Session 和 Cookie 的关系

    MySQL 索引类型

    MySQL 主要有以下几种索引类型: 普通索引,唯一索引,主键索引,组合索引,全文索引。

    创建索引的语句如下:

    CREATE TABLE table_name[col_name data type]
    [unique|fulltext][index|key][index_name](col_name[length])[asc|desc]
    
    1. unique|fulltext 为可选参数,分别表示唯一索引、全文索引
    2. indexkey 为同义词,两者作用相同,用来指定创建索引
    3. col_name 为需要创建索引的字段列,该列必须从数据表中该定义的多个列中选择
    4. index_name 指定索引的名称,为可选参数,如果不指定,默认 col_name 为索引值
    5. length 为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度
    6. ascdesc 指定升序或降序的索引值存储

    1. 普通索引

    是最基本的索引,它没有任何限制。

    CREATE INDEX index_name ON table(column(length))
    

    2. 唯一索引

    与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。

    CREATE UNIQUE INDEX indexName ON table(column(length))
    

    3. 主键索引

    是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引:

    CREATE TABLE `table` (
        `id` int(11) NOT NULL AUTO_INCREMENT ,
        `title` char(255) NOT NULL ,
        PRIMARY KEY (`id`)
    );
    

    4. 组合索引

    指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合:

    ALTER TABLE `table` ADD INDEX name_city_age (name,city,age); 
    

    5. 全文索引

    主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext 索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的 where 语句的参数匹配。fulltext 索引配合 match against 操作使用,而不是一般的 where 语句加 like。它可以在 create table,alter table ,create index 使用,不过目前只有 char、varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用 CREATE index 创建 fulltext 索引,要比先为一张表建立 fulltext 然后再将数据写入的速度快很多。

    CREATE FULLTEXT INDEX index_content ON article(content)
    

    参考资料

    1. 7.1.2 索引的分类
    2. MySQL 索引类型

    MySQL 建索引的原则

    1. 选择唯一性索引
      唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名现象,从而降低查询速度。
    2. 为经常排序、分组和联合操作的字段建立索引
      经常需要 ORDER BY、GROUP BY、DISTINCT 和 UNION 等操作的字段,排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。
    3. 为常作为查询条件的字段建立索引
      如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。
    4. 限制索引的数目
      索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
    5. 尽量使用数据量少的索引
      如果索引的值很长,那么查询的速度会受到影响。例如,对一个 CHAR(100) 类型的字段进行全文检索需要的时间肯定要比对 CHAR(10) 类型的字段需要的时间要多。
    6. 尽量使用前缀来索引
      如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT 和 BLOG 类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
    7. 删除不再使用或者很少使用的索引
      表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
    8. 最左前缀匹配原则,非常重要的原则。
      mysql 会一直向右匹配直到遇到范围查询 (>、<、between、like) 就停止匹配,比如a 1 and b=2 and c>3 and d=4 如果建立 (a,b,c,d) 顺序的索引,d 是用不到索引的,如果建立 (a,b,d,c) 的索引则都可以用到,a,b,d 的顺序可以任意调整。
    9. =和 in 可以乱序。
      比如a = 1 and b = 2 and c = 3 建立 (a,b,c) 索引可以任意顺序,mysql 的查询优化器会帮你优化成索引可以识别的形式
    10. 尽量选择区分度高的列作为索引
      区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是 1,而一些状态、性别字段可能在大数据面前区分度就 是 0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要 join 的字段我们都要求是 0.1 以上,即平均 1 条扫描 10 条 记录
    11. 索引列不能参与计算,保持列“干净”。
      比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本 太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
    12. 尽量的扩展索引,不要新建索引。
      比如表中已经有 a 的索引,现在要加 (a,b) 的索引,那么只需要修改原来的索引即可

    注意:选择索引的最终目的是为了使查询的速度变快。上面给出的原则是最基本的准则,但不能拘泥于上面的准则。读者要在以后的学习和工作中进行不断的实践。根据应用的实际情况进行分析和判断,选择最合适的索引方式。

    相关文章

      网友评论

          本文标题:Java 校招面试

          本文链接:https://www.haomeiwen.com/subject/qdmocqtx.html