java面试八股文

1.面向过程与面向对象
面向过程(PO):把事情拆分成一个个的方法和数据,然后按照一定的顺序,执行完毕这些方法,等方法执行完了,事情就搞定了。因为每个方法都可以看作一个过程所以叫面向对象。
面向对象(OO):面向对象会把事物抽象成对象的概念,先抽象出对象,然后对对象赋一些属性和方法,然后让每个对象去执行自己的方法。问题得到解决。

面向过程与面向对象的优缺点对比:
面向过程:
优点:性能比面向对象高,因为不需要实例化对象。
缺点:可维护性差
面向对象:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态的特性,可以设计出低耦合的系统,使系统更加灵活,更加易于维护。
缺点:性能比面向过程低。

面向对象编程的特性:封装、继承、多态
封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承:继承可以使用现有的类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态:一个类实例的相同方法可以有不同的表现形式。

2.深入理解HashMap与Hash算法

(1)HashMap的数据结构

JDK1.7及之前HashMap底层是数组和链表

java 八股文面试 java面试八股文都是什么_数组

JDK1.8及之后HashMap底层是数组和链表以及红黑树

java 八股文面试 java面试八股文都是什么_数组_02

(2)JDK1.8什么时候链表会转化成红黑树?

HashMap在元素比较少的时候。也只会有数组+链表结构。

当链表的长度大于8,HashMap可能会树化(链表转变成红黑树),不过就是需要再满足一个条件,就是数组长度必须大于等于64(默认是16经过两次扩容<每次扩容*2>),链表才会转变成红黑树。否则只会触发数组的扩容。

java 八股文面试 java面试八股文都是什么_数据结构_03


源码(JDK1.9)根据key值计算hash值的方法

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

源码(JDK1.9)根据hash值计算出对应数组的下标的代码

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {

第一,如果n(n为数组的长度)是2的次方的条件下:(n-1)&hash效果和hash%n是一样的,且效率更高。
第二,为什么HashMap要把hashcode做一个运算改进?
结论:让计算出来的数组下标更加分散

(3)HashMap的扩容机制是怎样的?
①什么条件下扩容?
HashMap中最主要的数据结构就是数组,一般情况下HashMap的元素是放数组上的。如果数组已经有元素,那么就发生了Hash冲突,那么就会使用链表或者红黑树的结构进行存储。
当然HashMap中数组的默认大小是16,如果不断的插入元素,那么Hash冲突就会很频繁,所以这个时候就需要扩容。
HashMap中有一个loadFactor参数,这个是当插入数组的元素数量达到了数组的比例之后,HashMap就要开始吧数组进行扩容,扩成原来的2倍。loadFactor默认值0.75,例如:数组长度是16.如果数组上已经有12个元素时,这个时候就需要进行扩容。

扩容以后会发生什么?
因为扩容后的元素也需要重新进行散列,数组长度变长了,元素在数组上的位置也会因为数组长度的改变而发生变化。

3.java中sleep方法和wait方法的区别

(1)sleep是线程中的方法,但是wait是Object中的方法。

java 八股文面试 java面试八股文都是什么_java_04


java 八股文面试 java面试八股文都是什么_散列表_05


(2)sleep方法不会释放锁,但是wait会释放锁。

(3)sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。

(4)sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要别人中断)。

4.final、finalize、finally的不同之处?
final是一个修饰符,可以修饰变量、方法和类。如果final修饰变量,意味着该变量的值在初始化后不能被改变。
finalize方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用finalize没有保证。因为它的执行优先级比较低,而且该方法只能被执行一次。
finally是一个关键字,与try和catch一起用于异常的处理。finally块一定会被执行,无论在try块中是否发生异常。

5.hashcode()有什么用?与equals()有什么关系?
简单来说HashCode就像一个签名,当两个对象的Hashcode一样的时候,两个对象就可能一样,但是如果hashcode不一样,那么肯定不是同一个对象。相当于先确定一个大的范围,再用equals去比较。
hashcode可以减少equals比较的次数,提高运算效率。(equals方法效率比较低)如果对象1和对象2相等,说明他们的散列码相等,反过来就不一样了。hashcode的存在主要用于查找的快捷性,如Hashtable,HashMap等。

6、= = 与equals()有什么区别?
= = 操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相等,要比较两个基本类型的数据或两个引用变量是否相等,只能用 = = 操作符。
如果a和b都是对象,则a = = b是比较两个对象的引用。
equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立。

7、JDK1.7到JDK1.8HashMaP发生了什么变化(底层)?
(1)1.7中底层是数组+链表,1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap的插入和查询整体效率。
(2)1.7中链表插入使用的是头插法,1.8中使用的尾插法,因为1.8中插入key和value时需要判断链表元素的个数,所以需要遍历链表统计链表元素个数,所以正好就直接使用尾插法。
(3)1.7中哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为复杂的哈希算法的目的就是提高散列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省CPU资源。

8.说一下HashMap的Put方法
先说一下HashMap的Put方法的大体流程:
1.根据Key通过哈希算法与运算得出数组下标
2.如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,Jdk1.8中是Node对象)并放入该位置
3.如果数组下标位置元素不为空,则要分情况讨论
 a.如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry 对象,并使⽤头插法添加到当前位置的链表中。
 b. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node。
    ①如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个 过程中会判断红⿊树中是否存在当前key,如果存在则更新value
    ②如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插 法插⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会 判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链 表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成 红⿊树
    将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要 就扩容,如果不需要就结束PUT⽅法。

9.HashMap的扩容机制原理
1.7版本

  1. 先⽣成新数组
  2. 遍历⽼数组中的每个位置上的链表上的每个元素
  3. 取每个元素的key,并基于新数组⻓度,计算出每个元素在新数组中的下标
  4. 将元素添加到新数组中去
  5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

1.8版本

  1. 先⽣成新数组
  2. 遍历⽼数组中的每个位置上的链表或红⿊树
  3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中。
  4. 如果是红⿊树,则先遍历红⿊树,先计算出红⿊树中每个元素对应在新数组中的下标位置
    a. 统计每个下标位置的元素个数
    b. 如果该位置下的元素个数超过了8,则⽣成⼀个新的红⿊树,并将根节点的添加到新数组的对应位置
    c. 如果该位置下的元素个数没有超过8,那么则⽣成⼀个链表,并将链表的头节点添加到新数组的 对应位置
  5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

10.JDK、JRE、JVM之间的区别
JDK:Java Develpment Kit java 开发⼯具
JRE:Java Runtime Environment java运⾏时环境
JVM:java Virtual Machine java 虚拟机

11.ArrayList和LinkedList区别
(1)底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于连标配实现的
(2)由于底层数据结构不同,他们所适用的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加,查询、添加、删除的时间复杂度不同。
(3)ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以LinkedList还可以当做队列来使用。

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

13.HashMap和HashTable有什么区别?其底层实现是什么?
区别 : 1. HashMap⽅法没有synchronized修饰,线程⾮安全,HashTable线程安全;
      2. HashMap允许key和value为null, key为null,存在下标0的位置,⽽HashTable不允许;
      3.HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75,HashMap扩容时是当前容量翻倍,Hashtable扩容时是容量翻倍+1

14.int 和 Integer哪个会占用更多的内存?
Integer对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据,但是int是一个基本数据类型的数据,所以占用的空间更少。但是在进行数据库封装的时候一般都会用Integer.

15.Java中++操作是线程安全的吗?
不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交叉从而导致值的不正确。

16.Java中如何实现序列化,有什么意义?
网络传输的数据都必须是二进制数据,但是java中都是对象,是没有办法在网络中进行传输的,所以就需要对Java对象进行序列化,而且要求转换算法是可逆的。
只要让类实现Serializable接口就行,序列化具体的实现是由ObjectOutputStream和ObjectInoutStream来实现的。
javaz序列化的缺点:序列化码流太大;序列化效率低
一般建议使用第三方的JSON、Hessian、ProtoBuf等效率高的方式。

17.重载和重写的区别?
① 重载: 发⽣在同⼀个类中,⽅法名必须相同,参数类型不同、个数不同、顺序不同,⽅法返回值和 访问修饰符可以不同,发⽣在编译时。
② 重写: 发⽣在⽗⼦类中,⽅法名、参数列表必须相同,返回值范围⼩于等于⽗类,抛出的异常范围 ⼩于等于⽗类,访问修饰符范围⼤于等于⽗类;如果⽗类⽅法访问修饰符为private则⼦类就不能重 写该⽅法。