新手必问Java面试题

一、Java基础部分

1、访问修饰符public,private,protected,以及不写(默认)时的区别?

修饰符当前类同 包子 类其他包
public
protected×
default××
private×××

类的成员不写访问修饰时默认为default。默认对于同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。Java中,外部类的修饰符只能是public或默认,类的成员(包括内部类)的修饰符可以是以上四种。

2、String是最基本的数据类型吗?

String不是最基本的数据类型,它是一个被final修饰的类。在Java中有8种基本数据类型:

字符型:byte、char

基本整型:short、int、long

浮点型:float、double、

布尔型:boolean

3、是否可以继承String类?

在Java中,String类的修饰符是final,这意味着String类是不可继承和修改的。

4、数组有没有length()方法?String有没有length()方法?

数组没有length()方法,有length 的属性。String 有length()方法。JavaScript中,获得字符串的长度是通过length属性得到的,这一点容易和Java混淆。

5、String s=new String(‘xyz’);创建了几个object对象?

两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。

6、String和StringBuilder、StringBuffer的区别?

Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。

7、short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗?

对于short s1 = 1; s1 = s1 + 1;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型。而short s1 = 1; s1 += 1;可以正确编译,因为s1+= 1;相当于s1 = (short)(s1 + 1);其中有隐含的强制类型转换。

8、两个对象值相同,x.equal(y)==true,但是却可有不同的hashcode,这句话对不对?

不对,两个对象如果equal的值为true,那么他们的hashcode一定是相同的。但是hashcode相同,他们的equal未必一定为true。

例如:在hashmap中,key值是不重复的,在往hashmap中put对象的时候,会有一个判断是否有相同的key,首先比较的是hashcode,如果hashcode相同则需要进一步使用equal进行判断,如果hashcode不相同,则这两个对象则是不相同的,这也可以提高效率。

9、&和&&的区别?

&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

10、Collection和Collections区别?

Collection是一个接口,它是Set、List等容器的父接口;Collections是一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。

11、重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

方法的重载和重写都是实现多态的方式,区别在于重载实现的是编译时的多态性,而重写实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

12、抽象类(abstract class)和接口(interface)的区别?

抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

13、java 受检异常和非受检异常?

异常的分类:
java.lang.Throwable
1、Error错误:JVM内部的严重问题。无法恢复。程序人员不用处理。
2、Exception异常:普通的问题。通过合理的处理,程序还可以回到正常执行流程。要求编程人员要进行处理。
3、RuntimeException:也叫非受检异常(unchecked exception).这类异常是编程人员的逻辑问题。应该承担责任。Java编译器不进行强制要求处理。 也就是说,这类异常再程序中,可以进行处理,也可以不处理。
4、受检异常(checked exception).这类异常是由一些外部的偶然因素所引起的。Java编译器强制要求处理。也就是说,程序必须进行对这类异常进行处理。

常见异常:
1、非受检的:NullPointerException,ClassCastException,ArrayIndexsOutOfBoundsException,ArithmeticException(算术异常,除0溢出)。
2、受检:Exception,FileNotFoundException,IOException,SQLException。

14、常见的几个RuntimeException异常?

NullPointerException:当操作一个空引用时会出现此错误。

NumberFormatException:数据格式转换出现问题时出现此异常。

ClassCastException:强制类型转换类型不匹配时出现此异常。

ArrayIndexOutOfBoundsException:数组下标越界,当使用一个不存在的数组下标时出现此异常。

ArithmeticException:数学运行错误时出现此异常

15、error和exception区别?

Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。

16、final、finally、finalize区别?

final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方法也同样只能使用,不能在子类中被重写。

finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。

finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。

17、运行时异常与受检异常的区别?

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,在Effective Java中对异常的使用给出了以下指导原则:

  • 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
  • 对可以恢复的情况使用受检异常,对编程错误使用运行时异常
  • 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
  • 优先使用标准的异常
  • 每个方法抛出的异常都要有文档
  • 保持异常的原子性
  • 不要在catch中忽略掉捕获到的异常

18、List、Set、Map是否继承自Collection接口?

List、Set 是,Map 不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。

19、List、Map、Set三个接口存取元素时,各有什么特点?

List以特定索引来存取元素,可以有重复元素。Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。Set和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果。

20、HashMap与HashTable的区别?

  • HashTable是线程安全,方法上添加了synchronized同步修饰,HashMap非线程安全。
  • HashMap的key和value可以为空,HashTable的key不可以为空。
  • HashMap继承AbstractMap,HashTable继承Dictionary,都实现了map接口。
  • HashMap的初始容量是16,填充因子默认0.75,扩充时2n,HashTable的初始容量是11,填充因子0.75,扩充时2n+1。
  • HashMap对key的hashcode进行了二次hash,然后对数组长度取模,HashTable是对key的hashcode直接取模。
  • HashTable是Enumeration遍历,HashMap是Iterator遍历。
  • HashMap和HashTable底层是数组+链表结构实现。
  • HashMap在1.8以后底层是数组+链表+红黑树(链表长度大于8)。

21、HashMap实现原理?

  • 底层实现是数组+链表结构(HashMap在1.8以后底层是数组+链表+红黑树)。
  • HashMap默认初始化时会创建一个默认容量为16的Entry数组,默认加载因子为0.75,同时设置临界值为16*0.75。
  • HashMap会对null值key进行特殊处理,总是放到table[0]位置。
  • put过程是先计算hash然后通过hash与table.length取摸计算index值,然后将key放到table[index]位置,当table[index]已存在其它元素时,判断key的值是否相等(equals方法),若相等直接覆盖;若不相等,会在table[index]位置形成一个链表,将新添加的元素放在table[index],原来的元素通过Entry的next进行链接,这样以链表形式解决hash冲突问题,当链表的长度大于8时,会转换为红黑树。当元素数量达到临界值(capactiyfactor)时,则进行扩容,是table数组长度变为table.length*2。
  • 同样当key为null时会进行特殊处理,在table[0]的链表上查找key为null的元素。
  • get的过程是先计算hash,然后通过hash与table.length取摸计算index值,然后遍历table[index]上的链表,直到找到key,然后返回。
  • remove方法和put get类似,计算hash,计算index,然后遍历查找,将找到的元素从table[index]链表移除。

22、重新调整HashMap大小存在什么问题 ?

  • 多线程的情况下,可能产生条件竞争(race condition)
  • 当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。因此,多线程条件下,Hashmap是线程不安全的。

23、我们可以使用自定义的对象作为Map的键吗?

  • 可以使用任何对象作为键。
  • 只要它遵守了equals()和hashCode()方法的定义规则。
  • 并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象是不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。
  • 如果这个对象是可变的,当属性值改变了后,他的hash相应改变了,get的时候将找不到原对象了。

24、如何让HashMap线程安全?

  • 使用Collections.synchronizedMap()方法来获取一个线程安全的集合(Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步,当然了实际上操作的还是我们传入的HashMap实例,简单的说就是Collections.synchronizedMap()方法帮我们在操作HashMap时自动添加了synchronized来实现线程同步,类似的其它Collections.synchronizedXX方法也是类似原理)。
  • 使用并发类ConcurrentHashMap。

25、ArrayList、LinkedList、Vector的区别 ?

  • Arraylist和Vector是采用动态数组的数据结构,LinkedList基于链表的数据结构。
  • 对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处。
  • 对于新增和删除操作add和remove,LinedList比较占优势,只需要对指针进行修改即可,而ArrayList要移动数据来填补被删除的对象的空间。
  • Vector使用了synchronized方法-线程安全,性能上比ArrayList差一点。
  • ArrayList数组的起始容量是10,当数组需要增长时,新容量=(旧容量*3)/2+1,也就是说每一次容量大概会增长50%。
  • Vector数组的起始容量是10,可以自定义初始容量,新容量=新容量+旧容量。

26、Comparable与Comparator的区别?

  • Comparable & Comparator 都是用来实现集合中元素的比较、排序的,只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序,所以,如想实现排序,就需要在集合外定义 Comparator 接口的方法或在集合内实现 Comparable 接口的方法。
  • Comparator位于包Java.util下,而Comparable位于包 java.lang下。
  • Comparator定义了俩个方法,分别是 int compare(T o1, T o2)和 boolean equals(Object obj),用于比较两个Comparator是否相等。有时在实现Comparator接口时,并没有实现equals方法,可程序并没有报错,原因是实现该接口的类也是Object类的子类,而Object类已经实现了equals方法。
  • Comparable接口只提供了 int compareTo(T o)方法。

27、HashMap 扩充时候是否允许插入?原始长度为什么设置为 16?

HashMap是线程不安全的,允许扩充的时候插入,不过插入的数据将不存在

HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标;算法如下:

/*Returns index for hash code h.*/
static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

假设数组长度分别为15和16,优化后的hash码分别为8和9,那么&运算后的结果如下:

h & (table.length-1) hash table.length-1

8 & (15-1):             0100   &   1110           =     0100

9 & (15-1):             0101   &   1110           =     0100

8 & (16-1):             0100   &   1111           =     0100

9 & (16-1):             0101   &   1111           =     0101

从上面的例子中可以看出:当8、9两个数和(15-1)2=(1110)进行“&运算”的时候,产生了相同的结果,都为0100,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到数组中的同一个位置上形成链表,那么查询的时候就需要遍历这个链表,得到8或者9,这样就降低了查询的效率。
同时,我们也可以发现,当数组长度为15的时候,hash值会与(15-1)2=(1110)进行“&运算”,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!
而当数组长度为16时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1(这是一个奇妙的世界),这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了高位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。
所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。

28、ArrayList使用时常遇到的问题以及解决方法?

集合元素大小为空,get之后没有判断

 list.isEmpty() list.size()>0

ArrayList在迭代的时候不能去改变自身的元素集合,否则会抛异常:java.util.ConcurrentModificationException

List<Integer> list = new ArrayList<Integer>();  
list.add(new Random().nextInt(10));  
list.add(new Random().nextInt(10));  
//开始迭代  
Iterator<Integer> iter = list.iterator();  
while (iter.hasNext()) {  
    System.out.println("迭代:" + iter.next());  
    list.add(new Random().nextInt());  
} 

ArrayList在遍历的时候直接删除,出现ConcurrentModificationException

ArrayList在迭代的时候可以用迭代器删除ArrayList中的元素

List<Integer> list = new ArrayList<Integer>();  
list.add(new Random().nextInt(10));  
list.add(new Random().nextInt(10));  
//开始迭代  
Iterator<Integer> iter = list.iterator();  
while (iter.hasNext()) {  
    System.out.println(iter.next()+"元素被删除");  
    iter.remove();  
} 

CopyOnWriteArrayList是线程安全的集合类,该集合在迭代的时候,可以改变自身的元素集合

List<Integer> syncList = new CopyOnWriteArrayList<Integer>();  
syncList.add(1);  
syncList.add(5);  
Iterator<Integer> iter = syncList.iterator();  
int flag;  
while (iter.hasNext()) {  
   flag = iter.next();  
   System.out.println("迭代:" + flag);  
   syncList.remove(new Integer(flag));  
}  
System.out.println("集合的大小:" + syncList.size()); 

CopyOnWriteArrayList是线程安全的集合类,该集合在迭代的时候,不能用迭代器去删除集合中的元素 ,否则会抛异常:java.lang.UnsupportedOperationException

List<Integer> syncList = new CopyOnWriteArrayList<Integer>();  
syncList.add(1);  
syncList.add(5);  
Iterator<Integer> iter = syncList.iterator();  
while (iter.hasNext()) {  
    System.out.println("迭代:" + iter.next());  
    iter.remove();  
}  
System.out.println("集合的大小:" + syncList.size()); 

29、TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?

TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。

30、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?

sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。

31、线程的sleep()方法和yield()方法有什么区别?

  • sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会。
  • 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态。
  • sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常。
  • sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

32、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?

不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。

33、说出与线程同步以及线程调度相关的方法?

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

34、多线程程序有几种实现方式?

Java 5以前实现多线程有两种实现方法:一种是继承Thread类;另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。

35、synchronized关键字的用法?

synchronized关键字可以将对象或者方法标记为同步,以实现对对象和方法的互斥访问,可以用synchronized(对象) { … }定义同步代码块,或者在声明方法时将synchronized作为方法的修饰符。

36、举例说明同步和异步。

如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。

37、启动一个线程是调用run()还是start()方法?

启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM 调度并执行,这并不意味着线程就会立即运行。run()方法是线程启动后要进行回调(callback)的方法。

38、什么是线程池(thread pool)?

在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:

  • newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  • newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  • newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
  • newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
  • newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

39、简述synchronized 和java.util.concurrent.locks.Lock的异同?

Lock是Java 5以后引入的新的API,和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的所有功能;主要不同点:Lock有比synchronized更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且最好在finally 块中释放(这是释放外部资源的最好的地方)。

40、Java中如何实现序列化,有什么意义?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。
要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆。

41、Java中有几种类型的流?

字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io 包中还有许多其他的流,主要是为了提高性能和使用方便。关于Java的I/O需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外Java中的流不同于C#的是它只有一个维度一个方向。

二、框架

1、hibernate是什么,工作原理?

Hibernate是对JDBC进行了非常轻量级的对象封装,Hibernate将POJO与数据库表建立映射关系,是一个全自动的orm框架,Hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。

Hibernate工作原理:

  • 配置好Hibernate的配置文件和与类对应的配置文件后,启动服务器。
  • 服务器通过实例化Configeration对象,读取hibernate.cfg.xml文件的配置内容,并根据相关的需求建好表或者和表建立好映射关系。
  • 通过实例化的Configeration对象就可以建立sessionFactory实例,进一步,通过sessionFactory实例可以创建session对象。
  • 得到session之后,便可以对数据库进行增删改查操作了,除了比较复杂的全文搜索外,简单的操作都可以通过hibernate封装好的session内置方法来实现。
  • 此外,还可以通过事物管理,表的关联来实现较为复杂的数据库设计

2、Hibernate的缓存级别?

Hibernate缓存包括两大类:一级缓存和二级缓存

  • Hibernate一级缓存又被称为“Session的缓存”。Session缓存是内置的,不能被卸载,是事务范围的缓存,在一级缓存中,持久化类的每个实例都具有唯一的OID。
  • Hibernate二级缓存又称为“SessionFactory的缓存”,由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别,第二级缓存是可选的,是一个可配置的插件,默认下SessionFactory不会启用这个插件。

3、锁机制有什么用?简述Hibernate的悲观锁和乐观锁机制?

有些业务逻辑在执行过程中要求对数据进行排他性的访问,于是需要通过一些机制保证在此过程中数据被锁住不会被外界修改,这就是所谓的锁机制。
Hibernate支持悲观锁和乐观锁两种锁机制。悲观锁,顾名思义悲观的认为在数据处理过程中极有可能存在修改数据的并发事务(包括本系统的其他事务或来自外部系统的事务),于是将处理的数据设置为锁定状态。悲观锁必须依赖数据库本身的锁机制才能真正保证数据访问的排他性。乐观锁,顾名思义,对并发事务持乐观态度(认为对数据的并发操作不会经常性的发生),通过更加宽松的锁机制来解决由于悲观锁排他性的数据访问对系统性能造成的严重影响。最常见的乐观锁是通过数据版本标识来实现的,读取数据时获得数据的版本号,更新数据时将此版本号加1,然后和数据库表对应记录的当前版本号进行比较,如果提交的数据版本号大于数据库中此记录的当前版本号则更新数据,否则认为是过期数据无法更新。Hibernate中通过Session的get()和load()方法从数据库中加载对象时可以通过参数指定使用悲观锁;而乐观锁可以通过给实体类加整型的版本字段再通过XML或@Version注解进行配置。

4、阐述Hibernate实体对象的三种状态以及转换关系?

最新的Hibernate文档中为Hibernate对象定义了四种状态分别是:瞬时态(new, or transient)、持久态(managed, or persistent)、游离态(detached)和移除态(removed,以前Hibernate文档中定义的三种状态中没有移除态)。

  • 瞬时态:当new一个实体对象后,这个对象处于瞬时态,即这个对象只是一个保存临时数据的内存区域,如果没有变量引用这个对象,则会被JVM的垃圾回收机制回收。这个对象所保存的数据与数据库没有任何关系,除非通过Session的save()、saveOrUpdate()、persist()、merge()方法把瞬时态对象与数据库关联,并把数据插入或者更新到数据库,这个对象才转换为持久态对象。
  • 持久态:持久态对象的实例在数据库中有对应的记录,并拥有一个持久化标识(ID)。对持久态对象进行delete操作后,数据库中对应的记录将被删除,那么持久态对象与数据库记录不再存在对应关系,持久态对象变成移除态(可以视为瞬时态)。持久态对象被修改变更后,不会马上同步到数据库,直到数据库事务提交。
  • 游离态:当Session进行了close()、clear()、evict()或flush()后,实体对象从持久态变成游离态,对象虽然拥有持久和与数据库对应记录一致的标识值,但是因为对象已经从会话中清除掉,对象不在持久化管理之内,所以处于游离态(也叫脱管态)。游离态的对象与临时状态对象是十分相似的,只是它还含有持久化标识。

5、简述Hibernate常见优化策略?

常用的有:
① 制定合理的缓存策略(二级缓存、查询缓存)。
② 采用合理的Session管理机制。
③ 尽量使用延迟加载特性。
④ 设定合理的批处理参数。
⑤ 如果可以,选用UUID作为主键生成器。
⑥ 如果可以,选用基于版本号的乐观锁替代悲观锁。
⑦ 在开发过程中, 开启hibernate.show_sql选项查看生成的SQL,从而了解底层的状况;开发完成后关闭此选项。
⑧ 考虑数据库本身的优化,合理的索引、恰当的数据分区策略等都会对持久层的性能带来可观的提升,但这些需要专业的DBA(数据库管理员)提供支持。

6、MyBatis工作流程?

  • 加载配置文件(mybatis-config.xml 、 *…Mapper.xml)并初始化,
    将SQL的配置信息加载成为一个个MappedStatement对象,包括了传
    入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
  • 接收调用请求(调用Mybatis提供的API即增删改查的方法)并传入参数:即SQL的ID和传入参数对象。
  • 处理操作请求,过程:
    ①根据SQL的ID查找对应的MappedStatement对象。
    ②根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
    ③获取数据库连接,根据得到的最终SQL语句和执行传入参数,到数据库执行,并得到执行结果。
    ④根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果。
  • 返回处理结果将最终的处理结果返回。
    如果你对MyBatis实现原理很感兴趣可以去看看我这篇文章:自定义持久层框架设计思路和源代码

7、MyBatis和Hibernate缓存机制的区别?

  1. 相同点

    Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。

  2. 不同点

    Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。

    MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

  3. 两者比较

    因为Hibernate对查询对象有着良好的管理机制,用户无需关心SQL。所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示。而MyBatis在这一方面,使用二级缓存时需要特别小心。如果不能完全确定数据更新操作的波及范围,避免Cache的盲目使用。否则,脏数据的出现会给系统的正常运行带来很大的隐患。

8、Spring中的事务管理支持哪几种方式,以及每种方式具体方法?

  1. 声明式事务@Transactional

​ @Transactional注解是一个用来定义一个接口、类或者方法必须具备事务化语义的元数据; 例如, " 在调用该方法时挂起所有已经存在的事务,开始一个新的只读事务 "。下面是@Transactional注解的默认设置:

  • 传播设置是PROPAGATION_REQUIRED。

  • 隔离等级是ISOLATION_DEFAULT。

  • 事务是可读可写的。

  • 事务超时是使用底层事务系统的默认值, 或者在不支持时没有。

  • 任何的RuntimeException触发回滚, 并且所有的检查的Exception不触发。

    这些默认设置都是可以修改的。

  1. 编程式事务

    Spring Framework提供了两种方式的编程式事务管理:

  • 使用TransactionTemplate。

  • 直接使用PlatformTransactionManager的一个实现。

    Spring一般都推荐使用TransactionTemplate来进行编程式事务管理. 第二种方式有点类似于使用JTA的 UserTransaction接口, 尽管异常处理没有那么复杂化了。

9、Spring中Bean的作用域有哪些?

  • singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。

  • prototype:为每一个bean请求提供一个实例。

  • request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

  • session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。

  • global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。

10、什么是IoC和DI?DI是如何实现的?

IoC叫控制反转,是Inversion of Control的缩写,DI(Dependency Injection)叫依赖注入,DI是对IoC更简单的诠释。控制反转是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的"控制反转"就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,由容器来创建对象并管理对象之间的依赖关系。依赖注入(DI)的基本原则是应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由容器负责,查找资源的逻辑应该从应用组件的代码中抽取出来,交给容器来完成。DI是对IoC更准确的描述,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。

依赖注入(DI)可以通过setter方法注入(设值注入)、构造器注入和接口注入三种方式来实现,Spring支持setter注入和构造器注入,通常使用构造器注入来注入必须的依赖关系,对于可选的依赖关系,则setter注入是更好的选择,setter注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。
在这里插入图片描述

11、什么叫AOP(面向切面编程)?

AOP(Aspect-Oriented Programming)指一种程序设计范型,该范型以一种称为切面(aspect)的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting concern)。

12、如何理解"横切关注"这个概念的?

"横切关注"是会影响到整个应用程序的关注功能,它跟正常的业务逻辑是正交的,没有必然的联系,但是几乎所有的业务逻辑都会涉及到这些关注功能。通常,事务、日志、安全性等关注就是应用中的横切关注功能。

13、如何理解AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)这些概念?

  • 连接点(Joinpoint):程序执行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。Spring仅支持方法的连接点。
  • 切点(Pointcut):如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。
  • 增强(Advice):增强是织入到目标类连接点上的一段程序代码。Spring提供的增强接口都是带方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。很多资料上将增强译为“通知”,这明显是个词不达意的翻译,让很多程序员困惑了许久。
  • 引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过引介功能,可以动态的未该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
  • 织入(Weaving):织入是将增强添加到目标类具体连接点上的过程,AOP有三种织入方式:①编译期织入:需要特殊的Java编译期(例如AspectJ的ajc);②装载期织入:要求使用特殊的类加载器,在装载类的时候对类进行增强;③运行时织入:在运行时为目标类生成代理实现增强。Spring采用了动态代理的方式实现了运行时织入,而AspectJ采用了编译期织入和装载期织入的方式。
  • 切面(Aspect):切面是由切点和增强(引介)组成的,它包括了对横切关注功能的定义,也包括了对连接点的定义。

14、Spring中自动装配的方式有哪些?

  • no:不进行自动装配,手动设置Bean的依赖关系。
  • byName:根据Bean的名字进行自动装配。
  • byType:根据Bean的类型进行自动装配。
  • constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。
  • autodetect:如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配。

15、Spring有哪些事务传播行为?

  • PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

  • PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

  • PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

  • PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

16、Spring有哪些隔离级别?

  • ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。

  • ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。

  • ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。

  • ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。

  • ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。

17、Spring MVC的工作原理是怎样的?

在这里插入图片描述

  • 客户端的所有请求都交给前端控制器(DispatcherServlet)来处理,它会负责调用系统的其他模块来真正处理用户的请求。
  • 控制器(DispatcherServlet)收到请求后,将根据请求的信息(包括URL、HTTP协议方法、请求头、请求参数、Cookie等)以及映射器(HandlerMapping)的配置找到处理该请求的Handler(任何一个对象都可以作为请求的Handler)。
  • 在这个地方Spring会通过适配器(HandlerAdapter)对该处理器进行封装。
  • 适配器(HandlerAdapter)用统一的接口对各种Handler中的方法进行调用。
  • Handler完成对用户请求的处理后,会返回一个视图对象(ModelAndView)对象给控制器(DispatcherServlet),ModelAndView顾名思义,包含了数据模型以及相应的视图的信息。
  • ModelAndView的视图是逻辑视图,DispatcherServlet还要借助ViewResolver完成从逻辑视图到真实视图对象的解析工作。
  • 当得到真正的视图对象后,DispatcherServlet会利用视图对象对模型数据进行渲染。
  • 客户端得到响应,可能是一个普通的HTML页面,也可以是XML或JSON字符串,还可以是一张图片或者一个PDF文件。

18、Struts2工作原理及工作流程?

在这里插入图片描述

  1. 工作原理
  • 客户端初始化一个指向Servlet容器(例如Tomcat)的请求 。

  • 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)

  • 接着控制器(FilterDispatcher)被调用,控制器(FilterDispatcher)询问映射器(ActionMapper)来决定这个请是否需要调用某个Action 。

  • 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy 。

  • ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类 。

  • ActionProxy创建一个ActionInvocation的实例。

  • ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

  • 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可 能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper

  1. 工作流程
  • 客户端浏览器发出HTTP请求 。
  • 根据web.xml配置,该请求被控制器(FilterDispatcher)接收 。
  • 根据struts.xml配置,找到需要调用的Action类和方法, 并通过IoC方式,将值注入给Aciton 。
  • Action调用业务逻辑组件处理业务逻辑,这一步包含表单验证。
  • Action执行完毕,根据struts.xml中的配置找到对应的返回结果result,并跳转到相应页面 。
  • 返回HTTP响应到客户端浏览器 。

三、数据库

1、事务四大特性?

  • 原子性:要么执行,要么不执行,也就是不可再分,已经最小了。
  • 隔离性:所有操作全部执行完以前其它会话不能看到过程。
  • 一致性:事务前后,数据总额一致。
  • 持久性:一旦事务提交,对数据的改变就是永久的。

2、数据库隔离级别?

  • 脏读:事务B读取事务A还没有提交的数据。
  • 不可重复读:两次事务读的数据不一致。
  • 幻读:事务A修改了数据,事务B也修改了数据,这时在事务A看来,明明修改了数据,咋不一样。

3、索引的优缺点,什么时候使用索引,什么时候不能使用索引?

  • 索引最大的好处是提高查询速度。
  • 缺点是更新数据时效率低,因为要同时更新索引。
  • 对数据进行频繁查询进建立索引,如果要频繁更改数据不建议使用索引。

4、数据库索引为什么会提高查询速度?

  • 数据库在执行一条SQL语句的时候,默认的方式是根据搜索条件进行全表扫描,遇到匹配条件的就加入搜索结果集合。
  • 有索引的话,首先去索引列表中查询,而索引列表是B类树的数据结构,定位到特定值的行就会非常快,所以其查询速度就很快。索引就是通过事先排好序,从而在查找时可以应用二分查找等高效率的算法。
  • 一般的顺序查找,复杂度为O(n),而二分查找复杂度为O(log2n)。当n很大时,二者的效率相差及其悬殊。

5、数据库三大范式?

  • 属性不可分。
  • 非主键属性,完全依赖于主键属性。
  • 非主键属性无传递依赖。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值