Java面试资料
Java面试资料全集
本文章根据开源项目JavaGuide改编+Java开发方向大厂真实面试题!(本文章会持续更新,直到两年后博主拿到大厂offer!)
文章目录
- Java面试资料全集
- 前言
- 第1章 Java基础
- 1.1 关于 JVM JDK 和 JRE 详细通俗的解答
- 1.2 Java和C++的区别?
- 1.3 什么是 Java 程序主类,应⽤程序和⼩程序的主类有何不同?
- 1.4 字符常量和字符串常量的区别?
- 1.5 构造器 Constructor 是否可被重写?重写和重载的区别?
- 1.6 重写和重载的区别?
- 1.7 String 为什么是不可变的?
- 1.8 String StringBuffer 和 StringBuilder 的区别是什么?
- 1.9 自动装箱与自动拆箱?
- 1.10 在⼀个静态方法内调用⼀个非静态成员为什么是非法的?
- 1.11 在 Java 中定义⼀个不做事且没有参数的构造方法的作用?
- 1.12 接口和抽象类的区别?
- 1.13 成员变量与局部变量的区别?
- 第2章 Java集合
- 2.1 说一下List、Set和Map三者的区别?
- 2.2 说一下ArrayList和LinkedList的区别?
- 2.3 ArrayList 与 Vector 区别呢?为什么要⽤Arraylist取代Vector呢?
- 2.4 说一说 ArrayList 的扩容机制?
- 2.5 说一下HashMap和Hashtable的区别?
- 2.6 说一下HashMap的底层实现?
- 2.7 HashMap的工作原理?
- 2.8 HashMap中两个对象的hashcode相同会发生什么?
- 2.9 如果两个键的hashcode相同,你如何获取值对象?
- 2.10 如果HashMap的大小超过负载因子(load factor)定义的容量,怎么办?
- 2.11 你了解重新调整HashMap大小存在什么问题吗?
- 2.12 说一下HashMap的长度为什么是2的幂次方?
- 2.13 说一下HashMap和HashSet的区别?
- 2.14 HashSet如何检查重复的?
- 2.15 HashMap 多线程操作导致死循环问题?
- 2.16 说一下ConcurrentHashMap和Hashtable的区别?
- 2.17 说一下Comparable和Comparator的区别?
- 第3章 Java多线程
- 总结
前言
提示:这里可以添加本文要记录的大概内容。
第1章 Java基础
1.1 关于 JVM JDK 和 JRE 详细通俗的解答
- JVM: 是一种运行Java字节码的虚拟机,它针对不同的操作系统(Windows、Linux、macOS)有特定的实现,目的是让相同的字节码在不同的操作系统下面,得到相同的结果。
- 字节码:它是一种.class文件,是一种只有JVM可以理解的代码,不面向其他任何处理器,只面向JVM虚拟机。
- 字节码的好处:字节码使得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中集合类!
2.1 说一下List、Set和Map三者的区别?
- List存储一组不唯一(可以有重复元素)的有序对象。
- Set存储一组无重复(不可重复)数据的集合。
- Map使用键值对来存储数据,Key不可重复,Value可重复。
2.2 说一下ArrayList和LinkedList的区别?
- 线程安全:两者都是不同步的,所以都是线程不安全的。
- 底层实现方式:ArrayList底层采用数组实现,LinkedList底层采用双向链表实现(JDK1.6之前采用循环链表来实现)
- 插入和删除是否受元素位置影响:ArrayList底层采用数组实现,所以插入和删除受元素位置影响(执行add()方法时,默认将元素追加到列表末尾,时间复杂度为O(1)。但是当指定插入的位置或者删除某个元素的时候,需要循环遍历数组,时间复杂度为O(n))。LinkedList的底层采用链表实现,所以插入和删除不受元素位置影响,时间复杂度为O(1)。
- 是否支持快速随机访问:ArrayList的get()方法通过索引数组下标可以快速访问,但是LinkedList不支持快速随机访问。
- 内存空间占用情况: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网络请求的数据。
总结
提示: