来自面试官的技术面试题


作者:xcbeyond
疯狂源自梦想,技术成就辉煌!微信公众号:《程序猿技术大咖》号主,专注后端开发多年,拥有丰富的研发经验,乐于技术输出、分享,现阶段从事微服务架构项目的研发工作,涉及架构设计、技术选型、业务研发等工作。对于Java、微服务、数据库、Docker有深入了解,并有大量的调优经验。 






   

  最近为公司面试了不少Java开发,有工作一两年的,也有工作十来年的人,在面试他人前,自己也需准备一下,免得错失人才,或者误导他人。为了更好的面试他人,所以我也会准备一番,全当查漏补缺(毕竟好东西我也不是很清楚的),因此,就最近面试情况及问题,进行汇总整理如下。

我一般面试提问,会从下面三个方面发问:

    自我介绍
    技术、框架
    Java基础

自我介绍

      自我介绍,老生常谈的话题,大部分面试官都以此作为面试的开口,以了解面试者的基本信息(工作时间、工作经历)、做过哪些/哪类项目、会什么技术、擅长什么等。

      温馨提示,个人自我介绍最好提前有所准备下,不至于说话磕磕碰碰,更重要的是要实事求是。自我介绍,也是对面试官产生第一印象开端,有了好的印象,才会有接下来的事情,对你后续面试及面试结果有一定的帮助。
技术、框架

      技术及框架,在此进行分类整理(没有先后顺序),如下:
线程部分

1、使用过线程么?线程如何实现?

      通过继承 Thread 类、实现Runnable 接口,在run方法中实现功能或业务逻辑。

2、线程中start和run方法有什么区别和联系?

      调用start方法可启动线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法

,即:线程要执行的内容。

     而run方法只是线程里面一个普通方法的调用而已,还是在主线程里执行。如果直接调用run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

     
    public static void main(String args[]) {
     
            Thread t = new Thread() {
     
                public void run() {
     
                    pong();
     
                }
     
            };
     
            t.start();
     
            System.out.print("ping");
     
     }
     
     
    public static void pong() {
     
            System.out.print("pong");
     
     }

    输出结果: pingpong

     
    public static void main(String args[]) {
     
            Thread t = new Thread() {
     
                public void run() {
     
                    pong();
     
                }
     
            };
     
            t.run();
     
            System.out.print("ping");
     
        }
     
     
     
    public  static void pong() {
     
            System.out.print("pong");
     
     }

    输出结果:pongping

通过以上两个程序实例,可以很容易的区分出start()方法和run()方法的区别:

t.start(); 该行代码相当于是启动线程,

t.run(); 该行代码相当于是使用t这个类中的run方法而已。

3、了解过线程死锁么?如何有效的避免线程死锁?

      死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外界作用下,它们都将无法进行下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

      避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

4、项目中有没有用过线程池 ?怎么用的 ?

      使用过。我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

    通过使用线程池就可以解决这个问题,使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务。Java的线程池最核心是ThreadPoolExecutor类,线程池底层都是通过 ThreadPoolExecutor 来实现的:

    public ThreadPoolExecutor(int  corePoolSize,                           
                              int  maximumPoolSize,                          
                              long  keepAliveTime,
                              TimeUnit  unit,                         
                              BlockingQueue<Runnable>  workQueue,                           
                              ThreadFactory  threadFactory,                           
                              RejectedExecutionHandler  handler)

其中参数的意思分别为:

    corePoolSize:线程池里最小线程数
    maximumPoolSize:线程池里最大线程数量,超过最大线程时候会使用 RejectedExecutionHandler
    keepAliveTime:线程最大的存活时间,超过这个时间就会被回收
    unit:线程最大的存活时间的单位
    workQueue:缓存需要执行的异步任务的队列
    threadFactory:新建线程工厂
    handler:拒绝策略,表示当workQueue已满,且池中的线程数达到maximumPoolSize时,线程池拒绝添加新任务时采取的策略。DiscardPolicy:抛弃当前任务,DiscardOldestPolicy:扔掉最旧的,CallerRunsPolicy:由向线程池提交任务的线程来执行该任务,AbortPolicy:抛出 RejectedExecutionException 异常。

MyBatis 部分

1、mybatis 中$ 与 # 的区别?

   都是可以来传递参数的,不过 # 可以方防止sql 注入,而 $ 就是字符串拼接的方式处理,可能会有sql 注入的问题。

  #{} 在预处理时,会把参数部分用一个占位符 ? 代替 ,变成了如下的 sql 语句:

select * from user where name = ?;

  而 ${} 则只是简单的字符串拼接,在动态解析阶段就直接拼接成了 最终的sql 语句:

select * from user where name = 'xcbeyond';

2、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。

    <select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>
           select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
    </select>

第2种: 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系。






     <select id="getOrder" parameterType="int" resultMap="orderresultmap">
         select * from orders where order_id=#{id}
     </select>
     
     <resultMap type=”me.gacl.domain.order” id=”orderresultmap”>
        <!–用id属性来映射主键字段–>
        <id property=”id” column=”order_id”>
     
        <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–>
        <result property = “orderno” column =”order_no”/>
        <result property=”price” column=”order_price” />
    </reslutMap>

3、MyBatis中怎么实现一个动态SQL?

      Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。

      Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。

4、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

     不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;

    原因就是namespace+id是作为Map<String, MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。

更多详见Mybatis常见面试题总结
数据库

1、有没有使用过视图?什么场景下会考虑使用它?

   视图,是一种虚拟的表,具有和一般表相同的功能。可以对视图进行增,改,查操作,试图是由一个表或者多个表的行或列的子集,即:是一个查询sql的查询结果集。

   以下场景,一般会考虑使用视图:

    频繁使用子查询。通常会将频繁使用的子查询,创建为一个视图,便于共用,以简化sql量,直接调用而不是每次都去重复写这个子查询。
    避免直接暴露表结构。需要给其他外部系统、他人提供表数据时,可创建一个对应数据的视图,而不是直接暴露原始表,这样一定程度上降低风险。

2、有没有使用过索引?使用索引时有什么注意事项么?

    表添加索引后,一定程度会加速表的查询速度,但过多的使用索引将会造成滥用。虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。

使用索引时的优缺点如下:

优点:

    可以通过建立唯一索引或者主键索引,保证数据库表中每一行数据的唯一性
    建立索引可以大大提高检索的数据,以及减少表的检索行数
    在表连接的连接条件 可以加速表与表直接的相连
    在分组和排序字句进行数据检索,可以减少查询时间中 分组 和 排序时所消耗的时间(数据库的记录会重新排序)
    建立索引,在查询中使用索引 可以提高性能

缺点:

    在创建索引和维护索引 会耗费时间,随着数据量的增加而增加
    索引文件会占用物理空间,除了数据表需要占用物理空间之外,每一个索引还会占用一定的物理空间
    当对表的数据进行 INSERT,UPDATE,DELETE 的时候,索引也要动态的维护,这样就会降低数据的维护速度,(建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快)。

3、查询语句速度很慢,如何优化?

  可从以下几个方面进行优化:

    建索引
    减少表之间的关联
    优化sql,尽量让sql很快定位数据,不要让sql做全表查询,应该走索引,把数据 量大的表排在前面
    简化查询字段,没用的字段不要,已经对返回结果的控制,尽量返回少量数据,避免slect * 查询
    尽量用PreparedStatement来查询,不要用Statement

下面列举一些优化小技巧:

技巧1  比较运算符能用 “=”就不用“<>”

     “=”增加了索引的使用几率。

技巧2  明知只有一条查询结果,那请使用 “LIMIT 1”

     “LIMIT 1”可以避免全表扫描,找到对应结果就不会再继续扫描了。

技巧3  为列选择合适的数据类型

     能用TINYINT就不用SMALLINT,能用SMALLINT就不用INT,道理你懂的,磁盘和内存消耗越小越好嘛。

技巧4  将大的DELETE,UPDATE or INSERT 查询变成多个小查询

     能写一个几十行、几百行的SQL语句是不是显得逼格很高?然而,为了达到更好的性能以及更好的数据控制,你可以将他们变成多个小查询。

 

技巧5  使用UNION ALL 代替 UNION,如果结果集允许重复的话

     因为 UNION ALL 不去重,效率高于 UNION。

技巧6  为获得相同结果集的多次执行,请保持SQL语句前后一致

     这样做的目的是为了充分利用查询缓冲。

     比如根据地域和产品id查询产品价格,第一次使用了:

     那么第二次同样的查询,请保持以上语句的一致性,比如不要将where语句里面的id和region位置调换顺序。

 

技巧7  尽量避免使用 “SELECT *”

     如果不查询表中所有的列,尽量避免使用 SELECT *,因为它会进行全表扫描,不能有效利用索引,增大了数据库服务器的负担,以及它与应用程序客户端之间的网络IO开销。

技巧8  WHERE 子句里面的列尽量被索引

     只是“尽量”哦,并不是说所有的列。因地制宜,根据实际情况进行调整,因为有时索引太多也会降低性能。

技巧9  JOIN 子句里面的列尽量被索引

     同样只是“尽量”哦,并不是说所有的列。

技巧10  ORDER BY 的列尽量被索引

     ORDER BY的列如果被索引,性能也会更好。

技巧11  使用 LIMIT 实现分页逻辑

     不仅提高了性能,同时减少了不必要的数据库和应用间的网络传输。

技巧12  使用 EXPLAIN 关键字去查看执行计划

      EXPLAIN 可以检查索引使用情况以及扫描的行。

4、你都使用过Mysql和Oracle数据库,能不能举例说一下他们都有哪些区别?

    可以从以下几个方面说起:

    本质区别

      MySql是AB 公司开发的,目前属于 Oracle 旗下的一个开源、免费的数据库,而Oracle是一个收费的数据库。

    SQL语法/句的区别

    (1) 删除表时,sql关键字的差异。

  MySql: drop table if exists tableName

  Oracle:drop table tableName

     注:Oracle没有if exists关键字,也没用类似if exists的SQL语法。

(2)函数、关键字的差异

     MySql中日期的转换用dateformat()函数,Oracle用to_date()与to_char()两个函数;

     MySql获取当前时间用NOW(),Oracle用sysdate;

    等等

(3)mysql可以实现自增长主键(通过字段的auto_increment属性);Oracle则需要通过序列(Sequence)来实现。

(4)索引

    在整个数据库内,MySql的索引可以同名,也就是说MySql的索引是表级别的;但是Oracle索引不可以同名,也就是说Oracle的索引是数据库级别的。

(5)空字符串问题

     Oracle中空字符串就是null(也就是说只有null,没有空字符),而MySql是有null和''区分的。

     对于使用语句:select * from table1 where user_name <> '' 来查询列user_name不为空(不为null且不为空字符)时,Oracle会查不出任何结果,而MySQL可以正常运行。这里MySQL之所以可以得到正确结果,还因为比较符号<>会先将列为null的内容进行过滤,然后再比较内容是否为空字符串。

     这就要求一方面,以后在编写代码的时候,尽量保证不会往数据库插入空字符串''这样的值,要么保持有数据,要么保持为null。另外,对于MySQL中已经同时存在Null和''时,所有判断是否为null或者''的地方改为判断列的长度是否为0。

 

持续整理中...