首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >常见 JAVA 集合面试题整理 自用长尾关键词版持续更新

常见 JAVA 集合面试题整理 自用长尾关键词版持续更新

原创
作者头像
啦啦啦191
发布2025-06-18 17:52:14
发布2025-06-18 17:52:14
1010
举报
文章被收录于专栏:Java开发Java开发

我整合了多个技术平台上的相关内容,从常见问题入手,结合应用实例,为你梳理出这篇Java集合面试题总结,希望能助你学习一臂之力。

常见JAVA集合面试题(自用整理,持续更新)

一、ArrayList和LinkedList的区别

1. 底层数据结构

  • ArrayList:基于动态数组实现。在创建ArrayList时,如果没有指定初始容量,会默认创建一个容量为10的数组。当数组中的元素数量超过数组容量时,会进行扩容操作,新的容量一般是原容量的1.5倍。例如:
代码语言:java
复制
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
// 当继续添加元素时,如果当前容量不足,会自动扩容
  • LinkedList:基于双向链表实现。每个节点不仅存储了元素本身,还包含了指向前一个节点和后一个节点的引用。比如:
代码语言:java
复制
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("a");
linkedList.add("b");
// 链表节点之间通过引用相互连接

2. 随机访问性能

  • ArrayList:支持快速随机访问,时间复杂度为O(1)。因为可以通过数组索引直接定位到元素的位置。例如:
代码语言:java
复制
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
int element = list.get(1); // 可以快速获取索引为1的元素,即20
  • LinkedList:随机访问性能较差,时间复杂度为O(n)。因为需要从链表的头节点或尾节点开始,逐个遍历节点,直到找到目标位置的节点。例如:
代码语言:java
复制
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(10);
linkedList.add(20);
linkedList.add(30);
// 获取索引为1的元素,需要从链表头开始遍历
int element = linkedList.get(1); 

3. 插入和删除性能

  • ArrayList:在数组中间位置插入或删除元素时,需要移动大量元素,时间复杂度为O(n)。但在数组尾部插入或删除元素时,时间复杂度为O(1)(不考虑扩容情况)。例如:
代码语言:java
复制
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 在索引为1的位置插入元素4,后续元素都要向后移动
list.add(1, 4); 
  • LinkedList:在链表的任意位置插入或删除元素,只需要修改相邻节点的引用,时间复杂度为O(1)。但如果要先定位到指定位置的节点,时间复杂度则为O(n)。例如:
代码语言:java
复制
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
// 在索引为1的位置插入元素4,只需修改相邻节点引用
linkedList.add(1, 4); 

4. 内存占用

  • ArrayList:内存占用相对较小,主要存储元素本身。
  • LinkedList:每个节点除了存储元素,还需要存储两个引用,内存占用较大。

二、HashMap和HashTable的区别

1. 线程安全性

  • HashMap:非线程安全,在多线程环境下并发访问可能会出现数据不一致问题,甚至引发死循环(在JDK 1.7及之前的版本中,多线程扩容时可能会出现)。例如:
代码语言:java
复制
HashMap<Integer, String> hashMap = new HashMap<>();
// 多线程同时进行put操作,可能会出现问题
new Thread(() -> {
    hashMap.put(1, "a");
}).start();
new Thread(() -> {
    hashMap.put(2, "b");
}).start();
  • HashTable:线程安全,它的大部分方法(如put、get等)都使用了synchronized关键字进行同步。例如:
代码语言:java
复制
Hashtable<Integer, String> hashtable = new Hashtable<>();
// 多线程环境下使用相对安全
new Thread(() -> {
    hashtable.put(1, "a");
}).start();
new Thread(() -> {
    hashtable.put(2, "b");
}).start();

2. Null键和Null值

  • HashMap:允许null键和null值。例如:
代码语言:java
复制
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(null, "nullValue");
hashMap.put(1, null);
  • HashTable:不允许null键和null值,否则会抛出NullPointerException。例如:
代码语言:java
复制
Hashtable<Integer, String> hashtable = new Hashtable<>();
// 以下代码会抛出NullPointerException
// hashtable.put(null, "value"); 
// hashtable.put(1, null); 

3. 迭代器

  • HashMap:迭代器是fail - fast的,即当迭代过程中集合结构被修改(除了通过迭代器自身的remove方法),会立即抛出ConcurrentModificationException。例如:
代码语言:java
复制
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "a");
hashMap.put(2, "b");
Iterator<Map.Entry<Integer, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<Integer, String> entry = iterator.next();
    // 这里如果直接调用hashMap.put(3, "c");会抛出异常
    iterator.remove();
}
  • HashTable:迭代器不是fail - fast的,在迭代过程中集合结构被修改,不会立即抛出异常,但可能导致迭代结果不准确。例如:
代码语言:java
复制
Hashtable<Integer, String> hashtable = new Hashtable<>();
hashtable.put(1, "a");
hashtable.put(2, "b");
Enumeration<Map.Entry<Integer, String>> enumeration = Collections.enumeration(hashtable.entrySet());
while (enumeration.hasMoreElements()) {
    Map.Entry<Integer, String> entry = enumeration.nextElement();
    // 这里如果调用hashtable.put(3, "c");不会立即抛出异常
}

4. 默认容量和扩容机制

  • HashMap:默认初始容量为16,负载因子为0.75。当HashMap中的元素个数超过容量 负载因子(即16 0.75 = 12)时,会进行扩容,新容量为原容量的2倍。例如:
代码语言:java
复制
HashMap<Integer, String> hashMap = new HashMap<>();
for (int i = 0; i < 13; i++) {
    hashMap.put(i, "value" + i);
    // 当添加到第13个元素时,会触发扩容
}
  • HashTable:默认初始容量为11,负载因子为0.75。当HashTable中的元素个数超过容量 负载因子(即11 0.75,取整后为8)时,会进行扩容,新容量为原容量的2倍 + 1(即11 * 2 + 1 = 23)。例如:
代码语言:java
复制
Hashtable<Integer, String> hashtable = new Hashtable<>();
for (int i = 0; i < 9; i++) {
    hashtable.put(i, "value" + i);
    // 当添加到第9个元素时,会触发扩容
}

三、HashSet和TreeSet的区别

1. 底层数据结构

  • HashSet:基于HashMap实现,内部使用HashMap来存储元素,HashSet的元素实际上是作为HashMap的键来存储的,值是一个固定的Object对象。例如:
代码语言:java
复制
HashSet<Integer> hashSet = new HashSet<>();
hashSet.add(1);
hashSet.add(2);
// 内部通过HashMap存储,键为1和2,值为固定对象
  • TreeSet:基于红黑树实现,红黑树是一种自平衡的二叉搜索树,能保证元素的有序性。例如:
代码语言:java
复制
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
// 内部通过红黑树存储,元素会按照从小到大的顺序排列

2. 元素顺序

  • HashSet:元素无序,元素的插入顺序和存储顺序不一定相同。例如:
代码语言:java
复制
HashSet<Integer> hashSet = new HashSet<>();
hashSet.add(3);
hashSet.add(1);
hashSet.add(2);
// 遍历hashSet时,元素顺序可能不是3, 1, 2
for (int i : hashSet) {
    System.out.print(i + " ");
}
  • TreeSet:元素有序,可以按照自然顺序(如数字从小到大、字符串按字典序)或通过实现Comparator接口来定义自定义顺序。例如:
代码语言:java
复制
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
// 遍历treeSet时,元素顺序是1, 2, 3
for (int i : treeSet) {
    System.out.print(i + " ");
}

3. 添加和查询性能

  • HashSet:添加和查询操作平均时间复杂度为O(1),因为使用哈希表进行快速查找。例如:
代码语言:java
复制
HashSet<Integer> hashSet = new HashSet<>();
hashSet.add(1);
boolean contains = hashSet.contains(1); // 快速判断是否包含元素1,时间复杂度O(1)
  • TreeSet:添加和查询操作时间复杂度为O(log n),因为红黑树的高度是对数级别的。例如:
代码语言:java
复制
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(1);
boolean contains = treeSet.contains(1); // 判断是否包含元素1,时间复杂度O(log n)

4. 唯一性保证

  • HashSet:通过hashCode()和equals()方法来保证元素的唯一性。当向HashSet中添加元素时,首先会计算元素的hashCode值,根据hashCode值确定元素在哈希表中的存储位置。如果该位置已经有元素存在,再通过equals()方法比较两个元素是否相等,如果相等则不添加。例如:
代码语言:java
复制
class CustomObject {
    private int id;
    public CustomObject(int id) {
        this.id = id;
    }
    @Override
    public int hashCode() {
        return id;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        CustomObject other = (CustomObject) obj;
        return id == other.id;
    }
}
HashSet<CustomObject> hashSet = new HashSet<>();
hashSet.add(new CustomObject(1));
hashSet.add(new CustomObject(1)); // 由于hashCode和equals方法判断为相同元素,不会重复添加
  • TreeSet:通过比较元素的顺序来保证唯一性,如果两个元素比较相等(通过自然顺序或自定义比较器判断),则不会同时存在于TreeSet中。例如:
代码语言:java
复制
class CustomComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
}
TreeSet<Integer> treeSet = new TreeSet<>(new CustomComparator());
treeSet.add(1);
treeSet.add(1); // 由于比较器判断为相同元素,不会重复添加

四、ConcurrentHashMap的实现原理

1. JDK 1.7版本

  • 分段锁机制:ConcurrentHashMap将数据分成多个段(Segment),每个段相当于一个小的HashTable,都有自己独立的锁。在JDK 1.7中,默认有16个Segment。当一个线程对某个Segment进行写操作时,只会锁住这个Segment,其他线程仍然可以访问其他Segment,从而提高并发性能。例如:
代码语言:java
复制
ConcurrentHashMap<Integer, String> concurrentHashMap = new ConcurrentHashMap<>();
// 多个线程可以同时对不同的Segment进行操作
new Thread(() -> {
    concurrentHashMap.put(1, "a");
}).start();
new Thread(() -> {
    concurrentHashMap.put(17, "b"); // 17和1可能位于不同的Segment,可并发操作
}).start();
  • 数据结构:每个Segment内部是一个数组 + 链表的结构,和HashMap类似。当发生哈希冲突时,通过链表来解决。例如:
代码语言:java
复制
// 假设某个Segment的数组长度为4
Segment<K, V> segment = new Segment<>(16, 0.75f);
// 当有元素put进来时,如果哈希冲突,会在链表上进行存储

2. JDK 1.8版本

  • CAS + Synchronized:摒弃了分段锁机制,采用CAS操作和Synchronized关键字相结合的方式。在JDK 1.8中,当多个线程进行读操作时,不需要加锁,可以直接并发进行。当进行写操作(如put)时,首先会尝试使用CAS操作来插入元素,如果CAS操作失败,再使用Synchronized关键字对当前节点进行加锁。例如:
代码语言:java
复制
ConcurrentHashMap<Integer, String> concurrentHashMap = new ConcurrentHashMap<>();
// 多个线程读操作可并发
new Thread(() -> {
    String value = concurrentHashMap.get(1);
}).start();
new Thread(() -> {
    String value = concurrentHashMap.get(2);
}).start();
// 写操作先尝试CAS,失败后加锁
new Thread(() -> {
    concurrentHashMap.put(3, "c");
}).start();
  • 数据结构:和HashMap类似,采用数组 + 链表 + 红黑树的结构。当链表长度超过8时,会将链表转换为红黑树,以提高查找效率。例如:
代码语言:java
复制
ConcurrentHashMap<Integer, String> concurrentHashMap = new ConcurrentHashMap<>();
// 当某个桶中的链表长度超过8时,会转换为红黑树
for (int i = 0; i < 10; i++) {
    concurrentHashMap.put(i, "value" + i);
    // 假设这些元素哈希冲突,都在同一个桶中,当添加到第9个元素时,链表可能转换为红黑树
}

五、如何遍历HashMap和HashTable

1. 遍历HashMap

(1)使用entrySet()遍历键值对
代码语言:java
复制
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "a");
hashMap.put(2, "b");
for (Map.Entry<Integer, String> entry : hashMap.entrySet()) {
    Integer key = entry.getKey();
    String value = entry.getValue();
    System.out.println("Key: " + key + ", Value: " + value);
}
(2)使用keySet()遍历键,再通过键获取值
代码语言:java
复制
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "a");
hashMap.put(2, "b");
for (Integer key : hashMap.keySet()) {
    String value = hashMap.get(key);
    System.out.println("Key: " + key + ", Value: " + value);
}
(3)使用values()遍历值(只能获取值,无法获取键)
代码语言:java
复制
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "a");
hashMap.put(2, "b");
for (String value : hashMap.values()) {
    System.out.println("Value: " + value);
}
(4)使用迭代器遍历
代码语言:java
复制
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "a");
hashMap.put(2, "b");
Iterator<Map.Entry<Integer, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<Integer, String> entry = iterator.next();
    Integer key = entry.getKey();
    String value = entry.getValue();
    System.out.println("Key: " + key + ", Value: " + value);
}

2. 遍历HashTable

(1)使用entrySet()遍历键值对
代码语言:java
复制
Hashtable<Integer, String> hashtable = new Hashtable<>();
hashtable.put(1, "a");
hashtable.put(2, "b");
for (Map.Entry<Integer, String> entry : hashtable.entrySet()) {
    Integer key = entry.getKey();
    String value = entry.getValue();
    System.out.println("Key: " + key + ", Value: " + value);
}
(2)使用keySet()遍历键,再通过键获取值
代码语言:java
复制
Hashtable<Integer, String> hashtable = new Hashtable<>();
hashtable.put(1, "a");
hashtable.put(2, "b");
for (Integer key : hashtable.keySet()) {
    String value = hashtable.get(key);
    System.out.println("Key: " + key + ", Value: " + value);
}

Java 集合面试题,Java 集合常见问题,ArrayList 面试题,LinkedList 面试题,HashMap 面试题,Hash

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 常见JAVA集合面试题(自用整理,持续更新)
    • 一、ArrayList和LinkedList的区别
      • 1. 底层数据结构
      • 2. 随机访问性能
      • 3. 插入和删除性能
      • 4. 内存占用
    • 二、HashMap和HashTable的区别
      • 1. 线程安全性
      • 2. Null键和Null值
      • 3. 迭代器
      • 4. 默认容量和扩容机制
    • 三、HashSet和TreeSet的区别
      • 1. 底层数据结构
      • 2. 元素顺序
      • 3. 添加和查询性能
      • 4. 唯一性保证
    • 四、ConcurrentHashMap的实现原理
      • 1. JDK 1.7版本
      • 2. JDK 1.8版本
    • 五、如何遍历HashMap和HashTable
      • 1. 遍历HashMap
      • 2. 遍历HashTable
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档