java 面试题总结(一)

从网上找了些面试题,自己手工总结了理解了一下,如有理解错误,还请指正。

java基础

1.String 为什么是final的?
    String作为引用类型,类设计成final的,不让任何子类有机会继承它,目的时保证String可以作为一个不可变的对象存储起来。
    String使用非常频繁,因此设计成一种类似于int那样的值传递,需要有直接的值,而且值不能被改变,如果String的值能够被轻易改变将是非常不安全的。
    String作为HashMap的key以及其他场合的时候,需要保持不可变性,否则会发生错误,因此需要声明成final的。
    String底层使用char数组实现,char数组虽然是final的,但是数组的内容可以被改变(引用不变),因此String设计成final的,保证不会被其他程序修改String的行为,导致错误。
    final方法编译器会采取内联方式实现,JVM执行效率得到提高。
 
2.HashMap的源码,底层结构和实现原理
    底层结构是一个Entry数组+链表。
    HashMap存放元素时,获取key的hashCode然后调用hash方法,获得Entry数组的下标。然后指定的这个value就会存储到这个数组下标位置。如果这个数组下标的位置已经有元素存在了,就将value插入数组下标的头元素前,形成一个链表。Entry作为一个HashMap一个静态内部类,内部持有了key,hash,value和下一个Entry的引用以及。每个Entry都是一个链表。
get元素时,根据传入的key,获取hash值,找到数组的下标。然后在这个链表中不停地迭代下一个Entry,根据key的字面值,获取对应的value。
源码中还包括了key值为null的put和get情况,数组初始化,超过负载因子时的再哈希算法等等内容。
    实际使用中要尽量避免哈希表多次扩容,因为扩容再哈希是十分消耗性能的做法;要注意key值一定要是不可变的元素而且符合equals约束和hashCode约束;尽量不要用null值作为key等。贴上两篇经典文章:
 
3.说说你知道的java集合及其实现类,如list,set,map等
    java集合类主要是实现了Collection,Map接口。其中Collection实现了Iterable,这个顶级的接口提供了元素的迭代器,凡是需要遍历类的值,都需要这个迭代器(list虽然可以通过下标遍历,但是不能在遍历时做插入和删除操作)。
    此外还有一个ListIteraotr,可以用来在迭代时插入删除并支持双向遍历。
    List和Set实现了Collection接口,其中List是有序的,允许元素重复的类似于数组的集合。Set是无序的不允许重复的底层是HashMap实现的用于模拟数学意义上集合的集合
    List和Set依然是接口,他们向下生成了AbstractList和AbstractSet两个抽象类,抽象类定义了一些基本的约束条件,然后再向下延伸出具体日常使用中的工具类。
    List下面有ArrayList,LinkedList和Vector几种集合类型,其中ArrayList日常使用最多,LinkedList是使用链表实现的List,提供快速的插入和删除,但是遍历较慢。Vector是线程安全的List,但是设计上有一些问题,用得不多,就算是需要同步功能,也可以通过Collections来获取一个同步的ArrayList。
    Set下面有HashSet,EnumSet,TreeSet,LinkedHashSet等,其中HashSet底层是使用没有value的Map来实现的。TreeSet使用了一个排序的平衡二叉树,因此没有HashMap的性能困扰,需要时可以从HashSet直接转换为TreeSet。LinkedHashSet提供了为Set排序和保持顺序的功能。
    Map接口下面是AbstractMap,抽象类,接下来是Map的其他具体实现。
    HashMap,EnumMap,HashTable,LinkedHashMap,WeakHashMap等
    HashMap是标准实现,用得最多,上面已经解释过了。EnumMap是使用枚举类型作为key的map,HashTable是同步的Map,不允许null值,因为同步所以没有HashMap的快速失败机制。HashTable不方便转换为有序Map。LinkedHashMap只是对HashMap做了一层简单的包装,保证放进去和拿出来的顺序是一样的。TreeMap使用树实现,支持排序等操作。WeakHashMap内部使用弱引用来管理数据,可以被垃圾回收器回收,因此是作为缓存实现的极好的方式。Properties是继承自Map的实现键值对数据管理的一个简单的工具类。
     Collection的另一个分支是队列,提供了ArrayDeque,PriorityQueue等实现,这些单线程的使用不多,而类似于ArrayBlockingQueue,ConcurrentLinkedDeque,LinkedBlockingDeque,SynchronousQueue等可用于并发环境下的队列在日常工作中使用最多!
     最后是关于线程安全的问题,虽然Vector和HashTable提供了这个功能,但是因为种种原因实际工作中使用的并不多。就算是要对List,Set,Map做同步操作,也可以通过:Collections.synchronizedCollection / synchronizedList / synchronizedMap / synchronizedSet / synchronizedSortedMap / synchronizedSortedSet来得到一个安全的集合。另外concurrent包下的一些包装集合类,也可以实现同步的功能,内部机制暂时还不清楚,但是无论在什么时候,多线程的操作都是需要非常注意的,并不是用一个class就能够一劳永逸不用管的。因为这些类也只能保证在读取的时候不修改,删除的时候不迭代,而无法阻止两个线程先后修改数据(业务上)。
     参考资料
    【java集合类详解 】

4. 描述一下ArrayList和LinkedList各自实现和区别  

     ArrayList是线性表 ,随机取数快,数据删除插入较慢,占用内存小

     LinkedList是链表  插入删除快,查找遍历较慢占用内存稍大

5. Java中的队列都有哪些,有什么区别。

    队列是实现生产者消费者的重要工具,java中的队列一般氛围阻塞的和非阻塞的,或者是根据需要有其他多重的实现方式:

    ArrayBlockingQueue——带边界的阻塞式队列

    ConcurrentLinkedDeque / ConcurrentLinkedQueue——无边界的链表队列(CAS)

    DelayQueue——元素带有延迟的队列

    LinkedBlockingDeque / LinkedBlockingQueue——链表队列(带锁),可设定是否带边界

    LinkedTransferQueue——可将元素`transfer`进行w/o存储

    PriorityBlockingQueue——并发PriorityQueue

    SynchronousQueue——使用Queue接口进行Exchanger

    Queue在设计之处就考虑了多线程的安全性,代表性的Queue的两种实现,表示实现线程安全的两套方案:•BlockingQueue,•ConcurrentLinkedQueue

    前者是阻塞的同步的,后者是并发的。

    这里提一下多线程安全问题的含义,之前有些搞混了。线程安全的类 ,指的是类内共享的全局变量的访问必须保证是不受多线程形式影响的。如果由于多线程的访问(比如修改、遍历、查看)而使这些变量结构被破坏或者针对这些变量操作的原子性被破坏,则这个类就不是线程安全的。实现线程安全又有两种方式,一种是同步的,将多线程的请求排个队,A线程进来了关上厕所门,其他线程在外面等待。A线程执行结束,B线程进入同样关上门。这样的操作保证了多线程下共享变量不会被修改,但是效率比较差,如果线程太多会导致严重的锁争用。还有一种方式是并发的,多个线程可以同时进入一段代码,如何保证线程安全呢?就是通过各个线程之间对共享变量的修改和操作都是可见的,你改了什么我必须第一时间知道,然后针对性地做一些操作。这样极大地提高了处理速度,只是算法的实现会非常的复杂。另外,类自身实现线程安全,只是代表类自身的api保持原子性,但是外部使用多个api的情况下,还是要手动进行加锁。concurrent包下的集合类实现,都是采用的并发方式,而Collections工具类包装的集合都是使用同步锁方式。其中CurrentHashMap应该是使用了分段锁和volatile。java并发:同步容器&并发容器 - 烟雨暗千家 - 博客园 

    LinkedList实现了Queue接口,也可以作为队列使用,但是不可以访问非Queue接口的方法,以免发生错乱。

    BlocingQueue:ArrayBlockingQueue,LinkedBlockingQueue(入队和出队两把锁),DelayQueue,PriorityBlockingQueue(公平锁),SynchronousQueue。

    ReentrantLock     只锁部分代码块

    ConcurrentLinkedQueue:使用volatile关键字使共享变量可见,具体细节颇为复杂。

   【Java多线程总结之聊一聊Queue - I'm Sure - ITeye技术网站 】【java中线程队列BlockingQueue的用法-shwenwen-ITPUB博客 】

    这个坑很深,目前只能研究到这里了……

6. 反射中,Class.forName和classloader的区别。

    Class.forName()方法底层调用Class.forName(className,true,classloader);表示在加载类的时候,执行初始化步骤;

    ClassLoader.loadClass()方法底层调用ClassLoader.loadClass(className,false);表示不执行初始化步骤;

    初始化步骤会初始化静态变量和静态代码块,如果此处没有初始化,那就需要在newInstance时,初始化了。根据不同的需要,调用相应的方法。

    可能会引申到JVM装载类的过程:编译解释java文件为二进制class,装载class(通过类的完全限定名和类加载器实例),链接(对二进制字节码校验,初始化静态变量,找到父类接口和类,确定调用的方法和类都是存在的且具备权限),初始化(静态代码块其他)。 

7. Java7、Java8的新特性

    java 7 ,java8 新特征 

    java7的更新多数是语法糖:

    try包含资源代码块,自动释放;多个catch合并;更强的类型推测,new的时候不再需要指定类型,直接<>即可;二进制字面量支持;通过[],{}初始化集合。

     java8

    Lambda表达式与函数接口,接口支持默认方法和静态方法,可使用类似于C++的方法引用 class::method,重复注解,更强的类型推测,可将参数名称保存在字节码中,Optional 支持对null的处理,Stream 类似于mapReduce的集合处理方式,api可以直接连缀调用,Date/Time API 的更新,在兼容旧api分基础上提供了许多新的api,传闻java 9 将进一步支持货币api,base64直接支持,支持运行javascript以及js和java的相互调用(java8就有了?),jvm的permGen移除,类依赖分析器jdeps。

8. Java数组和链表两种结构的操作效率,在哪些情况下(从开头开始,从结尾开始,从中间开始),哪些操作(插入,查找,删除)的效率高。

    数组 不能动态扩容,占内存小,添加删除操作稍慢,查询快。

    链表 可以动态扩容,占内存大,添加删除操作快,查询慢。

9. Java内存泄露的问题调查定位:jmap,jstack的使用等等。

    没用过不好说

10. string、stringbuilder、stringbuffer区别

    String 是final的字符串,不可变。StringBuffer是线程安全的可变String,StringBuilder是简化的可变字符串,不是线程安全的。

11. hashtable和hashmap的区别

    上面有所提及:

    1. HashTable不支持用null作为key,而HashMap支持

    2. HashTable是线程安全的map集合,HashMap不是线程安全的。

    3. HashTable因为支持线程安全,所以没有HashMap的快速失败机制,但是HashTable不能保证多个api复合调用时保证安全。

    4. Map想要支持多线程安全可以通过Collections工具类获取同步的Map,或者通过concurrent包中的concurrentHashMap返回并发的Map,而且可以与有序的TreeMap转化,很多时候,即便是多线程环境下也并不一定需要HashTable。

13 .异常的结构,运行时异常和非运行时异常,各举个例子。

    单独再说

14. String 类的常用方法

     length() , subString , indexOf , lastIndexOf , charAt , equals ,toUpperCase , equalsIgnoreCase , trim , toString , toLowerCase , replace , match(用于正则) , startsWith , compareTo. format(允许类似于C语言的输出字符串格式控制)

15. Java 的引用类型有哪几种

      String , Object ,基本类型的包装类型:Integer,Long , Boolean , Short , Float,Double,Byte,Character,Void。Enum枚举类型

16. 抽象类和接口的区别

      从语法上来看:  

参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有publicprotecteddefault这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

       从本质上来看:

       抽象类是对子类的一个抽象,抽象类提供了子类的某些方法约束,作为公共的大家都必须遵守的约束,有利于直接复用代码。不能确定的子类行为可以采用抽象方法定义,交给具体子类实现。抽象类更多的是在具体代码编写时的一种设计。

       接口是定义一组约束规则,作为彻底的抽象层存在。它只要求实现者实现所有的方法,而不关心具体的实现方式,它只为子类指定功能,但是不存在对子类实现方式的要求,也就是说没有公共的已经实现的方法。更多的是用于软件架构设计中,对不同模块的抽象。

       java8中支持了接口的默认方法,具体后面再研究。

18. java的基础类型和字节大小

       java中基本数据类型(不是引用类型)有8种

 

byte Byte 1 -128 127
char Character 2 unicode-0 unicode 2^16-1
short Short 2 -2^15  2^15-1
int Integer 4 -2^31 2^31-1
float Float 4    
double Double 8    
long Long 8    
boolean Boolean - true false
       对char不太理解,因为char是2个字节,但是unicode中utf-8的中文是3个字节,产生了混乱。后来有人建议不要用char类型。因为char类型使用的老旧的utf16编码,65535个字符根本不够囊括所有语言的字符。这里记录以下防止被坑吧……

19. Hashtable,HashMap,ConcurrentHashMap底层实现原理与线程安全问题。

      HashMap没有做多线程的控制,底层实现原理之前有提到过。HashTable是使用了重用锁,在操作时锁住了一段代码块,是同步的。ConcurrentHashMap,内部使用了volatile关键字,使得共享变量在多线程之间可见和同步,编程上考虑的比较多算法比较复杂。具体的上面的文章中有提到过。

20. Hash冲突怎么办?哪些解决散列冲突的方法?

      hash冲突应该是hash算法的问题,也有可能是hashCode算法的问题。这个//todo

21. HashMap冲突很厉害,最差性能,你会怎么解决?从O(n)提升到log(n)。

      首先哈希表是通过哈希函数计算元素的哈希值,然后将次哈希值作为要存放的数组地址的下标,以便达到用数组存放元素,同时能够实现较快的查找删除操作。

      这里哈希函数就非常重要,如果哈希函数计算的不同元素的哈希值容易重复,就会导致哈希冲突(不同元素映射到同一下标),哈希表不得不额外地解决这些冲突。

      好的哈希函数会尽量避免冲突,能均匀的分布。以及计算简单。

      解决哈希冲突的办法主要有下面几种:

      开放地址法:如果发现哈希值下标已经被占用,就向下查找下一个空元素存入,问题是非常容易产生集聚现象,使得冲突加剧。其中也有一些变种办法,例如不直接向下查找下一个元素,而是生成不同的查找步数,这个可以根据随机数或者二次哈希产生。

      再哈希法:冲突后再次执行另一个哈希算法,得到新的地址。

      链地址法:如果哈希冲突了,就直接将冲突的元素接在原占有的元素后面,形成一个链表。

      而HashMap就是采用这种办法做的。HashMap的性能,也是与其hash函数有关,当存放的key是用户自定义的对象而不是String的时候,需要重写这个对象的hashCode方法,这个hashCode方法的效率就会影响到HashMap的哈希函数的效率,如果HashCode不能做到计算简单随机分布,hash函数就会容易冲突。

      HashMap冲突很严重的话,还可以将HashMap转化为平衡树实现的TreeMap,后者的性能是logn.

23. rehash

      看起来应该是HashMap的再哈希算法,主要用于当HashMap的容量超过负载因子(导致元素容易集聚成链表),HashMap就会扩容,所有的元素的哈希值就要重新计算,rehash大概就是用于此处的。rehash比较消耗性能,因此应该避免HashMap频繁扩容。

24. hashCode() 与 equals() 生成算法、方法怎么重写。

      hashCode

      hashCode用于生成标记一个对象的最好是唯一的散列码,它将被用在HashMap等散列表中,哈希函数会根据该散列码计算数组下标从而存放起来。

      因此如果散列码是一样的话,在HashMap中认为是同一个对象,如果不一样,它就会认为这个一个新的对象。

      大家说在重写equals时重写hashCode方法,是为了避免对象在用在哈希表的键时造成的问题。

      设想重写了equals方法,两个不同的对象被认为是相等的,我们把相等的对象存到map的key中,然后再根据这个相等的对象取value的时候,就会出错。因为HashMap根据hashCode计算出来的两个对象的值不同,HashMap认为是不一样的。所以就找不到当初存放的对象。

      所以hashCode需要重写,并且保证两个值相同的对象,它的hashCode一定是相同的。(如果固定地返回某个常量值,会导致系统会将所有的这种类型的对象都视为相同。)

      重写hashCode方法是一项技术活,不过一些简单的重写方案还是有的。下面的代码从《effective java》中摘录

 

/**此处的变量都是int类型*/
@Override
public int hashCode(){
int result = 17;
result = 31*reuslt + areaCode;
result = 31*result + prefix;
result = 31*result + lineNumber;
return result;
}

  

      equals

/**
* equals方法需要满足自反性,对称性,传递性,一致性和非空性
* equasl方法写完了要进行相关的测试
* 过程是:
* 检查被比较的对象是不是自己,如果是返回true
* 检查被比较的对象是不是null,如果是返回false
* 检查被比较的对象是不是和自己是同一个类型的,如果不是返回false
* 将被比较对象强制转换成自己的类型,然后逐一比较值。
* 对于float,double,数组等格式的域,需要使用特殊的比较方法,而不能用==。
*
*/
@Override
public boolean equals(Object obj){
if(this == obj)return true;
if(obj == null)return false;
if(obj.getClass() != this.getClass())return false;//!(obj instanceof Te1)
Te1 t = (Te1)obj;
if(!t.name.equals(name))return false;
if(!(t.id==id))return false;
if(Double.compare(salary,t.salary)!=0)return false;
if(!Arrays.equals(strs,t.strs))return false;
return true;
}

  

----------------------------------------------------------------------
以上
原文地址:https://www.cnblogs.com/opensesame/p/6410948.html