欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java面试资料

程序员文章站 2024-03-21 21:34:52
...

Java面试资料全集

本文章根据开源项目JavaGuide改编+Java开发方向大厂真实面试题!(本文章会持续更新,直到两年后博主拿到大厂offer!)


文章目录


前言

提示:这里可以添加本文要记录的大概内容。


第1章 Java基础

1.1 关于 JVM JDK 和 JRE 详细通俗的解答

  • JVM: 是一种运行Java字节码的虚拟机,它针对不同的操作系统(Windows、Linux、macOS)有特定的实现,目的是让相同的字节码在不同的操作系统下面,得到相同的结果。
  • 字节码:它是一种.class文件,是一种只有JVM可以理解的代码,不面向其他任何处理器,只面向JVM虚拟机。
  • 字节码的好处:字节码使得Java程序拥有良好的移植性,解决了传统解释型语言执行效率低的问题,无需重新编译程序就可在多种不同的操作系统下面直接运行。
  • Java程序从源代码到执行过程:
    Java面试资料
  • JDK:是功能齐全的Java SDK,它拥有JRE所有的功能,还有编译器(javac)和工具(javadoc),它可以创建和编译程序。
  • JRE:是java运行时的环境,它不能创建程序。
    如果只是为了运行程序,那么只需装JRE即可,如果需要编程那么需要装JDK。但是有时候不在计算机进行编程也需要装JDK,因为有些其他软件需要JDK环境的支持(比如:Neo4j,在使用Neo4j图数据库前,需要预装JDK环境, 因为Neo4j是用java编写的。使用JSP来部署Web应用程序也需要JDK,因为应用服务程序服务器会将JSP转换为Java Servlet,需要使用JDK来编译Servlet)。

1.2 Java和C++的区别?

  • Java和C++都是面向对象的语言,都支持继承、封装和多态。
  • Java不提供指针来访问内存,使得程序内存更加安全。
  • Java类继承是单继承,C++支持多继承。Java可以通过接口来实现多继承。
  • Java有自动内存管理机制,C++需要程序员手动释放无用的内存。

1.3 什么是 Java 程序主类,应⽤程序和⼩程序的主类有何不同?

  • 一个程序中可以有多个类,但是只能有一个主类,主类是java程序执行的入口。
  • 在java应用程序中,主类是指包含main()方法的类,在java小程序中,主类是指继承自JApplet或Applet的子类。
  • 在java应用程序中,主类不一定要求是public类,在java小程序中,主类必须是public类。

1.4 字符常量和字符串常量的区别?

  • 字符常量是用单引号引起的一个字符,字符串常量是双引号引起的若干个字符。
  • 字符常量相当于一个整型值,可以参与表达式运算;字符串常量代表一个地址值。
  • 字符常量占2个内存,字符串常量占若干个字节。

1.5 构造器 Constructor 是否可被重写?重写和重载的区别?

构造器不可以被重写,但是可以被重载。

1.6 重写和重载的区别?

  • 重载:发生在编译期,在同一个类中,可以对任意的方法重载,不只局限于对构造器进行重载。方法名必须相同,必须修改参数列表(个数、类型、顺序),返回值类型、访问修饰符可以不同。
  • 重写:发生在运行期,在子类中,是子类对父类的允许访问的方法的实现过程进行重新编写。对构造函数不可以重写。要求方法名、返回值类型、参数列表必须相同,而且子类的访问修饰符范围要大于父类。(外表样子不可以改变,但是内部逻辑可以改变)。

1.7 String 为什么是不可变的?

String类中使用final关键字来修饰字符数组来保存字符串(private final char value[]),在Java 9以后,改用byte[]数组来存储字符串(private final byte value[])

1.8 String StringBuffer 和 StringBuilder 的区别是什么?

  • 是否可变性:String由于是字符串常量,所以是不可变的。StringBuffer和StringBuilder是继承自AbstractStringBuiler类(也是用char[] value 来保存字符串,但是没有用final来修饰),所以StringBuffer和StringBuilder是可变的。
  • 线程安全性:由于String是字符串常量,所以它是线程安全的。StringBuffer对方法或对调用的方法加了同步锁,所以StringBuffer是线程安全的。但是StringBuilder没有对方法加同步锁,所以StringBuilder是线程不安全的。
  • 性能方面:需要对String进行修改的时候,每次都要生成一个新的String对象,然后将指针指向新的String对象。但是StringBuffer进行修改的时候,都是对自身进行修改,不是生成新的对象并且对其进行改变。相同情况下,使用StringBuilder仅能提升大约10%左右的性能,但是要冒着线程不安全的风险。

1.9 自动装箱与自动拆箱?

  • 自动装箱:将基本类型用它们对应的引用类型包装起来,装箱过程是通过调用包装器的valueOf方法实现的。(int->Integer)
  • 自动拆箱:将包装类型转换成基本数据类型,拆箱过程是通过调用包装器的 xxxValue方法实现的。(Integer->int)
Integer i = 10;  //装箱			int n = i;   //拆箱
public class Main {
    public static void main(String[] args) {         
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
        System.out.println(i1==i2, i3==i4);
    }
}
输出:true, false
原因:
public static Integer valueOf(int i) {
	if(i >= -128 && i <= IntegerCache.high)
		return IntegerCache.cache[i + 128];
else
        return new Integer(i);
}

在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。

1.10 在⼀个静态方法内调用⼀个非静态成员为什么是非法的?

因为调用静态方法中的方法可以不通过对象来调用,所以在静态方法里,不能调用非静态变量,也不可以访问非静态变量成员。

1.11 在 Java 中定义⼀个不做事且没有参数的构造方法的作用?

因为Java程序在执行子类的构造方法之前,如果子类没有通过super()方法来调用父类特定的构造方法,那么会直接调用父类中无参构造方法。这时候,如果父类只定义了有参构造函数,没有定义无参构造函数,那么就会报错。

1.12 接口和抽象类的区别?

  • 接口的默认修饰符是public,抽象方法的修饰符可以有public、protected和default(不能有private,因为抽象方法就是为了被重写)。
  • 接口中不能存在实现方法,抽象类里面可以有非抽象方法。
  • 一个类可以实现多个接口(java通过接口实现多继承),一个类智能实现一个抽象类。接口本身可以通过extends来扩展多个接口。

1.13 成员变量与局部变量的区别?

成员变量 局部变量
在类中的位置不同 在类中方法外面 在方法或者参数列表中
在内存中的位置不同 在堆中(方法区中的静态区) 在栈中
生命周期不同 随着对象的创建而存在,随着对象的消失而消失 随着方法的调用或者代码块的执行而存在,随着方法的调用完毕或者代码块的执行完毕而消失
初始值 有默认初始值 没有默认初始值,使用之前需要赋值,否则编译器会报错

第2章 Java集合

本章主要介绍Java中集合类!


Java面试资料

2.1 说一下List、Set和Map三者的区别?

  • List存储一组不唯一(可以有重复元素)的有序对象。
  • Set存储一组无重复(不可重复)数据的集合。
  • Map使用键值对来存储数据,Key不可重复,Value可重复。

2.2 说一下ArrayList和LinkedList的区别?

  1. 线程安全:两者都是不同步的,所以都是线程不安全的。
  2. 底层实现方式:ArrayList底层采用数组实现,LinkedList底层采用双向链表实现(JDK1.6之前采用循环链表来实现)
  3. 插入和删除是否受元素位置影响:ArrayList底层采用数组实现,所以插入和删除受元素位置影响(执行add()方法时,默认将元素追加到列表末尾,时间复杂度为O(1)。但是当指定插入的位置或者删除某个元素的时候,需要循环遍历数组,时间复杂度为O(n))。LinkedList的底层采用链表实现,所以插入和删除不受元素位置影响,时间复杂度为O(1)。
  4. 是否支持快速随机访问:ArrayList的get()方法通过索引数组下标可以快速访问,但是LinkedList不支持快速随机访问。
  5. 内存空间占用情况:ArrayList浪费空间体现在:在list列表后面预留若干空间。LinkedList浪费空间体现在:它的每个元素都需要消耗比ArrayList多的空间(需要存放直接后继和直接前驱)。

2.3 ArrayList 与 Vector 区别呢?为什么要⽤Arraylist取代Vector呢?

  • Vector是线程安全的,可以由两个线程安全地访问一个Vector对象,但是当单线程访问Vector对象时,会在同步操作上浪费大量的时间。
  • ArrayList是线程不安全的,在不需要线程安全时建议使用ArrayList节省时间开销。

2.4 说一说 ArrayList 的扩容机制?

  • 扩容时机:当数组的大小大于初始容量的时候(比如默认初始为10,当添加第11个元素的时候),就会进行扩容,新的容量为旧的容量的1.5倍。
  • 扩容方式:以新的容量创建新的数组,让原来的数组指向新的数组,原数组被抛弃(GC回收)。

2.5 说一下HashMap和Hashtable的区别?

  • 线程是否安全:HashMap是线程不安全的,Hashtable是线程安全的(内部的方法都经过synchronized修饰)。
  • 效率:由于线程安全问题,HashMap的执行效率比Hashtable高(现在几乎不用Hashtable)。
  • 对key = null的支持:HashMap中key可以等于null(这样的键只能有一个)。但是在Hashtable中,如果key等于null,会抛出空指针异常。
  • 初始容量以及扩容:
    • 创建时不指定初始容量
      HashMap的初始容量为16,再次扩容大小为2n;Hashtable的初始容量为11,再次扩容的大小为2n+1。
    • 创建时指定初始容量
      HashMap的初始容量直接扩容大小为2的幂次方大小;Hashtable会直接使用初始容量。
  • 底层数据结构:JDK1.8之后,HashMap在解决哈希冲突上面有了较大变化,当链表大小大于阈值(默认为8)时,将链表转化成红黑树。Hashtable没有此机制。

2.6 说一下HashMap的底层实现?

  • JDK1.8之前:底层是数组+链表(链表散列)结合在一起使用。处理冲突的方法是拉链法(创建一个链表数组,数组中的每一格都是一个链表。若遇到哈希冲突,将冲突的值加入到链表中)。
  • JDK1.8之后:在解决哈希冲突上面有了较大变化,当链表大小大于阈值(默认为8)时,将链表转化成红黑树。

2.7 HashMap的工作原理?

HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。

2.8 HashMap中两个对象的hashcode相同会发生什么?

因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。

2.9 如果两个键的hashcode相同,你如何获取值对象?

当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置。找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

2.10 如果HashMap的大小超过负载因子(load factor)定义的容量,怎么办?

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。

2.11 你了解重新调整HashMap大小存在什么问题吗?

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

2.12 说一下HashMap的长度为什么是2的幂次方?

利用位移运算的效率比直接求余运算效率高。所以数组下标的计算方法是“(n-1)&hash”(底层源码)。当进行位移运算时,如果长度不是2的幂次方很容易出现冲突,所以为了尽量较少碰撞和让数据均匀分布,HashMap的长度取2的幂次方。

2.13 说一下HashMap和HashSet的区别?

HashMap HashSet
实现Map接口 实现Set接口
存储键值对 存储唯一对象
调用put()向map添加元素 调用add ()向set添加元素
HashMap使用键(Key)计算Hashcode HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false

2.14 HashSet如何检查重复的?

首先计算加入对象的hashcode值来判断对象的加入位置,如果存在一个对象的hashcode值和加入的对象的相同,那么利用equals()方法来判断两个对象本身是否相同。如果两个对象相同,那么HashSet就不会让操作成功。

2.15 HashMap 多线程操作导致死循环问题?

因为在并发下rehash会造成元素之间形成循环链表,在JDK1.8之后解决了这个问题,但是不建议在多线程下使用HashMap,因为在并发环境下还会出现数据丢失问题。并发环境推荐使用ConcurrentHashMap。

2.16 说一下ConcurrentHashMap和Hashtable的区别?

  • 底层数据结构:

    • ConcurrentHashMap:JDK1.7采用分段的数组+链表实现,JDK1.8采用数组+链表/红黑树实现。
    • Hashtable:一直采用数组+链表,数组是主体,链表只是为了解决哈希冲突。
  • 两者都是线程安全的,但是实现线程安全的方式不同。

    • ConcurrentHashMap(分段锁):在JDK1.7时,对桶数组进行分段分割,每把锁只锁一部分数据,多线程访问容器内不同的数据段的数据时,不会存在锁竞争,提高并发效率。在JDK1.8时,直接使用数组+链表+红黑树来实现,并发控制使用synchronized和CAS来操作。看起来像优化过的且进程安全的HashMap。

    • Hashtable(全表锁):使用synchronized保证线程安全,效率低下。当多个线程同时访问同步方法时,有可能进入堵塞或者轮询状态。

2.17 说一下Comparable和Comparator的区别?

  • Comparable接口来自java.lang,Comparator接口来自java.util。
  • Comparable是一个内比较器,支持自己和自己作比较。Comparator是一个外比较器,可以通过重写compare()方法来定制自己想要的比较规则。
    代码如下(示例):
Arrays.sort(intervals,new Comparator<int[]>(){
            public int compare(int[] a,int[] b){
                if(a[0]!=b[0]){
                    return a[0]-b[0];
                }else{
                    return a[1]-b[1];
                }
            }
        });
Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);

第3章 Java多线程

该处使用的url网络请求的数据。


总结

提示: