【转载】最全阿里 Java 面试题总结(Java基础)

目录

1.JAVA中的几种基本数据类型是什么?各自占用多少字节?

2.String类能被继承吗,为什么?

3.String,Stringbuffer,StringBuilder的区别?

4.ArrayList和LinkedList有什么区别?

5.讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序?

6.用过哪些Map类,都有什么区别,HashMap是线程安全的吗?并发下使用的Map是什么?他们内部原理分别是什么?比如存储方式,hashcode,扩容,默认容量等?

7.JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗?如果你来设计,你如何设计?

8.有没有有顺序的Map实现类,如果有,他们是怎么保证有序的?

9.抽象类(abstract)和接口(Interface)的区别?类可以继承多个类吗?接口可以继承多个接口吗?类可以实现多个接口吗?

10.继承和聚合的区别在哪?

11.IO模型有哪些?讲讲你理解的nio ,它和bio,aio的区别是啥?谈谈reactor模型?

总结

12.反射的原理,反射创建类实例的三种方式是什么?

13.反射中,Class.forName和ClassLoader区别?

14.描述动态代理的几种实现方式,分别说出相应的优缺点?

15.为什么cglib方式可以对接口实现代理?

16.final的用途?

17.写出三种单例模式实现?

19.如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣?

20.请结合OO设计理念,谈谈访问修饰符public、private、protected、default在应用设计中的作用?

21.深拷贝和浅拷贝区别?

22.error和exception的区别?CheckedException,RuntimeException的区别?

23.请列出5个运行时异常?

24.在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么?

25.说一说你对java.lang.Object对象中hashCode和equals方法的理解?在什么场景下需要重新实现这两个方法?

26.在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题?

27.有没可能2个不相等的对象有同hashcode?

28.这样的a.hashcode() 有什么用,与a.equals(b)有什么关系?

29.Java中的HashSet内部是如何工作的?

30.什么是序列化?怎么序列化?为什么序列化?反序列化会遇到什么问题?如何解决?

31.java8的新特性?

32.什么是基于注解的切面实现?

33.什么是对象/关系映射集成模块?

34.什么是 ACID?

35.BS与CS的联系与区别?

36.Cookie 和 Session的区别?

37.fail-fast 与 fail-safe 机制有什么区别?

38.get 和 post请求的区别?

39.IOC的优点是什么?

40.IO 和 NIO的区别,NIO优点?

41.JRE、JDK、JVM 及 JIT 之间有什么不同?

42.MVC的各个部分都有那些技术来实现?如何实现?

43.什么是 Web Service(Web服务)?

44.简单说说你了解的类加载器。是否实现过类加载器?

45.解释一下什么叫AOP(面向切面编程)?

46.请简述 Servlet 的生命周期及其相关的方法?

47.说一下强引用、软引用、弱引用、虚引用以及他们之间和gc的关系?

48.多线程同步的五种方法?

49.作用域public,private,protected,以及不写时的区别?

50.解析float型float f=3.4是否正确?编译能否通过?

51.java语言中有没有goto关键字?


1.JAVA中的几种基本数据类型是什么?各自占用多少字节?

1、bit --位:位是计算机中存储数据的最小单位,指二进制数中的一个位数,其值为“0”或“1”。
2、byte --字节:字节是计算机存储容量的基本单位,一个字节由8位二进制数组成。在计算机内部,一个字节可以表示一个数据,也可以表示一个英文字母,两个字节可以表示一个汉字。
1B=8bit 
1Byte=8bit
1KB=1024Byte(字节)=8*1024bit
1MB=1024KB
1GB=1024MB
1TB=1024GB

Java基本数据类型
int     32bit
short   16bit
long     64bit
byte     8bit
char     16bit
float   32bit
double   64bit
boolean 1bit
(boolean 的备注+翻译)
This data type represents one bit of information, but its "size" isn't something that's precisely defined.(ref)
这种数据类型代表一个比特的信息,但它的“大小”没有明确的定义。(参考)

String是引用数据类型,不是基本数据类型。

2.String类能被继承吗,为什么?

因为Sting是这样定义的:public final class String extends Object,里边有final关键字,所以不能被继承。

什么样的类不能被继承?

 一,在Java中,只要是被定义为final的类,也可以说是被final修饰的类,就是不能被继承的。
 二,final是java中的一个关键字,可以用来修饰变量、方法和类。用关键词final修饰的域成为最终域。用关键词final修饰的变量一旦赋值,就不能改变,也称为修饰的标识为常量。如果一个类的域被关键字final所修饰,它的取值在程序的整个执行过程中将不会改变。
 三,假如说整个类都是final,就表明自己不希望从这个类继承,或者不答应其他任何人采取这种操作。换言之,出于这样或那样的原因,我们的类肯定不需要进行任何改变;或者出于安全方面的理由,我们不希望进行子类化(子类处理)。

3.String,Stringbuffer,StringBuilder的区别?

String类的内容一旦声明则不可改变,而StringBuffer类与StringBuilder类声明的内容可以改变。

StringBuffer类中提供的方法都是同步方法,属于安全的线程操作,而StringBuilder类中方法属于异步方法,属于非线程安全的操作。

String 字符串常量

StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)

简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。

而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:

String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);

你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个
String S1 = “This is only a” + “ simple” + “test”; 其实就是:
String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;
这时候 JVM 会规规矩矩的按照原来的方式去做。

在大部分情况下 StringBuffer > String
StringBuffer
Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。


在大部分情况下 StringBuilder > StringBuffer

java.lang.StringBuilde
java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

4.ArrayList和LinkedList有什么区别?

一般大家都知道ArrayList和LinkedList的大致区别: 

1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 

一, 时间复杂度:

首先一点关键的是,ArrayList的内部实现是基于基础的对象数组的,因此,它使用get方法访问列表中的任意一个元素时(random access),它的速度要比LinkedList快。LinkedList中的get方法是按照顺序从列表的一端开始检查直到另外一端。对LinkedList而言,访问列表中的某个指定元素没有更快的方法了。 

假设我们有一个很大的列表,它里面的元素已经排好序了,这个列表可能是ArrayList类型的也可能是LinkedList类型的,现在我们对这个列表来进行二分查找(binary search),比较列表是ArrayList和LinkedList时的查询速度,看下面的程序:

package com.mangocity.test;   
import java.util.LinkedList;   
import java.util.List;   
import java.util.Random;   
import java.util.ArrayList;   
import java.util.Arrays;   
import java.util.Collections;   
public class TestList ...{   
     public static final int N=50000;   
  
     public static List values;   
  
     static...{   
         Integer vals[]=new Integer[N];   
  
         Random r=new Random();   
  
         for(int i=0,currval=0;i<N;i++)...{   
             vals=new Integer(currval);   
             currval+=r.nextInt(100)+1;   
         }   
  
         values=Arrays.asList(vals);   
     }   
  
     static long timeList(List lst)...{   
         long start=System.currentTimeMillis();   
         for(int i=0;i<N;i++)...{   
             int index=Collections.binarySearch(lst, values.get(i));   
             if(index!=i)   
                 System.out.println("***错误***");   
         }   
         return System.currentTimeMillis()-start;   
     }   
     public static void main(String args[])...{   
         System.out.println("ArrayList消耗时间:"+timeList(new ArrayList(values)));   
         System.out.println("LinkedList消耗时间:"+timeList(new LinkedList(values)));   
     }   
}

我得到的输出是:ArrayList消耗时间:15s
                             LinkedList消耗时间:2596s

看这样一个例子,假如我们有一个列表,要对其进行大量的插入和删除操作,在这种情况下LinkedList就是一个较好的选择。请看如下一个极端的例子,我们重复的在一个列表的开端插入一个元素:

package com.mangocity.test;   
  
import java.util.*;   
public class ListDemo {   
     static final int N=50000;   
     static long timeList(List list){   
     long start=System.currentTimeMillis();   
     Object o = new Object();   
     for(int i=0;i<N;i++)   
         list.add(0, o);   
     return System.currentTimeMillis()-start;   
     }   
     public static void main(String[] args) {   
         System.out.println("ArrayList耗时:"+timeList(new ArrayList()));   
         System.out.println("LinkedList耗时:"+timeList(new LinkedList()));   
     }   
}   

这时我的输出结果是:ArrayList耗时:246s

                                    LinkedList耗时:15s

对于插入、删除这样的操作,linkedlist 更具有优势(只是断开了原来的指针,加了新的指针)。而对于Arraylist 而言几乎类似于整体的搬迁,(在Arraylist尾部增、删的操作除外)。

二.空间复杂度 
在LinkedList中有一个私有的内部类,定义如下:

private static class Entry {   
         Object element;   
         Entry next;   
         Entry previous;   
     }   

每个Entry对象reference列表中的一个元素,同时还有在LinkedList中它的上一个元素和下一个元素。一个有1000个元素的LinkedList对象将有1000个链接在一起的Entry对象,每个对象都对应于列表中的一个元素。这样的话,在一个LinkedList结构中将有一个很大的空间开销,因为它要存储这1000个Entity对象的相关信息。 
ArrayList使用一个内置的数组来存储元素,这个数组的起始容量是10.当数组需要增长时,新的容量按如下公式获得:新容量=(旧容量*3)/2+1,也就是说每一次容量大概会增长50%。这就意味着,如果你有一个包含大量元素的ArrayList对象,那么最终将有很大的空间会被浪费掉,这个浪费是由ArrayList的工作方式本身造成的。如果没有足够的空间来存放新的元素,数组将不得不被重新进行分配以便能够增加新的元素。对数组进行重新分配,将会导致性能急剧下降。如果我们知道一个ArrayList将会有多少个元素,我们可以通过构造方法来指定容量。我们还可以通过trimToSize方法在ArrayList分配完毕之后去掉浪费掉的空间。

总结:
ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方,总的说来可以描述如下: 
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。


2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。


3.LinkedList不支持高效的随机元素访问。


4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间。


可以这样说:当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。

5.讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序?

此题考察的是类加载器实例化时进行的操作步骤(加载–>连接->初始化)。 
父类静态变量、 
父类静态代码块、 
子类静态变量、 
子类静态代码块、 
父类非静态变量(父类实例成员变量)、 
父类构造函数、 
子类非静态变量(子类实例成员变量)、 
子类构造函数。 

6.用过哪些Map类,都有什么区别,HashMap是线程安全的吗?并发下使用的Map是什么?他们内部原理分别是什么?比如存储方式,hashcode,扩容,默认容量等?

Java中的map是一个很重要的集合,他是一个接口,下面继承它实现了多个实现类,这些类各有千秋,各自有个各自的优点和缺点,先上图:

map的主要特点是键值对的形式,一一对应,且一个key只对应1个value。其常用的map实现类主要有HashMap、HashTable、TreeMap、ConcurrentHashMap、LinkedHashMap、weakHashMap等等。

一、HashMap的工作原理
HashMap的底层是用hash数组和单向链表实现的 ,当调用put方法时,首先计算key的hashcode,定位到合适的数组索引,然后再在该索引上的单向链表进行循环遍历用equals比较key是否存在,如果返回true,则新的value值覆盖原来的值;如果返回false,则插入单向链表的头部。

HashMap的两个重要属性是容量capacity和加载因子loadfactor,默认值分布为16和0.75,当容器中的元素个数大于 capacity*loadfactor时,容器会进行扩容resize 为2n,在初始化Hashmap时可以对着两个值进行修改,负载因子0.75被证明为是性能比较好的取值,通常不会修改,那么只有初始容量capacity会导致频繁的扩容行为,这是非常耗费资源的操作,所以,如果事先能估算出容器所要存储的元素数量,最好在初始化时修改默认容量capacity,以防止频繁的resize操作影响性能。

HashMap在容量不够进行resize时,由于高并发可能会出现死链,导致CPU占用飙升,在开发过程中可以使用高并发包里的ConcurrentHashMap,或者加锁的方式规避此风险。

二、HashMap和Hashtable有什么区别
1、HashMap是非线程安全的,HashTable是线程安全的。 
2、HashMap的键和值都允许有null值存在,而HashTable则不行。不允许存null的地方存储null值时会抛出NPE异常。 
3、因为线程安全的问题,HashMap效率比HashTable的要高。 
4、Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。 
5、HashMap提供了可供迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的枚举(Enumeration),是安全失败的。

注意:一般现在不建议用HashTable, ①是HashTable是遗留类,内部实现很多没优化和冗余。②即使在多线程环境下,现在也有同步的ConcurrentHashMap替代,没有必要因为是多线程而用HashTable。

三、ConcurrentHashMap

这个map实现类是在jdk1.5中加入的,其在jdk1.6/1.7中的主要实现原理是segment段锁,它不再使用和HashTable一样的synchronize一样的关键字对整个方法进行枷锁,而是转而利用segment段落锁来对其进行加锁,以保证Map的多线程安全。

其实可以理解为,一个ConcurrentHashMap是由多个HashTable组成,所以它允许获取不用段锁的线程同时持有该资源,segment有多少个,理论上就可以同时有多少个线程来持有它这个资源。

其实可以理解为,一个ConcurrentHashMap是由多个HashTable组成,所以它允许获取不用段锁的线程同时持有该资源,segment有多少个,理论上就可以同时有多少个线程来持有它这个资源。其默认的segment是一个数组,默认长度为16。也就是说理论上可以提高16倍的性能。

但是要注意咯,在JAVA的jdk1.8中则对ConcurrentHashMap又再次进行了大的修改,取消了segment段锁字段,采用了CAS+Synchronize技术来保障线程安全。底层采用数组+链表+红黑树的存储结构,也就是和HashMap一样。这里注意Node其实就是保存一个键值对的最基本的对象。其中Value和next都是使用的volatile关键字进行了修饰,以确保线程安全。这里推荐一下大神的Volatile的深入理解篇,写的非常好http://www.cnblogs.com/xrq730/p/7048693.html

在插入元素时,会首先进行CAS判定,如果OK就插入其中,并将size+1,但是如果失败了,就会通过自旋锁自旋后再次尝试插入,直到成功。

所谓的CAS也就是compare And Swap,即在更改前先对内存中的变量值和你指定的那个变量值进行比较,如果相同这说明在这期间没有被修改过,则可以进行修改,而如果不一样了,则就要停止修改,否则就会覆盖掉其他的参数。即内存值a,旧值b,和要修改的值c,如果这里a=b,那么就可以进行更新,就可以将内存值a修改成c。否则就要终止该更新操作。
为什么这里会用volatile进行修饰,我在其他博客找到了答案。主要有两个用处:

1、令这个被修饰的变量的更新具有可见性,一旦该变量遭到了修改,其他线程立马就会知道,立马放弃自己在自己工作内存中持有的该变量值,转而重新去主内存中获取到最新的该变量值。

2、产生了内存屏障,这里volatile可以保证CPU在执行代码时保证,所有被volatile中被修饰的之前的一定在之前被执行,也就是所谓的“指令重排序”。

同hashMap一样,在JDK1.8中,如果链表中存储的Entry超过了8个则就会自动转换链表为红黑树,提高查询效率。

四、TreeMap

TreeMap也是一个很常用的map实现类,因为他具有一个很大的特点就是会对Key进行排序,使用了TreeMap存储键值对,再使用iterator进行输出时,会发现其默认采用key由小到大的顺序输出键值对,如果想要按照其他的方式来排序,需要重写也就是override 它的compartor接口。此处引用一下其他大神的代码:

 1 import java.util.Comparator;
 2 import java.util.Iterator;
 3 import java.util.Set;
 4 import java.util.TreeMap;
 5 
 6 
 7 public class Compare {
 8     public static void main(String[] args) {
 9         TreeMap<String,Integer> map = new TreeMap<String,Integer>(new xbComparator());
10         map.put("key_1", 1);
11         map.put("key_2", 2);
12         map.put("key_3", 3);   
13         Set<String> keys = map.keySet();
14         Iterator<String> iter = keys.iterator();
15         while(iter.hasNext())
16         {
17                 String key = iter.next();
18                 System.out.println(" "+key+":"+map.get(key));
19         }
20     }
21 }
22 class xbComparator implements Comparator
23 {
24     public int compare(Object o1,Object o2)
25     {
26         String i1=(String)o1;
27         String i2=(String)o2;
28         return -i1.compareTo(i2);
29     }
30 }

另外,TreeMap底层的存储结构也是一颗红黑树。是不是发现好多都是红黑树,没错因为红黑树查找效率高,只有O(lgn)。它是一种自平衡的二叉查找树。在每次插入和删除节点时,都可以自动调节树结构,以保证树的高度是lgn。

五、LinkedHashMap

LinkedHashMap它的特点主要在于linked,带有这个字眼的就表示底层用的是链表来进行的存储。相对于其他的无序的map实现类,还有像TreeMap这样的排序类,linkedHashMap最大的特点在于有序,但是它的有序主要体现在先进先出FIFIO上。没错,LinkedHashMap主要依靠双向链表和hash表来实现的。

仔细看,这里虽然在计算hashcode时还是发生了hash冲突,采用了链地址法解决了冲突,但是这里的Entry对象是采用双向链表保存的,每个Entry都有一个after和before的属性。当插入一个entry时,如果发生了冲突,就可以将新的Entry插入Entry链表中的头部,但是按照双向链表的角度来说,又会将该Entry插入到双向链表的尾部。
 

7.JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗?如果你来设计,你如何设计?

jdk8 放弃了分段锁而是用了Node锁,减低锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。

但是ConcurrentHashMap的一些操作使用了synchronized锁,而不是ReentrantLock,虽然说jdk8的synchronized的性能进行了优化,但是我觉得还是使用ReentrantLock锁能更多的提高性能
 

8.有没有有顺序的Map实现类,如果有,他们是怎么保证有序的?

LinkedHashMap 是基于元素进入集合的顺序或者被访问的先后顺序排序,TreeMap 则是基于元素的固有顺序 (由 Comparator 或者 Comparable 确定)(TreeMap默认升序,LinkedHashMap则记录了插入顺序)。

9.抽象类(abstract)和接口(Interface)的区别?类可以继承多个类吗?接口可以继承多个接口吗?类可以实现多个接口吗?

先从总体解释抽象类和接口的基本概念,然后再比较两者的语法细节,最后再说两者的应用区别。

抽象类(abstract)

含有abstract修饰符的class即为抽象类,abstract 类不能创建的实例对象。

含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。

abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。

如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

接口(interface)

interface 可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。

接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

下面比较一下两者的语法区别:

1.抽象类可以有构造方法,接口中不能有构造方法。

2.抽象类中可以有普通成员变量,接口中没有普通成员变量

3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

4. 抽象类中可以包含静态方法,接口中不能包含静态方法

5. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。

6. 一个类可以实现多个接口,但只能继承一个抽象类。

7.抽象类的抽象方法是由非抽象类的子类实现,接口的抽象方法由接口的实现类实现。

8.接口不能有私有的方法跟对象,抽象类可以有自己的私有的方法跟对象。

9.类不可以继承多个类,接口可以继承多个接口,类可以实现多个接口。

10.继承和聚合的区别在哪?

继承:指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识,在设计时一般没有争议性。

聚合:聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享;比如计算机与CPU、公司与员工的关系等;表现在代码层面,和关联关系是一致的,只能从语义级别来区分。

11.IO模型有哪些?讲讲你理解的nio ,它和bio,aio的区别是啥?谈谈reactor模型?

1、阻塞式IO模型

2、非阻塞式IO模型

3、信号驱动IO模型

4、IO多路转接模型

5、异步IO模型

IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。

一、BIO

     在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。

二、NIO

    NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题: 在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。

NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。  也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。

BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。

NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。

在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发上来的话,还是会有BIO一样的问题。

HTTP/1.1出现后,有了Http长连接,这样除了超时和指明特定关闭的http header外,这个链接是一直打开的状态的,这样在NIO处理中可以进一步的进化,在后端资源中可以实现资源池或者队列,当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列里面就返回,并且在全局的地方保持住这个现场(哪个连接的哪个请求等),这样前面的线程还是可以去接受其他的请求,而后端的应用的处理只需要执行队列里面的就可以了,这样请求处理和后端应用是异步的.当后端处理完,到全局地方得到现场,产生响应,这个就实现了异步处理。

三、AIO

     与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。  即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。  在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel

其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。

BIO是一个连接一个线程。

NIO是一个请求一个线程。

AIO是一个有效请求一个线程。

先来个例子理解一下概念,以银行取款为例: 

  • 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
  • 异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
  • 阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
  • 非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)

Java对BIO、NIO、AIO的支持:

  • Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

  • Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

  • Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

BIO、NIO、AIO适用场景分析:

  • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

  • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

  • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。

在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作。

Reactor模型:反应器模式(事件驱动模式):当一个主体发生改变时,所有的属体都得到通知,类似于观察者模式。

总结

任何IO过程中,都包含两个步骤。一是等待,二是拷贝。 
在实际的应用场景中,等待消耗的时间往往都远远高于拷贝的时间。 
所以,让IO更高效, 最核心的办法就是让等待的时间尽量少,也就是阻塞越少,理论上效率也是最优。

12.反射的原理,反射创建类实例的三种方式是什么?

Java 反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java 的反射机制。
面试的时候一句话:
Java反射机制在运行的时候可以通过反射机制获得任意一个类的所有属性跟方法,也可以调用它的属性跟方法.
想再获得点分数 可以接着说:
Spring 中的 IOC 的底层实现原理就是反射机制,Spring 的容器会帮我们创建实例,该容器中使用的方法就是反射,通过解析xml文件,获取到id属性和class属性里面的内容,利用反射原理创建配置文件里类的实例对象,存入到Spring的bean容器中。

反射机制:
所谓的反射机制就是java语言在运行时拥有一项自观的能力。通过这种能力可以彻底的了解自身的情况为下一步的动作做准备。下面具体介绍一下java的反射机制。这里你将颠覆原来对java的理解。 Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;其中

class - 类对象

Constructor-类的构造器对象

Field-类的属性对象

Method-类的方法对象

通过这四个对象我们可以粗略的看到一个类的各个组成部分。Java反射之类的实例对象的三种表示方式:

public class ClassDemo1 {
public static void main(String[] args) {
    
    //Foo的实例对象如何表示
    Foo foo1 = new Foo();//foo1就表示出来了
    //Foo这个类,也是一个实例对象,Class类的实例对象,如何表示呢、
    //任何一个类都是Class的实例对象,这个实例对象那个有三个表示方式
    //第一种表示方式--》实际在告诉我们任何一个类都有一个隐含的静态成员变量class
    Class class1 = Foo.class;
    
    //第二种表示方式  已经知道该类的对象通过getClass方法
    Class class2 = foo1.getClass();
    
    /*
     * 官网class1 ,class2表示了Foo类的类类型(class type)
     * 万事万物 都是对象
     * 类也是对象,是Class类的实例对象
     * 这个对象我们称为该类的类类型
     */
    //不管class1  or class2都代表了Foo类的类类型,一个类只可能是Class;类的一个实例对象
    System.out.println(class1==class2);//true'
    
    //第三种表达方式
    Class class3 = null;
    try {
        class3 = Class.forName("com.imooc.reflect.Foo");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    //
    System.out.println(class2==class3);//true
    
    //我们完全尅通过类的类类型创建该类的对象实例--》通过class1  or class2 or class3
    //创建Foo类的实例对象
    try {
        //需要有无参数的构造方法
        Foo foo = (Foo) class1.newInstance();//需要强转
        foo.print();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
}
//
class Foo{
    public void print(){
        System.out.println("foo");
    }
}

13.反射中,Class.forName和ClassLoader区别?

class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

14.描述动态代理的几种实现方式,分别说出相应的优缺点?

动态代理有两种实现方式,分别是:jdk动态代理和cglib动态代理。

jdk动态代理的前提是目标类必须实现一个接口,代理对象跟目标类实现一个接口,从而避过虚拟机的校验。

cglib动态代理是继承并重写目标类,所以目标类和方法不能被声明成final。

15.为什么cglib方式可以对接口实现代理?

cglib动态代理是继承并重写目标类,所以目标类和方法不能被声明成final。而接口是可以被继承的。

16.final的用途?

1.final修饰的对象不能被修改;

2.final修饰的类不能被继承;

3.final修饰的方法不能被重写。

17.写出三种单例模式实现?

单例模式:单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

单例模式有三种:懒汉式单例,饿汉式单例,登记式单例。


1.懒汉式单例

public class Singleton {
    private static Singleton singleton;
    private Singleton() {}  //此类不能被实例化
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

优点:第一次调用才初始化,避免内存浪费。

缺点:必须加锁synchronized才能保证单例,(如果两个线程同时调用getInstance方法,会chuxia)但加锁会影响效率。

2.饿汉式单例

public class Singleton {
    private static final Singleton SINGLETON = new Singleton();
    private Singleton() {}  //此类不能被实例化
    public static Singleton getInstance() {
        return SINGLETON;
    }
}

优点:没有加锁,执行效率会提高。

缺点:类加载时就初始化,浪费内存。


3.登记式模式(holder)

public class Singleton {
    private Singleton() {} //构造方法是私有的,从而避免外界利用构造方法直接创建任意多实例。
    public static Singleton getInstance() {
        return Holder.SINGLETON;
    }
    private static class Holder {
       private static final Singleton SINGLETON = new Singleton();
    }
}

内部类只有在外部类被调用才加载,产生SINGLETON实例;又不用加锁。此模式有上述两个模式的优点,屏蔽了它们的缺点,是最好的单例模式。 

19.如何在父类中为子类自动完成所有的hashcode和equals实现?这么做有何优劣?

覆盖equals时需要遵守的通用约定:

覆盖equals方法看起来似乎很简单,但是如果覆盖不当会导致错误,并且后果相当严重。《Effective Java》一书中提到“最容易避免这类问题的办法就是不覆盖equals方法”,这句话貌似很搞笑,其实想想也不无道理,其实在这种情况下,类的每个实例都只与它自身相等。如果满足了以下任何一个条件,这就正是所期望的结果:

类的每个实例本质上都是唯一的。对于代表活动实体而不是值的类来说却是如此,例如Thread。Object提供的equals实现对于这些类来说正是正确的行为。

不关心类是否提供了“逻辑相等”的测试功能。假如Random覆盖了equals,以检查两个Random实例是否产生相同的随机数序列,但是设计者并不认为客户需要或者期望这样的功能。在这样的情况下,从Object继承得到的equals实现已经足够了。

超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。大多数的Set实现都从AbstractSet继承equals实现,List实现从AbstractList继承equals实现,Map实现从AbstractMap继承equals实现。

类是私有的或者是包级私有的,可以确定它的equals方法永远不会被调用。在这种情况下,无疑是应该覆盖equals方法的,以防止它被意外调用:

@Override

public boolean equals(Object o){

throw new AssertionError(); //Method is never called

}

在覆盖equals方法的时候,你必须要遵守它的通用约定。下面是约定的内容,来自Object的规范[JavaSE6]:

自反性。对于任何非null的引用值x,x.equals(x)必须返回true。

对称性。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。

传递性。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。

一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用该x.equals(y)就会一直地返回true,或者一致地返回false。

对于任何非null的引用值x,x.equals(null)必须返回false。

结合以上要求,得出了以下实现高质量equals方法的诀窍:

1.使用==符号检查“参数是否为这个对象的引用”。如果是,则返回true。这只不过是一种性能优化,如果比较操作有可能很昂贵,就值得这么做。

2.使用instanceof操作符检查“参数是否为正确的类型”。如果不是,则返回false。一般来说,所谓“正确的类型”是指equals方法所在的那个类。

3.把参数转换成正确的类型。因为转换之前进行过instanceof测试,所以确保会成功。

4.对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。如果这些测试全部成功,则返回true;否则返回false。

5.当编写完成了equals方法之后,检查“对称性”、“传递性”、“一致性”。

注意:

覆盖equals时总要覆盖hashCode 《Effective Java》作者说的

不要企图让equals方法过于只能。

不要将equals声明中的Object对象替换为其他的类型(因为这样我们并没有覆盖Object中的equals方法哦)

覆盖equals时总要覆盖hashCode

一个很常见的错误根源在于没有覆盖hashCode方法。在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。

在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。

如果两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。

如果两个对象根据equals()方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生相同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。

20.请结合OO设计理念,谈谈访问修饰符public、private、protected、default在应用设计中的作用?

OO设计理念:封装、继承、多态

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。所以我们可以通过public、private、protected、default 来进行访问控制:

21.深拷贝和浅拷贝区别?

浅拷贝只拷贝指针,深拷贝就是拷贝他的值,重新生成的对像。

22.error和exception的区别?CheckedException,RuntimeException的区别?

Error(错误)表示系统级的错误和程序不必处理的异常,是java运行环境中的内部错误或者硬件问题。比如:内存资源不足等。对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由Java虚拟机抛出的。
    Exception(违例)表示需要捕捉或者需要程序进行处理的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理的。
Exception又分为运行时异常,受检查异常。
       RuntimeException(运行时异常),表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止程序,因此,编译器不检查这些异常。
       CheckedException(受检查异常),是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且做出处理), 所以称之为受检查异常。
 

23.请列出5个运行时异常?

NullPointerException(当程序运行时,对象未初始化或为空时,NullPointerException就出现了)

IndexOutOfBoundsException(通常是指数组下表越界)

ClassCastException(JVM在检测到两个类型间转换不兼容时引发的运行时异常)

ArrayStoreException(你试图将错误类型的对象存储到一个对象数组时抛出的异常)

BufferOverflowException(写入的长度超出了允许的长度)

24.在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么?

不可以,双亲委派模式会保证父类加载器先加载类,就是BootStrap(启动类)加载器加载jdk里面的java.lang.String类,而自定义的java.lang.String类永远不会被加载到。(但是如果自己写的String类没有package,是可以被加载的)

25.说一说你对java.lang.Object对象中hashCode和equals方法的理解?在什么场景下需要重新实现这两个方法?

父类的equals不一定满足子类的equals需求。比如所有的对象都继承Object,默认使用的是Object的equals方法,在比较两个对象的时候,是看他们是否指向同一个地址。

但是我们的需求是对象的某个属性相同,就相等了,而默认的equals方法满足不了当前的需求,所以我们要重写equals方法。

如果重写了equals 方法就必须重写hashcode方法,否则就会降低map等集合的索引速度。

26.在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题?

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

27.有没可能2个不相等的对象有同hashcode?

有可能,最简单的方法,百分百实现的方式就是重写hascode();

HashSet和HashMap一直都是JDK中最常用的两个类,HashSet要求不能存储相同的对象,HashMap要求不能存储相同的键。 
那么Java运行时环境是如何判断HashSet中相同对象、HashMap中相同键的呢?当存储了“相同的东西”之后Java运行时环境又将如何来维护呢? 

在研究这个问题之前,首先说明一下JDK对equals(Object obj)和hashcode()这两个方法的定义和规范: 
在Java中任何一个对象都具备equals(Object obj)和hashcode()这两个方法,因为他们是在Object类中定义的。 
equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。 
hashcode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。 
接下来有两个个关于这两个方法的重要规范(我只是抽取了最重要的两个,其实不止两个): 
规范1:

若重写equals(Object obj)方法,有必要重写hashcode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值。说得简单点就是:“如果两个对象相同,那么他们的hashcode应该 相等”。不过请注意:这个只是规范,如果你非要写一个类让equals(Object obj)返回true而hashcode()返回两个不相等的值,编译和运行都是不会报错的。不过这样违反了Java规范,程序也就埋下了BUG。 


规范2:

如果equals(Object obj)返回false,即两个对象“不相同”,并不要求对这两个对象调用hashcode()方法得到两个不相同的数。说的简单点就是:“如果两个对象不相同,他们的hashcode可能相同”。 
根据这两个规范,可以得到如下推论: 
1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。 
2、如果两个对象不equals,他们的hashcode有可能相等。 
3、如果两个对象hashcode相等,他们不一定equals。 
4、如果两个对象hashcode不相等,他们一定不equals。 

28.这样的a.hashcode() 有什么用,与a.equals(b)有什么关系?

hashcode()方法提供了对象的hashCode值,是一个native方法,返回的默认值与System.identityHashCode(obj)一致。

通常这个值是对象头部的一部分二进制位组成的数字,具有一定的标识对象的意义存在,但绝不定于地址。

作用是:用一个数字来标识对象。比如在HashMap、HashSet等类似的集合类中,如果用某个对象本身作为Key,即要基于这个对象实现Hash的写入和查找,那么对象本身如何实现这个呢?就是基于hashcode这样一个数字来完成的,只有数字才能完成计算和对比操作。

hashcode是否唯一
hashcode只能说是标识对象,在hash算法中可以将对象相对离散开,这样就可以在查找数据的时候根据这个key快速缩小数据的范围,但hashcode不一定是唯一的,所以hash算法中定位到具体的链表后,需要循环链表,然后通过equals方法来对比Key是否是一样的。

equals与hashcode的关系
equals相等两个对象,则hashcode一定要相等。但是hashcode相等的两个对象不一定equals相等。

29.Java中的HashSet内部是如何工作的?

HashSet 的内部采用 HashMap来实现。由于 Map 需要 key 和 value,所以HashSet中所有 key 的都有一个默认 value。类似于HashMap,HashSet不允许重复的key,只允许有一个null key,意思就是HashSet中只允许存储一个null对象。

  • 实现了Set接口
  • HashSet依赖的数据结构是哈希表
  • 因为实现的是Set接口,所以不允许有重复的值
  • 插入到HashSet中的对象不保证与插入的顺序保持一致。对象的插入是根据它的hashcode
  • HashSet中允许有NULL值
  • HashSet也实现了Searlizable和Cloneable两个接口

HashSet的构造函数:

   HashSet h = new HashSet();      
   默认初始化大小是16,默认装载因子是0.75.
 
   HashSet h = new HashSet(int initialCapacity);  
   默认装载因子是0.75
 
   HashSet h = new HashSet(int initialCapacity, float loadFactor);
 
   HashSet h = new HashSet(Collection C);

什么是初始化大小与装载因子:

初始化尺寸就是当创建哈希表(HashSet内部用哈希表的数据结构)的时候桶(buckets)的数量。如果当前的尺寸已经满了,那么桶的数量会自动增长。

装载因子衡量的是在HashSet自动增长之前允许有多满。当哈希表中实体的数量已经超出装载因子与当前容量的积,那么哈希表就会再次进行哈希(也就是内部数据结构重建),这样哈希表大致有两倍桶的数量。

例如:如果内部容量为16,装载因子为0.75,那么当表中有12个元素的时候,桶的数量就会自动增长。

性能影响:

装载因子和初始化容量是影响HashSet操作的两个主要因素。装载因子为0.75的时候可以提供关于时间和空间复杂度方面更有效的性能。如果我们加大这个装载因子,那么内存的上限就会减小(因为它减少了内部重建的操作),但是将影响哈希表中的add与查询的操作。为了减少再哈希操作,我们应该选择一个合适的初始化大小。如果初始化容量大于实体的最大数量除以装载因子,那么就不会有再哈希的动作发生了。

所有Set接口的类内部都是由Map做支撑的。HashSet用HashMap对它的内部对象进行排序。你一定好奇输入一个值到HashMap,我们需要的是一个键值对,但是我们传给HashSet的是一个值。

那么HashMap是如何排序的?

实际上我们插入到HashSet中的值在map对象中起的是键的作用,因为它的值Java用了一个常量。所以在键值对中所有的键的值都是一样的。

如果我们在Java Doc中看一下HashSet的实现,大致是这样的:
 

private transient HashMap map;
 
// Constructor - 1
// All the constructors are internally creating HashMap Object.
public HashSet()
{
    // Creating internally backing HashMap object
    map = new HashMap<>();
}
 
// Constructor - 2
public HashSet(int initialCapacity)
{
    // Creating internally backing HashMap object
    map = new HashMap<>(initialCapacity);
}
 
// Dummy value to associate with an Object in Map
private static final Object PRESENT = new Object();

如果我们看下HashSet中的add方法:

public boolean add(E e)
{
   return map.put(e, PRESENT) == null;
}

我们可以注意到,HashSet类的add()方法内部调用的是HashMap的put()方法,通过你指定的值作为key,常量“PRESENT”作为值传过去。

remove()也是用类似的方法工作。它内部调用的是Map接口的remove:


public boolean remove(Object o)
{
  return map.remove(o) == PRESENT;
}

30.什么是序列化?怎么序列化?为什么序列化?反序列化会遇到什么问题?如何解决?

序列化是一种用来处理对象流的机制 ,所谓对象流就是将对象的内容进行流化。

序列化是为了解决在对对象流进行读写操作时所引发的问题。

序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

31.java8的新特性?

一、接口的默认方法

Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法。

interface Formula {
    double calculate(int a);
    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};
formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

二、Lambda表达式:Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

三、函数式接口:是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。

四、方法与构造函数引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

五、Lambda 作用域

六、访问局部变量

七、访问对象字段与静态变量

八、访问接口的默认方法

九、Date Time API:加强对日期与时间的处理。

十、Annotation 注解

32.什么是基于注解的切面实现?

首先解释下AOP:在程序运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。基于注解的切面实现目前有两种方式spring xml配置方式spring boot集成方式。
面试时答:在spring.xml里面开启扫描bean component-scan跟开启aop注解aspectj-autoproxy

spring xml配置方式:
在spring.xml文件中声明
<context:component-scan base-package="demo.spring"/>使用@Component自动发布bean,需要配置这个元素
<aop:aspectj-autoproxy /> 使用@AspectJ及其它AOP注解需要配置,否则无法使用注解;@AspectJ注解,将@Component自动发布出来的"interceptor" bean转换为一个aspectj切面,而@Pointcut、@Before、@After、@Around等注解,功能与在xml文件中配置是一样的

  注解只能使用在能活的源码的场景,如果不能获取源码,则只能通过xml配置的形式,将制定的对象配置成拦截器,对指定目标进行拦截;因此,通过xml文件配置,而不是注解,是更加通用的方式。

33.什么是对象/关系映射集成模块?

所谓对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。详细一点,是 面向对象编程中的 对象(Object)和关系数据库的 关系(Relation)  的一个映射(Mapping)。

面试的时候一句话:
对象关系映射是 在关系型数据库和业务实体对象之间做一个映射,这样,在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道了,只要像平时操作对象一样就可以了。

34.什么是 ACID?

事务(Transaction)是由一系列对系统中数据进行访问与更新的操作所组成的一个程序 执行逻辑单元(Unit)。狭义上的事务特指数据库事务。一方面,当多个应用程序并发访问数据库时,事务可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
另一方面,事务为数据库操作序列提供了一个从失败中恢复到正常状态的方法, 同时提供了数据库即使在异常状态下仍能保持数据一致性的方法。
事务具有四个特征,分别是原子性(Atomicity )、一致性(Consistency )、隔离性(Isolation) 和持久性(Durability),简称为事务的ACID特性。

35.BS与CS的联系与区别?

bs是浏览器(browser)和服务器(server) 。
cs是静态客户端程序(client)和服务器(server)。
区别在于,虽然同样是通过一个程序连接到服务器进行网络通讯,但是bs结构的,客户端运行在浏览器里,比如你看百度,就是通过浏览器。还有一些bs结构的应用,比如中国电信,以及一些电子商务平台。
用bs结构的好处是,不必专门开发一个客户端界面,可用asp、php、jsp等比较快速开发web应用的程序开发。
cs结构的,要做一个客户端。网络游戏基本上大多是cs结构,比如你玩传奇,要专门开个传奇程序,玩冒险岛,要专门开个冒险岛...... 
cs结构的优点是可以定做很多外观,可以做很多安全措施,可以补充浏览器没有的功能。缺点是开发速度比较慢,一个功能比较完善的客户端比较难做。

36.Cookie 和 Session的区别?

1、cookie数据存放在客户的浏览器上,session数据放在服务器上。

2、cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。

3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。

4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

5、可以考虑将登陆信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中。

37.fail-fast 与 fail-safe 机制有什么区别?

快速失败(fail-fast)在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
安全失败(fail-safe)采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

38.get 和 post请求的区别?

GET请求在URL中传送的参数是有长度限制的,而POST没有。

GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

GET参数通过URL传递,POST放在Request body中。

GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

GET请求只能进行URL编码,而POST支持多种编码方式。

GET请求会被浏览器主动cache,而POST不会,除非手动设置。

GET产生的URL地址可以被Bookmark,而POST不可以。

GET在浏览器回退时是无害的,而POST会再次提交请求。

GET和POST还有一个重大区别

简单的说:

GET产生一个TCP数据包;POST产生两个TCP数据包。

长的说:

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200(返回数据)。

39.IOC的优点是什么?

先把IOC的概念说出来:依赖注入和控制反转

所谓的依赖注入是甲方开放接口,在它需要的时候,能够将乙方传递进来(注入);所谓的控制反转,甲乙双方不相互依赖,交易活动的进行不依赖于甲乙任何一方,整个活动的进行由第三方负责管理。这就是spring IoC的思想所在。

然后说优缺点

IOC的优点:实现组件之间的解耦,提高程序的灵活性和可维护性。
IOC的缺点:
1、创建对象的步骤变复杂了,不直观,当然这是对不习惯这种方式的人来说的。
2、因为使用反射来创建对象,所以在效率上会有些损耗。但相对于程序的灵活性和可维护性来说,这点损耗是微不足道的。
3、缺少IDE重构的支持,如果修改了类名,还需到XML文件中手动修改,这似乎是所有XML方式的缺憾所在。

40.IO 和 NIO的区别,NIO优点?

IO是面向流的,NIO是面向缓冲区的。
Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方;NIO则能前后移动流中的数据,因为是面向缓冲区的。
IO流是阻塞的,NIO流是不阻塞的。
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。 
非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
选择器
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
NIO的优势:
1.优势在于一个线程管理多个通道;但是数据的处理将会变得复杂;
2.如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,采用这种;
传统IO的优势:
1.适用于一个线程管理一个通道的情况;因为其中的流数据的读取是阻塞的;
2.如果需要管理同时打开不太多的连接,这些连接会发送大量的数据;

41.JRE、JDK、JVM 及 JIT 之间有什么不同?

Java虚拟机(JVM)
使用java编程语言的主要优势就是平台的独立性。
你曾经想知道过java怎么实现平台的独立性吗?对,就是虚拟机,它抽象化了硬件设备,开发者和他们的程序的得以操作系统。
虚拟机的职责就是处理和操作系统的交流。java不同的接口规范对任何平台都有良好的支持,因为JVM很好的实现了每个平台的规范。
JVM可以理解伪代码字节码,在用户和操作系统之间建立了一层枢纽。
 
Java运行时环境(JRE)
Java运行时环境是JVM的一个超集。
JVM对于一个平台或者操作系统是明确的,而JRE确实一个一般的概念,他代表了完整的运行时环境。
我们在jre文件夹中看到的所有的jar文件和可执行文件都会变成运行时的一部分。
事实上,运行时JRE变成了JVM。所以对于一般情况时候使用JRE,对于明确的操作系统来说使用JVM。
当你下载了JRE的时候,也就自动下载了JVM。
 
Java开发工具箱(JDK)
Java开发工具箱指的是编写一个java应用所需要的所有jar文件和可执行文件。
事实上,JRE是JDK的一部分。
如果你下载了JDK,你会看到一个名叫JRE的文件夹在里面。JDK中要被牢记的jar文件就是tools.jar,它包含了用于执行java文档的类还有用于类签名的jar包。
 
即时编译器(JIT)
即时编译器是种特殊的编译器,它通过有效的把字节码变成机器码来提高JVM的效率。
JIT这种功效很特殊,因为他把检测到的相似的字节码编译成单一运行的机器码,从而节省了CPU的使用。
这和其他的字节码编译器不同,因为他是运行时执行编译(从字节码到机器码)而不是在程序运行之前。
正是因为这些,动态编译这个词汇才和JIT有那么紧密的关系。

42.MVC的各个部分都有那些技术来实现?如何实现?

MVC是Model-View-Controller的简写。

Model 代表的是应用的业务逻辑(通过JavaBean,EJB组件实现),
View 是应用的表示面(由JSP页面产生),
Controller 是提供应用的处理过程控制(一般是一个Servlet),
通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。

43.什么是 Web Service(Web服务)?

从表面上看,Web Service就是一个应用程序,它向外界暴露出一个能够通过Web进行调用的API。
这就是说,你能够用编程的方法透明的调用这个应用程序,不需要了解它的任何细节,跟你使用的编程语言也没有关系。
例如可以创建一个提供天气预报的Web Service,那么无论你用哪种编程语言开发的应用都可以通过调用它的API并传入城市信息来获得该城市的天气预报。
之所以称之为Web Service,是因为它基于HTTP协议传输数据,这使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件,就可相互交换数据或集成。

补充:这里必须要提及的一个概念是SOA(Service-Oriented Architecture,面向服务的架构),
SOA是一种思想,它将应用程序的不同功能单元通过中立的契约联系起来,独立于硬件平台、操作系统和编程语言,使得各种形式的功能单元能够更好的集成。
显然,Web Service是SOA的一种较好的解决方案,它更多的是一种标准,而不是一种具体的技术。

44.简单说说你了解的类加载器。是否实现过类加载器?

类加载器就是负责检索并加载其他Java类或者资源(如文件)的对象,它一般继承于java.lang.ClassLoader这个抽象类(除了BootstrapClassLoader)。
实际上,程序中所有的类都是通过类加载器进行加载的,并且它们都持有各自类加载器对象的引用,可以通过java.lang.Class的getClassLoader方法得到。
一个程序中的各个类加载器构成了一棵树,位于根部的被称作BootstrapClassLoader,它作为Java虚拟机的一部分,它使用C++语言实现,在程序刚启动时就被加载进来,负责Java标准库的加载,并且只有它能完成该任务。

标准扩展(Extension)类加载器负责加载Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的类库。

应用程序(Application)类加载器负责加载系统类路径(CLASSPATH)中指定的类库。同时它常被称为系统(System)加载器,因为我们可以通过getSystemClassLoader()方法来获取它。

而由我们程序员自己编写的类加载器被称为自定义类加载器,如果生成自定义类加载器时没有明确地指出父类加载器,会默认把应用程序(Application)类加载器作为自己的父亲。

类加载器的父子关系相当重要,当你指定由一个类加载器加载某一个类时,它会无论如何先把它交给自己的父类加载器来执行,除非父类加载器检索不到这个类,才会开始尝试自己检索和加载。

45.解释一下什么叫AOP(面向切面编程)?

这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。
使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。
从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了将不同的关注点分离出来的效果。

46.请简述 Servlet 的生命周期及其相关的方法?

Servlet 生命周期:Servlet 加载——>实例化——>服务——>销毁。
init():在Servlet的生命周期中,仅执行一次init()方法。
它是在服务器装入Servlet时执行的,负责初始化Servlet对象。
可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。
无论有多少客户机访问Servlet,都不会重复执行init()。
service():它是Servlet的核心,负责响应客户的请求。
每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。
在HttpServlet中已存在Service()方法。
默认的服务功能是调用与HTTP请求的方法相应的do功能。
destroy(): 仅执行一次,在服务器端停止且卸载Servlet时执行该方法。
当Servlet对象退出生命周期时,负责释放占用的资源。
一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。

47.说一下强引用、软引用、弱引用、虚引用以及他们之间和gc的关系?

强引用:new出的对象之类的引用,只要强引用还在,永远不会回收。

软引用:引用但非必须的对象,内存溢出异常之前,回收。

弱引用:非必须的对象,对象能生存到下一次垃圾收集发生之前。 

虚引用:对生存时间无影响,在垃圾回收时得到通知。

48.多线程同步的五种方法?

1、同步方法

即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

2、同步代码块

即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

3、使用特殊域变量(volatile)实现线程同步

(1)volatile关键字为域变量的访问提供了一种免锁机制;

(2)使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新;

(3)因此每次使用该域就要重新计算,而不是使用寄存器中的值;

(4)volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。

volatile会组织编译器对代码优化,因此能不使用它就不使用它吧。它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是从缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。

4、使用重入锁实现线程同步

在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() :创建一个ReentrantLock实例
lock() :获得锁
unlock() :释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用。

如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁。

5、使用局部变量实现线程同步

package com.demo.test;

/** 
 * @author lixiaoxi
 * 
 */ 
public class Bank {
    
    private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){  

        @Override  
        protected Integer initialValue() {  
            // TODO Auto-generated method stub  
            return 0;  
        }  

    };  

    // 存钱  
    public void addMoney(int money) {  
        count.set(count.get()+money);  
        System.out.println(System.currentTimeMillis() + "存进:" + money);  

    }  

    // 取钱  
    public void subMoney(int money) {  
        if (count.get() - money < 0) {  
            System.out.println("余额不足");  
            return;  
        }  
        count.set(count.get()- money);  
        System.out.println(+System.currentTimeMillis() + "取出:" + money);  
    }  

    // 查询  
    public void lookMoney() {  
        System.out.println("账户余额:" + count.get());  
    }      
}

运行效果:

余额不足
账户余额:0

1511166594460存进:100
账户余额:200

余额不足
账户余额:0

1511166595460存进:100
账户余额:300

余额不足
账户余额:0

1511166596460存进:100
账户余额:400

看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,只是名字相同而已。所以就会发生上面的效果。

ThreadLocal与同步机制

a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题;

b.前者采用以”空间换时间”的方法,后者采用以”时间换空间”的方式。

49.作用域public,private,protected,以及不写时的区别?

1.private修饰的成员变量和函数只能在类本身和内部类中被访问。

2.protected修饰的成员变量和函数能被类本身、子类及同一个包中的类访问。

3.public修饰的成员变量和函数可以被类、子类、同一个包中的类以及任意其他类访问。

4.默认情况(不写)下,属于一种包访问,即能被类本身以及同一个包中的类访问。

50.解析float型float f=3.4是否正确?编译能否通过?

不正确。精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4 或float f = 3.4f。

在java里面,没小数点的默认是int,有小数点的默认是 double。

int 转成 long 系统自动作没有问题,因为后者精度更高。
double 转成 float 就不能自动做了,所以后面的加上个f。

编译不能通过。

51.java语言中有没有goto关键字?

其实goto这个词是C语言中的,goto语句通常与条件语句配合使用,可用来实现条件转移, 构成循环,跳出循环体等功能。而在结构化程序语言中一般不主张使用goto语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。但是在java语言中,goto这个词只是作为了保留字,还没有使用。那是因为java语言讲究简单,方便。

猜你喜欢

转载自blog.csdn.net/Kevin_Gu6/article/details/87908176