第一部分 Java基础
第二部分 Java进阶

Java集合面试题

集合专题

● 说一下你了解的Java集合?

● HashMap与Hashtable的区别?

● 哈希碰撞/哈希冲突你有了解吗,它是什么,怎么解决的?

● ArrayList和LinkedList的不同?

● ArrayList和Vector的区别?

● HashMap的数据存储和实现原理你了解多少?

● HashMap的长度为什么是2的幂次方?

● HashSet和HashMap的区别与联系?

● Hashtable和ConcurrentHashMap的区别?

● ConcurrentHashMap线程安全的具体实现原理?

● 怎么获取一个线程安全的ArrayList?

● TreeMap/TreeSet底层是什么数据结构,怎么实现的自动排序?

● HashMap集合的put和get方法的实现原理?

● Comparable和Comparator的区别?

● for循环和foreach遍历集合哪个更快?

● 如何快速的遍历map集合,哪种方式最快?

● HashSet、TreeSet、LinkedHashSet区别?

● 在什么场景下要重写equals()和hashCode()方法?

● HashMap和TreeMap有什么不同?

● Collections和Collection有哪些区别?

● 如何对一组对象进行排序?

● Collection接口的remove()方法和Iterator接口的remove()方法区别?

● Java集合中List、Set、Map的区别?

● Iterator和ListIterator的区别是什么?

● 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

● 什么是Java优先级队列(Priority Queue)?

PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。

● Enumeration接口和Iterator接口的区别有哪些?

Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被Iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。

● 在迭代一个集合的时候,如何避免ConcurrentModificationException?
在遍历一个集合的时候,我们可以使用并发集合类来避免ConcurrentModificationException,比如使用CopyOnWriteArrayList,而不是ArrayList。

● 哪些集合类是线程安全的?

Vector、Hashtable、Properties和Stack是同步类,所以它们是线程安全的,可以在多线程环境下使用。Java1.5并发包下包括一些集合类,允许迭代时修改,因为它们都工作在集的克隆上。所以它们在多线程环境中是安全的。

● 并发集合类是什么?

Java1.5并发包(java.util.concurrent)包含线程安全集合类,允许在迭代时修改集合。迭代器被设计为fail-fast的,会 抛出ConcurrentModificationException。一部分类为:CopyOnWriteArrayList、 ConcurrentHashMap、CopyOnWriteArraySet。

● BlockingQueue是什么?

java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。

● 大写的O是什么,举几个例子?

大写的O描述的是数据结构中一个算法的性能。Collection类就是实际的数据结构,我们通常基于时间、内存和性能,使用大写的O来选择集合实现。比如:例子1:ArrayList的get(index i)是一个常量时间操作,它不依赖list中元素的数量,所以它的性能是O(1)。例子2:一个对于数组或列表的线性搜索的性能是O(n),因为我们需要 遍历所有的元素来查找需要的元素。

数据库专题/SQL优化专题

● 你用过存储过程吗?

之前用过存储过程,近期的项目没有使用存储过程,由于存储过程具有数据库特色,mysql有自己一套语法机制,oracle数据库则有另一套语法机制,虽然使用存储过程会提高执行速度,但使用了存储过程后,项目的数据库很难平滑的移植,例如项目A在开发的时候使用了mysql存储过程,假设将来将A项目的数据库更改为B项目则是很困难的,故我们开发的时候为了保证数据库的可移植性,就没有使用存储过程。

注意:游标、触发器、存储过程,都是存储过程相关的。

● 千万级数据,你如何提高查询效率?

● 你对SQL优化有了解吗,可以描述一些你用过的优化策略吗?

● 索引的实现原理?

● 索引的分类?

● 索引在什么情况下失效?

● 内连接和外连接有什么区别?

● 数据库设计范式?

第一范式:每个表都应该有主键,并且每个字段要求原子性不可再分。

第二范式:建立在第一范式基础之上,所有非主键字段必须完全依赖主键,不能产生部分依赖。

第三范式:建立在第二范式基础之上,所有非主键字段必须直接依赖主键,不能产生传递依赖。

设计范式的最终目的是:减少数据的冗余。但在实际的开发中,我们以满足客户的需求为目的,有的时候也会拿冗余来换取速度。(建议把这句话说上,体现工作经验)

● MySQL默认的事务隔离级别?

可重复读(Repeatable Read)

● 事务的隔离级别有哪些?

读未提交(Read UnCommitted)、读提交(Read Committed)、可重复读(Repeatable Read)、序列化(Serializable)

● 在什么情况下使用having过滤?

从效率方面考虑,建议优先使用where进行过滤,如果专门是对分组之后的数据进行过滤,才会使用having。

● 事务的特性有哪些?

原则性(A)、一致性(C)、隔离性(I)、持久性(D)。

原子性表示事务是最小的工作单元不可再分。

一致性表示事务必须同时成功或者同时失败。

隔离性表示事务A和事务B之间具有隔离。

持久性是事务最终结束的保障。

JVM专题

● Java GC的工作原理你有了解吗?

● System.gc()和Runtime.gc()会做什么事情?

这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。

● 如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
不会,在下一个垃圾回收周期中,这个对象将是可被回收的。

● 你听说过哪些GC算法?

● 你知道哪些垃圾回收机制?

● 你对JVM调优有了解吗,可以说一下吗?

● JVM的内存结构你有了解吗(运行时数据区)?

● Java的堆结构是什么样子的?

JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收掉之前,他们会一直占据堆内存空间。

● 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?

吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程序。而串行收集器对大多数的小应用(在现代处理器上需要大概100M左右的内存)就足够了。

● JVM的永久代中会发生垃圾回收么?

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。

● 什么是分布式垃圾回收(DGC)?它是如何工作的?

DGC叫做分布式垃圾回收。RMI使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。

设计模式专题

● 你在实际项目中使用过哪些设计模式?

在实际的开发中也用过很多设计模式,例如GoF的设计模式,JavaEE的设计模式,例如在项目中写过BaseController,所有的Controller都继承这个Controller,这个BaseController当中定义核心算法骨架,具体的实现延迟到子类中完成,这个就符合模板方法设计模式。

另外还使用了JavaEE设计模式DAO,持久层统一使用该模式。还有在分页查询的时候封装了PaginationVO,这里使用了JavaEE的VO模式。

在实际的项目中也使用了过滤器、拦截器等,这些都是符合GoF责任链设计模式的。还有使用Spring AOP控制事务,AOP底层使用了动态代理模式。

在P2P项目中,生成合同文件的时候,我们使用了两个设计模式,一个是门面模式,一个是策略模式。门面模式体现在我们提供了生成合同文件的统一接口,策略模式体现在合同文件可以最终生成到PC端,也可以生成到H5端等。

● 你对设计模式有了解吗,什么是设计模式,常见设计模式你知道哪些?

设计模式就是可以重复利用的解决方案。常见的设计模式有:单例模式、装饰器模式、适配器模式、责任链模式、代理模式、策略模式、观察者模式等。

单例模式:解决对象创建问题,属于创建型设计模式,保证创建的实例只有1个,用于节省内存的开销,符合单例模式的对象不会被垃圾回收器回收,所以在实际开发中通常使用单例模式来实现缓存,因为缓存需要避免被GC回收。

装饰器模式:IO流当中使用了大量的装饰器模式,装饰器模式属于结构型设计模式,例如关闭流的时候只需要关闭最外层流即可。

适配器模式:Servlet规范中的GenericServlet使用了适配器模式,适配器模式属于结构型设计模式,其中GenericServlet属于适配器,所有Servlet类继承GenericServlet,而不需要直接实现Servlet接口,这样代码会更加优雅(该词汇出自阎宏的《Java与模式》一书)。另外过滤器Filter、监听器Listener都符合适配器模式。

责任链设计模式:Filter过滤器符合责任链设计模式,包括SpringMVC中的拦截器也是符合该设计模式的,责任链设计模式属于行为型设计模式,再不改变java源代码的基础之上,可以实现方法的动态调用组合。

代理模式:Spring AOP就使用了动态代理机制。代理模式属于结构型设计模式。

策略模式:比较器Comparator就体现了策略模式,面向接口编程,更换比较器即可更换比较规则,策略模式属于行为型设计模式。

观察者模式:Servlet规范中的Listener监听器就实现了观察者模式,另外MVC架构模式也属于符合观察者设计模式,观察者设计模式属于行为型设计模式。

● 你知道哪些开发原则?

据我了解,软件的开发原则有六大原则。

开闭原则:开闭原则是面向对象的可复用设计的第一块基石,它是最重要的面向对象设计原则。开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

单一职责原则:单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小。单一职责原则定义如下:单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。

接口隔离原则:接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

里氏代换原则:里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。

迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用。迪米特法则还有几种定义形式,包括:不要和“陌生人”说话、只与你的直接朋友通信等。

依赖倒转原则:依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。

● GoF设计模式分类?

创建型、结构型、行为型。

创建型的有:单例模式、工厂模式等。

结构型的有:适配器、装饰器、代理模式等。

行为型的有:策略模式、观察者模式、责任链模式等。

● JavaEE的设计模式知道那些?

DAO、POJO、DTO、VO、BO等。

● 怎么写一个线程安全的单例模式?

//饿汉(绝对线程安全,但是对象在未使用时初始化,占用内存)
public final class EagerSingleton {  
    private static EagerSingleton singObj = new EagerSingleton();  
    private EagerSingleton(){  
    }  
    public static EagerSingleton getSingleInstance(){  
       return singObj;
    }  
}
//懒汉(多线程并发时,存在线程安全问题)
public final class LazySingleton {  
    private static LazySingleton singObj = null;  
    private LazySingleton(){  
    }  
    public static LazySingleton getSingleInstance(){  
        if(null == singObj ) singObj = new LazySingleton();
          return singObj;
    }  
}
//懒汉加synchronized(线程安全的,但是同步的是整个方法,并发度差)
public final class ThreadSafeSingleton {  
    private static ThreadSafeSingleton singObj = null;  
    private ThreadSafeSingleton(){  
    }  
    public static Synchronized ThreadSafeSingleton getSingleInstance(){  
        if(null == singObj ) singObj = new ThreadSafeSingleton();
            return singObj;
    }  
}
//双重检查锁(如果发生指令重排序,则会出现线程不安全)
public final class DoubleCheckedSingleton {  
    private static DoubleCheckedSingletonsingObj = null;  
    private DoubleCheckedSingleton(){  
    }  
    public static DoubleCheckedSingleton getSingleInstance(){  
        if(null == singObj ) {
              Synchronized(DoubleCheckedSingleton.class){
                     if(null == singObj) // (如果发生指令重排序,则会出现线程不安全)
                           singObj = new DoubleCheckedSingleton();
              }
         }
       return singObj;
    }  
}
//双重检查锁(加volatile关键字)
public final class DoubleCheckedSingleton {  
    private static volatile DoubleCheckedSingletonsingObj = null;  
    private DoubleCheckedSingleton(){  
    }  
    public static DoubleCheckedSingleton getSingleInstance(){  
        if(null == singObj ) {
              Synchronized(DoubleCheckedSingleton.class){
                     if(null == singObj) 
                           singObj = new DoubleCheckedSingleton();
              }
         }
       return singObj;
    }  
}
// 静态内部类方式
public class SingletonPattern {
   private SingletonPattern() {
   }
   private static class SingletonPatternHolder {
       private static final SingletonPattern singletonPattern = new SingletonPattern();
   }
   public static SingletonPattern getInstance() {
       return SingletonPatternHolder.singletonPattern;
   }
}

● 单例模式的优缺点及使用场景?

(1)优点: 

在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例。 

提供了对唯一实例的受控访问。 

由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。 

避免对共享资源的多重占用。

(2)缺点:

不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。 

由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。

单例类的职责过重,在一定程度上违背了“单一职责原则”。 

滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

(3)使用注意事项:

使用时不能用反射模式创建单例,否则会实例化一个新的对象。

使用懒单例模式时注意线程安全问题。

饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)。

(4)经典使用场景:

网站的计数器,一般也是采用单例模式实现,否则难以同步。 

应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。 

Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。 

数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。 

多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

● 什么是享元模式,哪里用了?

享元模式通过共享对象来避免创建太多的对象。为了使用享元模式,你需要确保你的对象是不可变的,这样你才能安全的共享。JDK 中 String 池、Integer 池以及 Long 池都是很好的使用了享元模式的例子。

● 描述一下接口和抽象类你是如何选用的?

接口和抽象类其实表示事物与事物之间的联系的一种关系的体现。接口更多的体现的是like A的关系,而抽象类更多的是is A的关系。如果这两个类他们之间确实无形中体现出is A的关系,比如猫和狗都是动物的一种,则可以写抽象类。而如果这两个类它们之间的行为很像,则它们体现出了一种Like A的关系,如媒婆代理别人去相亲,那么本身就体现了一种方法,则体现出了接口的关系。也就是说接口更偏向于描述行为。