2024年Java实战面试题(北京)_java 5 年 面试-CSDN博客
一、redis基础类型:string(字符串)、hash(哈希)、list(列表)、set(集合)、sort set (有序集合)。
二、多线程有几种实现方式?
四种:①继承Thread类;②实现Runnable接口;③使用Callable和FutureTask实现有返回值的多线程;④使用ExecutorService和Executors工具类实现线程池(如果需要线程的返回值,需要在线程中实现Callable和Future接口)。
三、线程池有哪些?
六种常见线程池:FixedThreadPool(固定线程数的线程池)、CachedThreadPool(缓存线程池)、ScheduledThreadPool(定时线程池)、SingleThreadExecutor(单线程线程池)、SingleThreadScheduleExecutor(单线程线程池)、ForkJoinPool(分而治之)。
四、在Java中有哪些常见的队列?
ArrayList:ArrayList可以被用作队列。
LinkedList:LinkedList也可以用作队列。
ArrayBlockingQueue:ArrayBlockingQueue是一个有界阻塞队列。
LinkedBlockingQueue:LinkedBlockingQueue是一个可选有界或无界的阻塞队列。PriorityBlockingQueue:PriorityBlockingQueue是一个支持优先级的无界阻塞队列。ConcurrentLinkedQueue:ConcurrentLinkedQueue是一个非阻塞无界队列。
五、HashMap和HashTable区别?
1、继承的父类不同
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
2、线程安全性不同
Hashtable 中的方法有Synchronize锁,而HashMap中的方法没有Synchronize锁,在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。
六、ConcurrentHashMap和HashMap的区别?
1、线程安全性:ConcurrentHashMap是线程安全的、HashMap非线程安全
2、锁的粒度:在并发环境虾HashMap需要使用外部锁。ConcurrentHashMap则使用的是分段锁
3、迭代的一致性:HashMap在迭代中对其进行修改,可能会导ConcurrentModificationException 异常或者遍历时丢失一些元素。ConcurrentHashMap允许在迭代的过程中进行修改,不会抛出 ConcurrentModificationException 异常。
4、初始化容量和负载因子的意义:
4.1、HashMap: 初始容量和负载因子用于控制 HashMap 的性能和空间占用。当元素数量达到容量乘以负载因子时,HashMap 会进行扩容。
4.2、ConcurrentHashMap: 初始容量和负载因子在 ConcurrentHashMap 中依然存在,但它们的含义稍有不同。由于 ConcurrentHashMap 的分段结构,不同的段会根据负载因子动态调整大小,而不是整个表进行扩容。
七、ConcurrentHashMap原理是什么?
1、volatile修饰的节点数组
//ConcurrentHashMap使用volatile修饰节点数组,保证其可见性,禁止指令重排。
transient volatile Node<K,V>[] table;
2、ConcurrentHashMap的put()方法
//put()方法直接调用putVal()方法
public V put(K key, V value) {
return putVal(key, value, false);
}
putVal方法内容语言解析
做插入操作时,首先进入乐观锁,
然后,在乐观锁中判断容器是否初始化,
如果没初始化则初始化容器,
如果已经初始化,则判断该hash位置的节点是否为空,如果为空,则通过CAS操作进行插入。
如果该节点不为空,再判断容器是否在扩容中,如果在扩容,则帮助其扩容。
如果没有扩容,则进行最后一步,先加锁,然后找到hash值相同的那个节点(hash冲突),
循环判断这个节点上的链表,决定做覆盖操作还是插入操作。
循环结束,插入完毕。
3、ConcurrentHashMap的get()方法
//ConcurrentHashMap的get()方法是不加锁的,方法内部也没加锁。
public V get(Object key)
ConcurrentHashMap的get()方法是不加锁的,为什么可以不加锁?因为table有volatile关键字修饰,保证每次获取值都是最新的
八、线程生命周期?
new(初始化状态)、Runnable(就绪状态)、Running(运行状态)、Blocked(阻塞状态)、Terminated(终止状态)
九、JDK 和 JRE 有什么区别?
1、jdk和jre的区别为:JRE是java运行时环境而JDK是java开发工具包,JDK包含JRE,但是JRE可以独立安装。
2、JDK:java development kit (java开发工具),JDK 是用于开发 Java 程序的最小环境。
3、JRE:java runtime environment (java运行时环境),是提供给 Java 程序运行的最小环境
十、== 和 equals 的区别是什么?== 解读
"=="是判断两个变量或实例是不是指向同一个内存空间?
"equals"是判断两个变量或实例所指向的内存空间的值是不是相同。
==解读:==比较的是2个对象的地址,基本数据类型是直接保存在栈里面的,所以如果两个基本数据类型的值相同,则他们的栈的地址也相同。如果两个引用数据类型使用==比较,只要是两个分别new出来的对象,得到的结果就一定是false了。
十一、两个对象的 hashCode() 相同, 那么 equals() 也一定为 true吗?
不一定为true,哈希值是JDK根据对象的地址或者字符串或者数字(属性)算出来的int类型的数值,对象的地址一样String str1="通话",String str2="话通" 内存地址一样字符串长度一样hashcode一样,但是value对比的是值,所以不一定为true。
十二、final 在 Java 中有什么作用?
final可以用来修饰的结构:类、方法、变量
final用来修饰一个类:此类不能被其它类继承。
final用来修饰方法 :表明此方法不可以被重写
final用来修饰变量 ,此时变量就相当于常量
十三、Java 中的 Math. round(-1. 5) 等于多少?
Math.round(-1.5)的返回值是-1。四舍五入的原理是在参数上加0.5然后做向下取整。
十四、String 属于基础的数据类型吗?
String不是基本数据类型,是引用数据类型。
十五、Java 中操作字符串都有哪些类?它们之间有什么区别?
操作字符串的类有:String、StringBuffer、StringBuilder。
拼接速度:StringBuilder>StringBuffer>String
线程方面:StringBuilder线程不安全、StringBuffer线程安全、
十六、String str="i"与 String str=new String("i")一样吗?(这道题考的原理是对Jvm对象一些存放位置的考察)
`String str="i";` 是一种简单的字符串字面量的创建方式,它使用字符串常量池中已经存在的 "i" 字符串。
而 `String str=new String("i");` 则是在堆上通过构造函数 new 一个新的 String 对象,这个新对象会在堆上分配一个新的内存地址。
十七、如何将字符串反转?
StringBuilder 或者StringBuffer的 reverse() 方法。
十八、String 类的常用方法都有那些?
equals:字符串是否相同、 equalsIgnoreCase:忽略大小写后字符串是否相同 、compareTo:根据字符串中每个字符的Unicode编码进行比较、 compareToIgnoreCase:根据字符串中每个字符的Unicode编码进行忽略大小写比较、 indexOf:目标字符或字符串在源字符串中位置下标、 lastIndexOf:目标字符或字符串在源字符串中最后一次出现的位置下标、 valueOf:其他类型转字符串 、charAt:获取指定下标位置的字符、 codePointAt:指定下标的字符的Unicode编码、 concat:追加字符串到当前字符串、 isEmpty:字符串长度是否为0、 contains:是否包含目标字符串 、startsWith:是否以目标字符串开头、 endsWith:是否以目标字符串结束、 format:格式化字符串、 getBytes:获取字符串的字节数组 、getChars:获取字符串的指定长度字符数组、 toCharArray:获取字符串的字符数组 、join:以某字符串,连接某字符串数组、 length:字符串字符数、 matches:字符串是否匹配正则表达式、 replace:字符串替换 、replaceAll:带正则字符串替换 、replaceFirst:替换第一个出现的目标字符串 、split:以某正则表达式分割字符串、 substring:截取字符串、 toLowerCase:字符串转小写 、toUpperCase:字符串转大写、 trim:去字符串首尾空格。
十九、抽象类必须要有抽象方法吗?
不需要,抽象类不一定非要有抽象方法
二十、普通类和抽象类有哪些区别?
抽象类需要abstract进行修饰
普通类可以实例化,抽象类不可以实例化
抽象方法只能定义在抽象类中,但是抽象类中可以有抽象方法和普通方法
二十一、抽象类能使用 final 修饰吗?
抽象类不能使用 final 修饰符,因为这两者的设计目的是相互矛盾的。抽象类是为了被继承和实现而存在的,使用 final 修饰符就不能被继承了。
二十二、接口和抽象类有什么区别?
声明方式不同:接口通过interface关键字声明;抽象类通过abstract声明。
修饰方式的不同:接口通常使用 public 修饰,也允许默认修饰符,只限本包访问。抽象类可以被 public、protected、default 修饰,但是不能被 private 和 final 修饰,抽象类可以有非抽象的方法。
变量的不同:接口中声明的变量全是静态常量;抽象类中可以有不同类型的变量。
实现的不同:类通过implements关键字来实现接口,一个类可以实现多个接口;类通过extends关键字来继承抽象类,一个类只能继承一个抽象类,因为抽象类首先是一个类。
方法是否需要实现: 抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类;接口里的接口方法如果不能被全部实现,那么该类也只能为抽象类。
二十三、Java 中 IO 流分为几种?
在Java中,IO流主要分为4种类型:字节流、字符流、字节缓冲流和字符缓冲流。
二十四、BIO、NIO、AIO 有什么区别?
BIO:Blocking IO,阻塞io,效率很低,因为经常被阻塞
NIO:New Non-Blocking:非阻塞IO(单线程模式)
AIO:异步IO
二十五、Files的常用方法都有哪些?
Files.exists() : 检测该路径文件是否存在
Files.createFile() : 创建文件
Files.createDirectory() : 创建目录
Files.copy() : 复制文件
Files.move() : 移动文件
Files.delete() : 删除文件
Files.size() : 查询文件个数
Files.read() : 读文件
Files.write() : 写文件
二十六、Java 容器都有哪些?
Collection:
list:
ArrayList:动态数组,可以根据需要自动扩展大小。
LinkedList:双向链表,可以高效地进行插入和删除操作。
Vector:动态数组,可以根据需要自动扩展大小。->Stack:栈,后进先出的数据结构。
Queue(队列,先进先出的数据结构):
LinkedList:双向链表,可以高效地进行插入和删除操作。
PriorityQueue:优先队列,根据元素的优先级进行排序。
set:
HashSet:无序集合,不允许重复元素。->LinkHashSet:实现的有序去重集合列表
TreeSet:有序集合,按照元素的自然顺序进行排序。
Map:
HashMap:无序键值对集合,根据键快速查找值。
TreeMap:有序键值对集合,根据键的自然顺序进行排序。
二十七、Collection 和 Collections 有什么区别?
collection是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,实现该接口的类主要有List和Set。
collections是针对集合类的一个包裹类,它提供了一系列静态方法实现对各种集合的搜索、排序以及线程安全化等操作。
二十八、List、Set、Map 之间的区别是什么?
List:有序、可重复。通过索引查找快,增删速度慢 (操作时后续的数据需要移动)。
Set:无序、不可重复的集合。
Map:键值对、键唯一、值不唯一。Map 集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对 map 集合遍历时先得到键的 set 集合,对 set 集合进行遍历,得到相应的值。
二十九、如何决定使用 HashMap 还是 TreeMap?
你需要得到一个有序的结果时就应该使用TreeMap(因为HashMap中元素的排列顺序是不固定的)。除此之外,由于HashMap有更好的性能,所以大多不需要排序的时候我们会使用HashMap。
三十、说一下 HashMap 的实现原理?
数组、链表、红黑树(jdk1.8)
三十一、说一下 HashSet 的实现原理?
HashSet的底层是使用一种称为哈希表的数据结构,值得一提的是,在Java中,HashSet内部是使用HashMap来存储元素的(将整个元素作为key)
三十二、ArrayList 和 LinkedList 的区别是什么?
数据结构不同:
ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。
效率不同:
当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。
自由性不同:
ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;
而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。
主要控件开销不同
ArrayList主要控件开销在于需要在List列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。
1、在后方插入时,ArrayList和LinkedList数据量小时速度相差无几,数据量大了之后ArrayList完胜;
2、在前方插入时,LinkedList速度完爆ArrayList,充分体现了其优势;
3、在随机插入时,LinkedList又被ArrayList完爆了(出来混迟早要还的)
4、在前方插入时,ArrayList可以使用后方插入,最后再使用Collections.reverse()方法反转,速度依然完爆LinkedList
三十三、如何实现数组和 List 之间的转换?
List<X> XList = Arrays.asList(X类型的数组);
三十四、ArrayList 和 Vector 的区别是什么?
Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。
三十五、Array 和 ArrayList 有何区别?
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
三十六、在 Queue 中 poll()和 remove()有什么区别?
poll()方法队列为null返回null或特定的空值
remove()方法在队列为空时常是NoSuchElementException或类似的异常
三十七、哪些集合类是线程安全的?
线程安全的集合有Vector、HashTable、Stack、ArrayBlockingQueue、ConcurrentHashMap、ConcurrentLinkedQueue等。
三十八、迭代器 Iterator 是什么?
迭代器(Iterator)是一个对象,它的工作是遍历并目标序列中的对象,它提供了一种访问一个容器(container)对象中的各个元素的方法,把访问逻辑从不同类型的集合类中抽象出来,又不必暴露该对象内部细节。通过迭代器,开发人员不需要了解容器底层的结构,就可以实现对容器的遍历。由于创建迭代器的代价小,因此迭代器通常被称为轻量级的容器。
三十九、 Iterator 怎么使用?有什么特点?
可以在遍历的同时进行删除操作
对于所有的集合都可以使用
不会影响集合的遍历过程
四十、Iterator 和 ListIterator 有什么区别?
1. 支持遍历方向不同:Iterator 只支持从前向后遍历集合,而 ListIterator 支持从前向后遍历和从后向前遍历两个方向。
2. 支持修改元素的方法不同:Iterator 只支持使用 remove() 方法删除集合中的元素,不支持修改和添加操作;而 ListIterator 则支持使用 set() 修改当前元素,以及使用 add() 方法在当前元素之前添加元素。
3. 支持元素索引不同:Iterator 没有提供获取元素索引的方法,而 ListIterator 可以通过 nextIndex() 和 previousIndex() 方法获取下一个元素和上一个元素的索引值。
四十一、怎么确保一个集合不能被修改?
可以使用Collections.unmodifiableXXX
方法来确保一个集合不能被修改
四十二、并行和并发有什么区别?
并行:在同一时刻,任务同时开始进行,彼此没有依赖关系。多个任务同一时刻同时执行。
并发:以交替的方式 利用等待某件事情完成的时间 来做其他事情,轮流执行任务,不一定同时。宏观上同时,微观上依次执行。
四十三、线程和进程的区别?
进程包含线程:一个进程可以拥有多个线程,每个进程至少要有一个线程存在,这一个线程称之为主线程。
进程是系统分配资源的最小单位,线程是操作系统调度的最小单位
线程的创建、消耗、调度都要比进程快
四十四、守护线程是什么?
守护线程,是指在程序运行的时,后台提供一种通用服务的线程。比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了
四十五、创建线程有哪几种方式?
继承Thread,重写run方法
实现Runnable接口,重写run方法
使用匿名内部类创建 Thread 子类对象
使用匿名内部类,实现Runnable接口
lambda表达式
实现Callable接口
使用线程池创建线程
四十六、说一下 runnable 和 callable 有什么区别?
两者最大的不同点是:Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值
Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息。
四十七、线程有哪些状态?
new(初始化状态)、Runnable(就绪状态)、Running(运行状态)、Blocked(阻塞状态)、Terminated(终止状态)
四十八、sleep() 和 wait() 有什么区别?
1、类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
2、是否释放锁:sleep() 不释放锁;wait() 释放锁。
3、用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
4、用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒
四十九、notify()和 notifyAll()有什么区别?
1、notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。
2、notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而
3、notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制
4、调用对象的 notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获得该对象锁,要等到执行notify方法的线程将程序执行完 ,也就是退出sychronized代码块后,当前线程才会释放锁,该而呈wait状态所在的线程才可以获取对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用
五十、线程的 run() 和 start() 有什么区别?
run() 方法用于执行线程的任务,而 start() 方法用于启动线程。
run() 方法可以被多次调用,每次调用都会执行一次任务,而 start() 方法只能被调用一次。
五十一、创建线程池有哪几种方式?
Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池; Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置
五十二、线程池都有哪些状态?
Running、ShutDown、Stop、Tidying、Terminated
线程池的初始化状态是RUNNING
当一个线程池调用shutdown()方法时,线程池由RUNNING -> SHUTDOWN
调用线程池的shutdownNow()方法的时候,线程池由(RUNNING或者SHUTDOWN ) -> STOP
当所有的任务已终止,记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。 terminated()方法在ThreadPoolExecutor类中是空的,没有任何实现
线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED
五十三、线程池中 submit() 和 execute() 方法有什么区别?
execut()可以添加一个Runable任务,submit()不仅可以添加Runable任务还可以添加Callable任务
execut()没有返回值,而submit()在添加Callable任务时会有返回值(再添加Runable任务时也有,不过无意义),可以通过返回值来查看线程执行的情况
如果发生异常submit()可以通过捕获Future.get抛出的异常,而execute()会终止这个线程
五十四、在 Java 程序中怎么保证多线程的运行安全?
线程切换带来的原子性问题
解决办法:使用多线程之间同步synchronized或使用锁(lock)。
缓存导致的可见性问题
解决办法:synchronized、volatile、LOCK,可以解决可见性问题
编译优化带来的有序性问题
解决办法:Happens-Before 规则可以解决有序性问题
五十五、多线程中 synchronized 锁升级的原理是什么?
锁的升级过程是为了适应不同竞争程度的场景。在竞争不激烈的情况下,使用偏向锁和轻量级锁可以减少系统开销和线程切换,提高性能。而在竞争激烈的情况下,采用重量级锁可以确保线程访问的同步性,保证数据的一致性。这种升级策略可以根据实际场景进行优化,提高程序运行效率。
五十六、什么是死锁?
是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面
五十七、怎么防止死锁?
1、避免嵌套锁: 尽量避免一个线程在持有一个锁的同时去请求另一个锁。
2、请求和释放锁的顺序: 确保所有线程以相同的顺序请求和释放锁。
3、使用定时锁: 使用tryLock()方法来请求锁,它允许线程等待锁一定的时间后放弃,从而避免死锁。
4、锁分割: 将大的锁分割成几个小的锁,如果可能的话,使得不同的线程可以同时访问不同的资源。
5、检测死锁: 使用工具或JVM内置功能(如JConsole)来监控和检测系统中的死锁,然后进行相应的处理。
五十八、ThreadLocal 是什么?有哪些使用场景?