Java 基础面试题及答案汇总
Java 基础面试题涵盖 JDK 与 JRE 区别、equals 与==、final 修饰符、字符串处理、IO 流模型、集合框架(List/Set/Map)、多线程(线程状态、锁机制、线程池)、反射、序列化、设计模式等核心知识点。内容包含 79 道常见面试题及其详细解答,适合初中级 Java 程序员查漏补缺,面试官参考使用。重点解析 HashMap 原理、synchronized 锁升级、ThreadLocal 应用场景等高频考点。

Java 基础面试题涵盖 JDK 与 JRE 区别、equals 与==、final 修饰符、字符串处理、IO 流模型、集合框架(List/Set/Map)、多线程(线程状态、锁机制、线程池)、反射、序列化、设计模式等核心知识点。内容包含 79 道常见面试题及其详细解答,适合初中级 Java 程序员查漏补缺,面试官参考使用。重点解析 HashMap 原理、synchronized 锁升级、ThreadLocal 应用场景等高频考点。

JDK 是 Java 开发工具包,而 JRE 是 Java 运行时环境,JDK 包含了 JRE。JDK 是 Java 开发的核心,提供了完整的开发环境;而 JRE 则是 Java 程序运行的基础,确保了 Java 程序能够在不同的平台上运行。**JDK:**面向 Java 开发者,**JRE:**面向 Java 程序使用者
==:这是一个运算符,对于基本数据类型,比较的是值是否相等;对于引用数据类型,比较的是两个对象的引用是否指向同一块内存地址。
equals:这是一个方法,默认情况下,equals 方法比较的是两个对象的引用是否相等(即与 == 运算符相同)。但通常,我们会在子类中重写 equals 方法,以比较两个对象的属性值是否相等。
不对。在 Java 中,两个对象的
hashCode()相同,并不意味着equals()也一定为true。hashCode():返回对象的哈希码值,是一个整数。主要用于哈希表等数据结构中,以快速定位对象。同一个对象多次调用
hashCode()方法应该返回相同的值,但不同的对象可能会有相同的哈希码(哈希冲突)
final是一个非常重要的修饰符,它的主要作用是限制访问和修改,以确保程序的稳定性和安全性。修饰类:不能被继承,不希望这些逻辑被子类修改或扩展。例如,Java 标准库中的
String类修饰方法:不能被子类重写(override),防止关键业务逻辑被子类改变,确保类的功能不被意外或恶意修改
修饰变量:常量,它们的值在初始化后就不能再被修改,确保某些关键数据不会被意外更改
Math.round()方法用于将浮点数四舍五入为最接近的整数。Math.round(-1.5)的结果是-1,而不是-2。这是因为Math.round()方法在处理负数时采用的是'向零舍入'的策略,即向着零的方向进行舍入。
String不属于 基本数据类型(primitive type)。相反,String是一个类(class),属于引用数据类型(reference type)
String 类,StringBuilder 类,StringBuffer 类,StringTokenizer 类,正则表达式类,如
java.util.regex.Pattern和java.util.regex.Matcher可变性:
String是不可变的,而StringBuilder和StringBuffer是可变的。线程安全性:
String是线程安全的(因为它是不可变的),StringBuffer也是线程安全的,而StringBuilder不是线程安全的。性能:在单线程环境下,
StringBuilder的性能通常比StringBuffer更高,因为它不需要线程安全性的开销。而String在频繁修改字符串的情况下可能会导致较大的性能损失,因为它每次操作都会创建新的字符串对象。
并不完全一样,
str1和str2的内容相同,但它们的引用不同。str1引用的是常量池中的字面量,而str2引用的是堆上新创建的对象。因此,使用==运算符比较它们时,结果是false。但是,使用equals()方法比较它们的内容时,结果是true。性能:字面量方式通常更高效,因为它避免了不必要的对象创建和垃圾回收。
StringBuilder和StringBuffer类都提供了reverse方法,可以直接反转字符串。由于StringBuilder在单线程环境下性能更高,因此通常更推荐使用它。
length():返回字符串的长度。charAt(int index):返回指定索引处的字符。substring(int beginIndex):返回从指定索引开始的子字符串。substring(int beginIndex, int endIndex):返回从指定开始索引到结束索引(不包括结束索引)的子字符串。indexOf(int ch):返回指定字符第一次出现的索引。lastIndexOf(int ch):返回指定字符最后一次出现的索引。compareTo(String anotherString):按字典顺序比较两个字符串。toLowerCase():将字符串转换为小写。toUpperCase():将字符串转换为大写。trim():去除字符串两端的空格。toCharArray():将字符串转换为字符数组。getBytes():将字符串转换为字节数组。replace(char oldChar, char newChar):替换字符串中的字符。split(String regex):根据正则表达式拆分字符串。concat(String str):将指定字符串拼接到当前字符串的末尾。isEmpty():判断字符串是否为空(长度为 0)。startsWith(String prefix):判断字符串是否以指定前缀开头。endsWith(String suffix):判断字符串是否以指定后缀结尾。由于
String类是不可变的,所有修改字符串的方法都会返回一个新的字符串对象,而不是修改原字符串
抽象类不一定要包含抽象方法,但它们的主要目的是提供一个通用的基类,并且可以包含一些通用的方法实现或状态,以供子类继承和使用。如果抽象类不包含任何抽象方法,它仍然不能被实例化,因为它的本质仍然是一个抽象类。
抽象类不能使用
final修饰。这是因为final关键字用于表示一个类是不能被继承的,而抽象类的主要目的正是为了被其他类继承,并提供一些通用的方法或状态给子类使用或实现。
一个类可以实现多个接口,这提供了多重继承的能力(针对接口)。一个类只能继承一个抽象类,这是单继承的限制。
接口中的属性默认是
public static final修饰的,即它们是公开的、静态的、不可变的常量。抽象类中的属性访问控制符没有这样的限制,可以是任意访问修饰符。接口中的方法默认是
public的,并且不能定义为其他访问修饰符。抽象类中的方法控制符没有限制,但抽象方法不能使用private修饰。在 JDK 8 之前,接口中的方法不能有具体实现(只能是抽象方法)。从 JDK 8 开始,接口可以包含有具体实现的默认方法(
default方法)和静态方法(static方法)。接口中不能使用静态代码块。抽象类中可以使用静态代码块。
字节输入流:如
InputStream。 字符输入流:如Reader。 字节输出流:如OutputStream。 字符输出流:如Writer。按照数据类型分类
字节流(Byte Stream):用于处理字节数据,以字节为单位进行数据的读写。字节输入流:如
FileInputStream、ByteArrayInputStream等。字节输出流:如FileOutputStream、ByteArrayOutputStream等。字符流(Character Stream):用于处理 Unicode 字符数据,以字符为单位进行数据的读写。字符输入流:如
FileReader、CharArrayReader、BufferedReader等。字符输出流:如FileWriter、CharArrayWriter、BufferedWriter等。节点流(Node Stream):也称为低级流,可以直接从/向一个特定的 IO 设备(如磁盘、网络)读/写数据。如
FileInputStream、FileOutputStream等。处理流(Processing Stream):也称为高级流,是对一个已存在的流的连接和封装,通过所封装的流的功能实现数据读写功能。处理流不能直接与 IO 设备进行交互,必须建立在低级流的基础之上。如
BufferedInputStream、BufferedOutputStream、DataInputStream、DataOutputStream、InputStreamReader、OutputStreamWriter等。
BIO(Blocking I/O):是 Java 早期版本中使用的 I/O 模型,也称为同步阻塞 I/O 模型。在 BIO 模型中,当应用程序发起 I/O 操作时,会创建一个线程来执行该操作。如果操作尚未完成,线程将被阻塞,直到操作完成。优点是编程方式简单,代码易理解,缺点是并发性能较差,因为每个线程都会阻塞等待 I/O 完成,导致资源浪费。适用于连接数比较小且固定的架构,如文件服务器或小型网站。
NIO(Non-Blocking I/O):是 Java 1.4 引入的新 I/O 模型,也称为非阻塞 I/O 模型。NIO 使用了非阻塞的 I/O 方式,允许线程在等待 I/O 操作完成时执行其他任务。NIO 提供了更多的选择器、通道等操作,使开发者能够更灵活地控制读写操作。优点是高效且灵活,能够支持处理成千上万的并发连接,缺点是编程难度较高,需要处理不同的操作系统底层细节和协议。适用于连接数比较多且连接比较短(轻操作)的架构,如聊天服务器。
AIO(Asynchronous I/O):是 Java 1.7 提供的新 I/O 模型,也称为异步非阻塞 I/O 模型。AIO 使用了异步 I/O 方式,与 I/O 操作相关的线程会在完成操作后通知应用程序。这意味着应用程序可以在等待 I/O 操作完成时执行其他任务。优点是异步非阻塞,能够实现异步处理操作,适合处理高并发场景,且编程模型相对简单和统一,缺点是技术难度较高,需要开发者具备一定的异步编程经验,且依赖于操作系统提供的异步 I/O 支持。适用于连接数比较多且连接比较长(重操作)的架构,如相册服务器。
创建文件
Files.createFile(Path path): 创建一个新的空文件,如果文件已存在则抛出异常。Files.createFile(Path path, FileAttribute<?>... attrs): 创建一个新的空文件,并指定文件属性。删除文件
Files.delete(Path path): 删除一个文件,如果文件不存在则抛出异常。Files.deleteIfExists(Path path): 如果文件存在则删除它,不存在则不执行任何操作。读取文件
Files.readAllBytes(Path path): 读取文件的所有字节并返回一个字节数组。Files.readAllLines(Path path): 读取文件的所有行并返回一个List<String>。Files.newBufferedReader(Path path): 打开一个文件并返回一个BufferedReader来读取文件内容。写入文件
Files.write(Path path, byte[] bytes): 将字节数组写入文件,如果文件已存在则覆盖。Files.write(Path path, List<String> lines): 将字符串列表写入文件,每个字符串作为一行。Files.newBufferedWriter(Path path, OpenOption... options): 打开一个文件并返回一个BufferedWriter来写入文件内容。复制文件
Files.copy(Path source, Path target, CopyOption... options): 复制文件到目标位置,可以指定复制选项如StandardCopyOption.REPLACE_EXISTING。移动文件
Files.move(Path source, Path target, CopyOption... options): 移动文件到目标位置,可以指定移动选项。目录操作
Files.createDirectory(Path dir): 创建一个新的空目录,如果目录已存在则抛出异常。Files.createDirectories(Path dir): 创建指定路径中的所有目录,包括必要的父目录。Files.delete(Path path): 删除一个空目录,如果目录不为空则抛出异常。Files.list(Path dir): 返回一个Stream<Path>,包含目录中的每个项。Files.newDirectoryStream(Path dir): 打开一个目录并返回一个DirectoryStream<Path>来迭代目录中的项。文件属性与元数据
Files.readAttributes(Path path, Class<A> type): 读取文件的属性,返回一个指定类型的属性对象。Files.getAttribute(Path path, String name, LinkOption... options): 读取文件的单个属性。Files.setAttribute(Path path, String name, Object value, LinkOption... options): 设置文件的单个属性。文件权限与所有权
Files.setPosixFilePermissions(Path path, Set<PosixFilePermission> perms): 设置文件的 POSIX 权限。Files.isReadable(Path path): 检查文件是否可读。Files.isWritable(Path path): 检查文件是否可写。Files.isExecutable(Path path): 检查文件是否可执行。Files.getOwner(Path path): 获取文件的所有者。Files.setOwner(Path path, UserPrincipalLookupService lookupService, String owner): 设置文件的所有者。其他常用方法
Files.exists(Path path): 检查文件或目录是否存在。Files.isDirectory(Path path): 判断路径是否为目录。Files.isRegularFile(Path path): 判断路径是否为常规文件。 : 获取文件的大小(以字节为单位)。 : 获取文件最后一次修改的时间。 : 创建一个临时文件,并返回其路径。
在 Java 中,容器是用于存储和管理多个对象的数据结构。Java 的容器主要分为几大类,包括 List、Set、Map 等。以下是详细的分类和每种容器的一些常见实现:
- List(列表)
- ArrayList:基于动态数组实现,提供快速的随机访问,但在插入和删除元素时可能需要移动其他元素。
- LinkedList:基于链表实现,提供快速的插入和删除操作,但在随机访问元素时效率较低。
- Vector:与 ArrayList 类似,但它是线程安全的,性能相对较低。
- Set(集合)
- HashSet:基于哈希表实现,提供快速的插入、删除和查找操作。
- LinkedHashSet:类似于 HashSet,但维护了元素的插入顺序。
- TreeSet:基于红黑树实现,可以对元素进行排序。
- Map(映射)
- HashMap:基于哈希表实现,提供快速的插入、删除和查找操作。
- LinkedHashMap:类似于 HashMap,但维护了键值对的插入顺序。
- TreeMap:基于红黑树实现,可以对键进行排序。
- Queue(队列)
- PriorityQueue:基于优先级堆实现,元素按照优先级进行排序。
- ArrayDeque:基于动态数组实现,提供快速的插入和删除操作。
- LinkedList:可以用作队列、栈或双端队列。
- Deque(双端队列)
- ArrayDeque:基于动态数组实现,提供快速的插入和删除操作。
- LinkedList:同样可以用作双端队列。
- 其他特殊用途的容器类
- Stack:栈是后进先出(LIFO)的数据结构,Java 提供了 Stack 类来实现栈的功能。它继承自 Vector,因此也是线程安全的。
- BitSet:用于表示一组位或布尔值的容器,提供了一种紧凑的方式来存储大量的布尔值。
- Properties:是 Hashtable 的子类,通常用于处理配置文件中的键值对,提供了方便的方法来加载和存储属性文件。
容器(Container)
- 广义概念:容器是一个更加通用的术语,它包括了数组和集合在内。容器用于存储和组织数据,可以根据需要选择合适的数据结构。容器的目的是管理数据的存储和访问,提供数据操作的方法。容器可以是数组,也可以是集合,取决于具体的需求和场景。
- 管理对象关系:容器还可以管理对象的生命周期、对象与对象之间的依赖关系。例如,在 Java EE 中,容器(如 Servlet 容器、EJB 容器)会自动管理这些关系,开发者无需手动编写相关代码。
集合(Collection)
- 数据结构:集合是一种动态大小的数据结构,可以容纳一组元素。集合的大小可以根据需要动态增加或减少,不需要手动指定大小。集合通常用于存储对象(类的实例),而不是基本数据类型。集合提供了丰富的操作方法,如添加、删除、查询、遍历等。
- 框架体系:Java 集合框架包括 List、Set、Map 等不同类型的集合。每种集合类型都有其特定的特性和用途。例如,List 是有序集合,允许元素重复;Set 是无序集合,不允许元素重复;Map 用于存储键值对。
区别总结
- 广义与狭义:容器是一个广义的术语,包括了数组和集合等多种数据结构;而集合是 Java 中存储对象数据的一种狭义的数据结构。
- 功能范围:容器不仅用于存储数据,还可能包括管理对象关系等更广泛的功能;而集合主要关注于数据的存储和操作。
- 具体实现:集合是 Java 集合框架的一部分,提供了丰富的实现类(如 ArrayList、HashSet 等);而容器可能包括更广泛的数据结构,如数组、自定义容器类等。
Collection:是 Java 集合框架中的一个接口,表示一组对象的集合。它是所有集合类的父接口,为集合操作提供了基本的规范。Collection 是一个抽象的概念,定义了集合的基本操作,如添加、删除、遍历等,这些操作通过接口中的方法来实现,具体的集合类需要实现这些方法来提供具体的功能。
Collections:是 Java 提供的一个工具类,它包含了一系列对集合进行操作的静态方法。Collections 不是一个接口,而是一个具体的类。它提供了许多实用的静态方法,用于对集合进行排序、查找、替换、复制、同步等操作。这些方法通常用于对集合进行一些常见的操作,使用起来非常方便。
有序性
- List:是有序的集合,可以按照插入顺序或者指定顺序进行元素访问,允许存储重复元素。
- Set:是无序的集合,不保证元素的顺序,且不允许重复元素。
- Map:是无序的键值对映射的集合,键是唯一的,每个键只能出现一次,值可以重复。虽然 Map 本身是无序的,但某些实现类(如
LinkedHashMap和TreeMap)可以维护特定的顺序。元素唯一性
- List:允许存储重复元素,同一个元素可以存储多次。
- Set:不允许重复元素,如果尝试添加重复元素,则添加操作不会成功。
- Map:键是唯一的,每个键只能映射到一个值,但值可以重复。即可以有多个键值对使用相同的值,但键必须不同。
接口继承关系
- List 和 Set 都继承自
Collection接口,因此它们都具有Collection接口的一些通用方法,如add、remove、contains等。- Map 不是
Collection的子接口,它是一个独立的接口,用于表示键值对映射的集合。应用场景
- List:适用于需要保持元素顺序且可以有重复元素的情况,如存储列表数据。
- Set:适用于需要保持元素唯一性且不需要保持顺序的情况,如去重操作、集合运算等。
- Map:适用于以键和值的形式进行数据存储的情况,如存储学生的姓名和对应的学号、配置信息等。
线程安全性
- HashMap:是非线程安全的,这意味着在多线程环境下使用 HashMap 时需要手动同步。
- Hashtable:是线程安全的,它的方法都是同步的,因此可以在多线程环境下安全地使用,但性能相对较低。
允许 null 值
- HashMap:允许键或值为 null,但键只能有一个 null 值,且总是存储在 table 数组的第一个节点上。
- Hashtable:不允许键或值为 null,如果尝试添加 null 键或值,将会抛出 NullPointerException 异常。
继承关系
- HashMap:继承自 AbstractMap 类。
- Hashtable:继承自 Dictionary 类,但 Dictionary 类已经被废弃,因此 Hashtable 也不再推荐使用。
初始容量和扩容机制
- HashMap:默认初始容量为 16,当已用容量超过总容量与负载因子的乘积时(默认负载因子为 0.75),会进行扩容,容量翻倍。
- Hashtable:默认初始容量为 11,扩容机制为将当前容量翻倍再加 1。
遍历方式
- HashMap:只支持 Iterator 遍历。
- Hashtable:除了支持 Iterator 遍历外,还支持 Enumeration 遍历。
哈希算法
- HashMap:在计算哈希值时,会对 key 的 hashCode 进行二次哈希,以获得更好的散列值。
- Hashtable:直接使用 key 的 hashCode 对 table 数组的长度进行取模。
决定使用 HashMap 还是 TreeMap 主要取决于具体需求和场景。
性能与时间复杂度
- HashMap:在理想情况下,插入、删除、查找的时间复杂度为 O(1)。但在最坏情况下,如果所有的键都在同一个桶中(发生哈希冲突),性能可能降为 O(n)。
- TreeMap:基于红黑树实现,插入、删除、查找的时间复杂度为 O(log n)。
排序与顺序
- HashMap:不保证键值对的顺序,键值对的顺序可能会随时间变化。
- TreeMap:按键的自然顺序(如果键实现了 Comparable 接口)或自定义 Comparator 提供的顺序进行排序。
null 键和值支持
- HashMap:允许一个 null 键和多个 null 值。
- TreeMap:不允许 null 键,但允许多个 null 值。
HashMap 的实现原理基于哈希表(hash table),它使用哈希算法来存储键值对(key-value pairs)。
数据结构
- 底层结构:HashMap 的底层是一个数组 + 链表 + 红黑树的组合。在 Java 8 及以后版本中,当链表长度超过一定阈值(默认是 8)时,链表会转换为红黑树以提高查找性能。
- 数组:HashMap 的核心是一个名为
table的数组,用于存储键值对。数组的每一项可以是一个链表或红黑树的头节点。- 链表/红黑树:当多个键通过哈希函数映射到数组的同一个索引位置时,这些键值对将形成一个链表或红黑树(取决于链表长度)。
哈希函数
- 计算哈希值:通过调用键的
hashCode()方法计算原始哈希值,然后通过扰动函数(hash 函数)对原始哈希值进行处理,以减少哈希冲突。- 定位桶位置:使用哈希值通过取模运算(
hash % table.length)找到对应的数组索引(桶位置)。存储数据(put 方法)
- 计算哈希值:对键调用
hashCode()方法计算哈希值。- 定位桶位置:使用哈希值找到对应的数组索引。
- 处理哈希冲突:如果桶位置为空,直接将键值对存储在该位置。如果桶位置已有元素,遍历链表或红黑树查找是否已存在相同的键:如果键已存在,更新值。如果键不存在,将新键值对添加到链表或红黑树的末尾。
获取数据(get 方法)
- 通过计算键的哈希值找到对应的桶位置。在桶位置遍历链表或红黑树以找到匹配的键。如果找到,则返回对应的值;否则返回 null。
HashSet 的实现原理基于 HashMap。
数据结构
- HashSet 的底层实际上是使用 HashMap 来存储元素的。HashMap 的键存储 HashSet 中的元素,而值则是一个固定的常量对象(例如,Java 中的
PRESENT,一个私有的、唯一的Object实例),用于表示键(即 HashSet 中的元素)的存在。存储元素(add 方法)
- 当向 HashSet 添加元素时,实际上是将该元素作为键(key)存储到 HashMap 中。HashMap 的值(value)则是一个固定的常量对象(例如,
PRESENT)。判断元素是否存在(contains 方法)
- HashSet 的
contains方法实际上调用的是 HashMap 的containsKey方法,通过计算元素的哈希值并定位到 HashMap 的相应位置,然后遍历链表或红黑树来查找是否存在该元素。
底层数据结构
- ArrayList:基于动态数组实现。它的底层是一个可变大小的数组,当数组容量不足时会自动扩容。
- LinkedList:基于双向链表实现。它的底层是一个由节点(Node)组成的链表,每个节点包含数据部分和指向前后节点的引用。
元素访问速度
- ArrayList:支持随机访问,即可以通过索引直接访问任意位置的元素。时间复杂度为 O(1)。
- LinkedList:不支持随机访问,访问任意位置的元素需要从头节点开始遍历,时间复杂度为 O(n)。
插入和删除操作
- ArrayList:在数组中间插入或删除元素时,需要移动插入点或删除点之后的所有元素,时间复杂度为 O(n)。
- LinkedList:在链表的任意位置插入或删除元素时,只需要修改相邻节点的引用,时间复杂度为 O(1)(找到插入或删除点的时间复杂度除外)。
数组转换为 List
- 使用
Arrays.asList方法:这个方法可以将一个数组转换为一个固定大小的 List。如果你需要一个可以修改的 List,可以进一步将这个固定大小的 List 转换为真正的ArrayList。String[] array = {"a", "b", "c"}; List<String> list = Arrays.asList(array); List<String> arrayList = new ArrayList<>(list);- 使用
Collections.addAll方法:这个方法也可以将数组中的元素添加到指定的 Collection 中,通常用于初始化一个空的ArrayList。String[] array = {"a", "b", "c"}; List<String> arrayList = new ArrayList<>(); Collections.addAll(arrayList, array);List 转换为数组
- 使用
List.toArray方法:这个方法可以将 List 转换为一个 Object 数组。如果你需要特定类型的数组,你可以传入一个类型正确的数组作为参数。List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); String[] stringArray = list.toArray(new String[0]);- 使用流(Streams):在 Java 8 及更高版本中,你可以使用流来将 List 转换为数组。
String[] stringArray = list.stream().toArray(String[]::new);
ArrayList 和 Vector 的主要区别在于线程安全性和性能。在大多数情况下,如果不需要线程安全,ArrayList 是更好的选择。如果需要线程安全,并且性能不是主要考虑因素,那么可以选择 Vector。然而,在需要线程安全的场景中,通常更推荐使用
Collections.synchronizedList()或CopyOnWriteArrayList等更现代的并发集合类。
大小和可变性
- Array:数组的大小在创建时就被固定下来,之后不能改变。
- ArrayList:是一个动态数组,其大小可以随着元素的添加和删除而自动调整。
数据类型
- Array:数组可以存储基本数据类型(如 int、char 等)或对象类型。
- ArrayList:只能存储对象引用,不能直接存储基本数据类型。
泛型支持
- Array:数组本身不支持泛型。
- ArrayList:支持泛型,可以在声明时指定元素的类型,从而在编译时期进行类型检查。
poll():由于它不会抛出异常,因此在使用时不需要额外的异常处理代码。 remove():在使用时应该准备好处理
NoSuchElementException异常,以避免程序因未捕获异常而崩溃。两者的主要区别在于它们处理空队列时的方式。
- Vector:Vector 是 List 接口的实现类之一,与 ArrayList 类似,但它是线程安全的。
- Hashtable:Hashtable 是 Map 接口的实现类之一,与 HashMap 类似,但它是线程安全的。
- Stack:Stack 类继承了 Vector,因此也是线程安全的。
- ConcurrentHashMap:ConcurrentHashMap 是 Map 接口的高效实现类,专门设计用于高并发环境。
- CopyOnWriteArrayList:CopyOnWriteArrayList 是 List 接口的线程安全实现类,适用于读多写少的场景。
- ConcurrentLinkedQueue:ConcurrentLinkedQueue 是 Queue 接口的非阻塞线程安全实现类。
迭代器(Iterator)是一种设计模式,也是一种用于遍历或访问集合(如数组、列表、集合等)中元素的接口或对象。在编程中,迭代器提供了一种统一的方式来访问集合中的每一个元素,而不需要了解集合的内部结构或实现细节。
迭代器通常包含以下几个基本方法:
- hasNext():检查集合中是否还有下一个元素。
- next():返回集合中的下一个元素。
- remove():移除迭代器最近返回的元素。
使用方法
- 通过调用集合的
iterator()方法来获取一个 Iterator 对象。- 使用
hasNext()检查是否有下一个元素,使用next()获取下一个元素。- 如果需要移除元素,调用
remove()方法。特点
- 统一访问方式:适用于所有实现了 Iterable 接口的集合。
- 安全性:避免并发修改异常(ConcurrentModificationException)。
- 单向遍历:只能从头开始逐个访问元素。
- 遍历方向:Iterator 只能单向遍历。ListIterator 是双向遍历集合的。
- 适用集合类型:Iterator 适用于所有集合。ListIterator 专门用于遍历 List 集合。
- 修改元素:Iterator 只能删除。ListIterator 可以修改(
set())、添加(add())和删除。- 元素索引:ListIterator 提供了
nextIndex()和previousIndex()方法。
- 使用 Java 提供的不可变集合方法:如
Collections.unmodifiableList()、List.of()等。- 使用第三方库提供的不可变集合:例如 Guava 库的
ImmutableList。- 防御性复制:传递副本而非原始集合。
- 封装集合:只提供读取方法,不提供修改方法。
并行是指多个任务在同一时刻真正同时执行,这通常依赖于多核处理器或分布式系统来支持。 并发是指系统在同一时间段内处理多个任务的能力,但这些任务并不是真正同时执行,而是通过任务的分时调度机制交替进行。
资源占用:进程拥有独立的内存单元,线程共享所属进程的地址空间和资源。 独立性:进程之间相互独立,线程不能独立执行,必须依存在应用程序中。 并发性:线程间的切换只需要保存和恢复较少的上下文信息,效率相对较高。 系统开销:线程在创建或撤销时,系统的开销相对较小。
守护线程是在程序运行时在后台提供一种通用服务的线程,并且这种线程不属于程序中不可或缺的部分。当所有的非守护线程结束运行时,虚拟机会自动退出,并同时终止所有还在运行的守护线程。
- 继承 Thread 类:重写 run 方法。
- 实现 Runnable 接口:实现 run 方法。
- 实现 Callable 接口与 Future 接口结合使用:可以返回结果。
- 使用线程池:通过 ExecutorService 接口创建。
- 使用匿名内部类:简化代码编写。
- 使用 Lambda 表达式:Java 8 及以上版本。
- 返回值:Runnable 无返回值,Callable 可以返回结果。
- 异常处理:Runnable 无法抛出 checked Exception,Callable 可以。
- 使用方式:Runnable 可通过 Thread 或线程池执行,Callable 通常通过线程池执行。
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
- 所属类和方法类型:sleep() 是 Thread 类静态方法,wait() 是 Object 类实例方法。
- 释放锁的行为:sleep() 不释放锁,wait() 释放锁。
- 唤醒机制:sleep() 自动唤醒,wait() 需 notify() 唤醒。
- 唤醒线程数量:notify() 随机唤醒一个,notifyAll() 唤醒所有。
- 适用场景:notify() 适用于单一消费者,notifyAll() 适用于复杂同步场景。
- 功能与作用:run() 是线程主体,start() 启动线程。
- 线程执行:直接调用 run() 在当前线程执行,start() 在新线程执行。
- 线程状态:start() 改变线程状态,直接调用 run() 不改变。
- 通过 ThreadPoolExecutor 构造函数来创建:推荐方式,高度定制性。
- 通过 Executors 工具类来创建:FixedThreadPool, CachedThreadPool 等。
- 通过 Spring 框架创建线程池。
- 使用第三方库。
RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED。
- 任务提交的类型:execute() 接受 Runnable,submit() 接受 Runnable 或 Callable。
- 返回值:execute() 无返回值,submit() 返回 Future。
- 异常处理:execute() 记录异常,submit() 封装在 Future 中。
- 使用同步机制:synchronized, Lock。
- 使用 volatile 关键字:确保可见性。
- 使用原子类:AtomicInteger 等。
- 使用线程安全的集合类:ConcurrentHashMap 等。
- 使用 ThreadLocal 类:线程隔离。
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。根据竞争情况逐步升级,优化性能。
死锁是指多个线程因竞争资源而造成的一种僵局,它们互相等待对方占用的资源,从而无法继续执行下去。
- 确保锁的顺序:避免循环等待。
- 避免长时间持有锁。
- 使用可重入锁或公平锁。
- 设置锁超时。
- 检测和诊断死锁。
ThreadLocal 是 Java 提供的一个用于线程存储本地变量的类,它为每个线程提供独立的变量副本。使用场景包括 Web 应用上下文、数据库连接绑定、线程池数据隔离等。
涉及对象头、Monitor(监视器)和内存屏障。通过锁升级(偏向锁、轻量级锁、重量级锁)优化性能。
- 功能差异:synchronized 保证互斥、可见性、原子性;volatile 保证可见性、禁止指令重排序,但不保证原子性。
- 实现方式:synchronized 基于 Monitor 锁,volatile 基于内存屏障。
- 类型与实现:synchronized 是关键字,Lock 是接口。
- 加锁机制:synchronized 隐式,Lock 显式。
- 锁的类型与特性:Lock 支持公平锁、中断响应、超时等。
- 使用方式:synchronized 可修饰方法,ReentrantLock 只能代码块。
- 锁的公平性:ReentrantLock 可配置公平锁。
- 响应中断:ReentrantLock 支持 lockInterruptibly()。
核心在于通过 CAS(Compare-And-Swap)机制实现不可分割的原子操作,以确保多线程环境下的数据一致性和完整性。
反射(Reflection)是一种在运行时能够动态地获取类的信息(包括类的属性、方法、构造器等),并且可以操作它们的机制。
Java 序列化是指将对象的状态转换为字节流的过程。需要序列化的情况包括网络通信、对象持久化、分布式计算、RMI 等。
动态代理是一种在运行时动态生成代理类及其实例的机制。应用包括 AOP、事务管理、缓存代理、RPC 等。
- JDK 动态代理:基于接口,使用 Proxy 类和 InvocationHandler。
- CGLIB 动态代理:基于继承,使用 Enhancer 类。
避免共享引用、保持对象不变性、复制复杂对象、原型模式、实现撤销/重做功能。
实现 Cloneable 接口并重写 clone() 方法。注意浅拷贝和深拷贝的区别。
浅拷贝:只复制字段值,引用类型共享。 深拷贝:复制对象及其所有引用的对象。
- throw:用在函数内部,抛出具体的异常对象。
- throws:用在函数声明,声明可能抛出的异常类型。
final 声明不可变,finally 用于异常处理资源释放,finalize 与垃圾回收相关(不推荐使用)。
try 块不能省略。catch 和 finally 可选,但至少需要一个。
会执行。finally 块中的代码无论 try 或 catch 中发生什么都会执行。
RuntimeException(如 NullPointerException, ArithmeticException), Checked Exceptions(如 IOException, SQLException)。
301 永久重定向,302 临时重定向。301 有助于 SEO 权重传递,302 通常不传递。
- 工作原理:forward 服务器内部转发,redirect 客户端重新请求。
- 地址栏显示:forward 不变,redirect 变。
- 数据共享:forward 可共享 request 数据,redirect 不可。
TCP 可靠、面向连接、效率低;UDP 不可靠、无连接、效率高。
确保连接的可靠性,防止失效的连接请求突然传到服务器,避免资源浪费。
发送端使用 Nagle 算法合并小包,或接收端缓冲区读取速度慢于写入速度,导致多个包被合并。
物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
GET 幂等、参数在 URL、不安全、可缓存;POST 非幂等、参数在 Body、更安全、不可缓存。
JSONP, CORS, Nginx 反向代理, Node.js 中间件代理, iframe 方案, WebSocket 等。
利用
<script>标签的跨域特性,前端定义回调函数,服务端返回 JS 代码调用该函数。
单例模式、工厂模式、观察者模式、策略模式、装饰器模式、代理模式、模板方法模式、适配器模式。
- 抽象程度:简单工厂简单,抽象工厂抽象。
- 产品种类:简单工厂少,抽象工厂多。
- 扩展性:简单工厂违反开闭原则,抽象工厂遵循。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Files.size(Path path)Files.getLastModifiedTime(Path path)Files.createTempFile(String prefix, String suffix)