Java集合框架是Java编程的基石,掌握集合的使用和原理是每个Java开发者的必备技能。本文将深入剖析Java集合框架的核心接口和常用实现类,助你快速掌握集合的使用技巧。
一、集合框架概述
1.1 什么是集合?
关键点: 集合是用于存储和操作一组对象的容器,提供了比数组更灵活的数据结构。
数组 vs 集合:
java
// ❌ 数组的局限性
public class ArrayDemo {
public static void main(String[] args) {
// 1. 长度固定,不能动态扩展
String[] names = new String[3];
names[0] = "张三";
names[1] = "李四";
names[2] = "王五";
// names[3] = "赵六"; // 数组越界!
// 2. 只能存储同一类型
// String[] mixed = {"张三", 123}; // 编译错误
// 3. 没有提供便捷的操作方法
// 查找元素需要手动遍历
boolean found = false;
for (String name : names) {
if ("李四".equals(name)) {
found = true;
break;
}
}
}
}
// ✅ 集合的优势
public class CollectionDemo {
public static void main(String[] args) {
// 1. 长度可变,自动扩容
List<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
names.add("王五");
names.add("赵六"); // 自动扩容
// 2. 提供丰富的操作方法
boolean contains = names.contains("李四"); // 直接查找
names.remove("王五"); // 直接删除
int size = names.size(); // 获取大小
// 3. 支持泛型,类型安全
List<Integer> numbers = new ArrayList<>();
numbers.add(100);
// numbers.add("abc"); // 编译错误,类型不匹配
}
}
1.2 集合框架体系
Collection (接口)
├── List (接口) - 有序、可重复
│ ├── ArrayList - 动态数组
│ ├── LinkedList - 双向链表
│ └── Vector - 线程安全的动态数组
│
├── Set (接口) - 无序、不可重复
│ ├── HashSet - 基于HashMap
│ ├── LinkedHashSet - 保持插入顺序
│ └── TreeSet - 有序集合(红黑树)
│
└── Queue (接口) - 队列
├── PriorityQueue - 优先队列
└── Deque - 双端队列
└── ArrayDeque
Map (接口) - 键值对
├── HashMap - 哈希表
├── LinkedHashMap - 保持插入顺序
├── TreeMap - 有序Map(红黑树)
└── Hashtable - 线程安全(已过时)
二、List接口详解
2.1 ArrayList - 动态数组
关键点: ArrayList基于数组实现,查询快,增删慢。
java
public class ArrayListDemo {
public static void main(String[] args) {
// 创建ArrayList
List<String> list = new ArrayList<>();
// 添加元素
list.add("Java");
list.add("Python");
list.add("C++");
list.add(1, "JavaScript"); // 在指定位置插入
System.out.println(list); // [Java, JavaScript, Python, C++]
// 获取元素
String first = list.get(0); // Java
// 修改元素
list.set(1, "TypeScript");
// 删除元素
list.remove(2); // 删除索引2的元素
list.remove("C++"); // 删除指定元素
// 查找元素
int index = list.indexOf("Java"); // 0
boolean contains = list.contains("Python"); // false
// 遍历
for (String lang : list) {
System.out.println(lang);
}
// 使用Lambda表达式遍历
list.forEach(lang -> System.out.println(lang));
// 使用Stream API
list.stream()
.filter(lang -> lang.startsWith("J"))
.forEach(System.out::println);
}
}
ArrayList特点:
- ✅ 查询快:O(1)时间复杂度
- ❌ 插入删除慢:需要移动元素
- ✅ 支持随机访问
- ❌ 非线程安全
2.2 LinkedList - 双向链表
关键点: LinkedList基于链表实现,增删快,查询慢。
java
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
// 添加元素
list.add("A");
list.add("B");
list.add("C");
// 头部操作
list.addFirst("头部");
list.addLast("尾部");
// 获取元素
String first = list.getFirst(); // 头部
String last = list.getLast(); // 尾部
// 删除元素
list.removeFirst();
list.removeLast();
// 作为队列使用
list.offer("D"); // 入队
String element = list.poll(); // 出队
// 作为栈使用
list.push("E"); // 入栈
String top = list.pop(); // 出栈
System.out.println(list);
}
}
ArrayList vs LinkedList:
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 查询速度 | 快 O(1) | 慢 O(n) |
| 插入删除 | 慢 O(n) | 快 O(1) |
| 内存占用 | 连续内存 | 不连续,额外指针 |
| 使用场景 | 查询多 | 增删多 |
三、Set接口详解
3.1 HashSet - 无序不重复
关键点: HashSet基于HashMap实现,元素无序且不重复。
java
public class HashSetDemo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
// 添加元素
set.add("Apple");
set.add("Banana");
set.add("Orange");
set.add("Apple"); // 重复元素不会添加
System.out.println(set); // [Apple, Orange, Banana] 顺序不确定
System.out.println("大小:" + set.size()); // 3
// 判断是否包含
boolean contains = set.contains("Apple"); // true
// 删除元素
set.remove("Banana");
// 遍历
for (String fruit : set) {
System.out.println(fruit);
}
// 集合运算
Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
// 并集
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("并集:" + union); // [1, 2, 3, 4, 5, 6, 7, 8]
// 交集
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("交集:" + intersection); // [4, 5]
// 差集
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("差集:" + difference); // [1, 2, 3]
}
}
3.2 TreeSet - 有序集合
关键点: TreeSet基于红黑树实现,元素自动排序。
java
public class TreeSetDemo {
public static void main(String[] args) {
// 自然排序
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
System.out.println(numbers); // [1, 2, 5, 8] 自动排序
// 获取最小值和最大值
System.out.println("最小值:" + numbers.first()); // 1
System.out.println("最大值:" + numbers.last()); // 8
// 范围查询
System.out.println("小于5的元素:" + numbers.headSet(5)); // [1, 2]
System.out.println("大于等于5的元素:" + numbers.tailSet(5)); // [5, 8]
System.out.println("2到8之间:" + numbers.subSet(2, 8)); // [2, 5]
// 自定义排序
TreeSet<String> names = new TreeSet<>((a, b) -> b.compareTo(a)); // 降序
names.add("Alice");
names.add("Bob");
names.add("Charlie");
System.out.println(names); // [Charlie, Bob, Alice]
}
}
// 自定义对象排序
class Student implements Comparable<Student> {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public int compareTo(Student other) {
// 按分数降序排列
return other.score - this.score;
}
@Override
public String toString() {
return name + ":" + score;
}
}
public class CustomSortDemo {
public static void main(String[] args) {
TreeSet<Student> students = new TreeSet<>();
students.add(new Student("张三", 85));
students.add(new Student("李四", 92));
students.add(new Student("王五", 78));
System.out.println(students); // [李四:92, 张三:85, 王五:78]
}
}
四、Map接口详解
4.1 HashMap - 键值对存储
关键点: HashMap基于哈希表实现,键不能重复,值可以重复。
java
public class HashMapDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// 添加键值对
map.put("张三", 85);
map.put("李四", 92);
map.put("王五", 78);
map.put("张三", 90); // 键重复,会覆盖旧值
System.out.println(map); // {李四=92, 张三=90, 王五=78}
// 获取值
Integer score = map.get("张三"); // 90
Integer defaultScore = map.getOrDefault("赵六", 0); // 0
// 判断是否包含
boolean hasKey = map.containsKey("李四"); // true
boolean hasValue = map.containsValue(92); // true
// 删除
map.remove("王五");
// 遍历方式1:遍历键
for (String name : map.keySet()) {
System.out.println(name + ": " + map.get(name));
}
// 遍历方式2:遍历值
for (Integer value : map.values()) {
System.out.println(value);
}
// 遍历方式3:遍历键值对(推荐)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 遍历方式4:Lambda表达式(推荐)
map.forEach((name, score1) -> {
System.out.println(name + ": " + score1);
});
// Java 8新方法
map.putIfAbsent("赵六", 88); // 键不存在时才添加
map.computeIfAbsent("孙七", k -> 95); // 键不存在时计算并添加
map.merge("张三", 5, Integer::sum); // 合并值
}
}
4.2 HashMap原理解析
java
/**
* HashMap底层原理:
* 1. 数组 + 链表 + 红黑树(JDK 1.8+)
* 2. 默认容量16,负载因子0.75
* 3. 当链表长度>8且数组长度>=64时,转为红黑树
*/
public class HashMapPrincipleDemo {
public static void main(String[] args) {
// 指定初始容量和负载因子
Map<String, String> map = new HashMap<>(32, 0.8f);
// 哈希冲突演示
Map<Integer, String> conflictMap = new HashMap<>();
// 这些键的hashCode可能产生冲突
for (int i = 0; i < 20; i++) {
conflictMap.put(i, "Value" + i);
}
System.out.println("大小:" + conflictMap.size());
}
}
// 自定义对象作为Key
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 必须重写hashCode和equals
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
public class CustomKeyDemo {
public static void main(String[] args) {
Map<Person, String> map = new HashMap<>();
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25); // 内容相同
map.put(p1, "员工1");
map.put(p2, "员工2"); // 会覆盖p1,因为equals返回true
System.out.println(map.size()); // 1
System.out.println(map.get(p1)); // 员工2
}
}
4.3 TreeMap - 有序Map
关键点: TreeMap基于红黑树实现,键自动排序。
java
public class TreeMapDemo {
public static void main(String[] args) {
// 自然排序
TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "C");
map.put(1, "A");
map.put(2, "B");
System.out.println(map); // {1=A, 2=B, 3=C} 按键排序
// 获取第一个和最后一个
System.out.println("第一个:" + map.firstEntry()); // 1=A
System.out.println("最后一个:" + map.lastEntry()); // 3=C
// 范围查询
System.out.println("小于3的:" + map.headMap(3)); // {1=A, 2=B}
System.out.println("大于等于2的:" + map.tailMap(2)); // {2=B, 3=C}
// 自定义排序
TreeMap<String, Integer> scoreMap = new TreeMap<>((a, b) -> b.compareTo(a));
scoreMap.put("张三", 85);
scoreMap.put("李四", 92);
scoreMap.put("王五", 78);
System.out.println(scoreMap); // {王五=78, 李四=92, 张三=85} 键降序
}
}
五、实战案例
5.1 统计单词频率
java
public class WordFrequency {
public static void main(String[] args) {
String text = "Java is great and Java is powerful Java is everywhere";
// 分割单词
String[] words = text.toLowerCase().split("\\s+");
// 使用HashMap统计频率
Map<String, Integer> frequency = new HashMap<>();
for (String word : words) {
frequency.put(word, frequency.getOrDefault(word, 0) + 1);
}
// 按频率排序
List<Map.Entry<String, Integer>> list = new ArrayList<>(frequency.entrySet());
list.sort((a, b) -> b.getValue() - a.getValue());
// 输出结果
System.out.println("单词频率统计:");
for (Map.Entry<String, Integer> entry : list) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
5.2 学生成绩管理系统
java
class Student {
private String id;
private String name;
private Map<String, Integer> scores; // 科目-成绩
public Student(String id, String name) {
this.id = id;
this.name = name;
this.scores = new HashMap<>();
}
public void addScore(String subject, int score) {
scores.put(subject, score);
}
public double getAverage() {
if (scores.isEmpty()) return 0;
return scores.values().stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
}
public void displayInfo() {
System.out.println("学号:" + id + ",姓名:" + name);
System.out.println("成绩:" + scores);
System.out.println("平均分:" + String.format("%.2f", getAverage()));
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public Map<String, Integer> getScores() { return scores; }
}
class ScoreManager {
private Map<String, Student> students; // 学号-学生
public ScoreManager() {
this.students = new HashMap<>();
}
// 添加学生
public void addStudent(Student student) {
students.put(student.getId(), student);
System.out.println("添加学生成功:" + student.getName());
}
// 录入成绩
public void addScore(String studentId, String subject, int score) {
Student student = students.get(studentId);
if (student != null) {
student.addScore(subject, score);
System.out.println("录入成绩成功");
} else {
System.out.println("学生不存在");
}
}
// 查询学生信息
public void queryStudent(String studentId) {
Student student = students.get(studentId);
if (student != null) {
student.displayInfo();
} else {
System.out.println("学生不存在");
}
}
// 统计某科目平均分
public double getSubjectAverage(String subject) {
return students.values().stream()
.filter(s -> s.getScores().containsKey(subject))
.mapToInt(s -> s.getScores().get(subject))
.average()
.orElse(0.0);
}
// 获取成绩排名
public List<Student> getRanking() {
List<Student> ranking = new ArrayList<>(students.values());
ranking.sort((a, b) -> Double.compare(b.getAverage(), a.getAverage()));
return ranking;
}
// 显示所有学生
public void displayAllStudents() {
System.out.println("\n===== 所有学生 =====");
students.values().forEach(Student::displayInfo);
}
}
public class ScoreManagementSystem {
public static void main(String[] args) {
ScoreManager manager = new ScoreManager();
// 添加学生
manager.addStudent(new Student("2021001", "张三"));
manager.addStudent(new Student("2021002", "李四"));
manager.addStudent(new Student("2021003", "王五"));
// 录入成绩
manager.addScore("2021001", "数学", 85);
manager.addScore("2021001", "英语", 90);
manager.addScore("2021001", "语文", 88);
manager.addScore("2021002", "数学", 92);
manager.addScore("2021002", "英语", 87);
manager.addScore("2021002", "语文", 95);
manager.addScore("2021003", "数学", 78);
manager.addScore("2021003", "英语", 82);
manager.addScore("2021003", "语文", 80);
// 查询学生
manager.queryStudent("2021001");
// 统计科目平均分
System.out.println("\n数学平均分:" +
String.format("%.2f", manager.getSubjectAverage("数学")));
// 显示排名
System.out.println("\n===== 成绩排名 =====");
List<Student> ranking = manager.getRanking();
for (int i = 0; i < ranking.size(); i++) {
Student s = ranking.get(i);
System.out.println("第" + (i + 1) + "名:" + s.getName() +
" - 平均分:" + String.format("%.2f", s.getAverage()));
}
}
}
5.3 去重和排序
java
public class DeduplicationAndSort {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 2, 9, 1, 5, 3);
// 方法1:使用HashSet去重
Set<Integer> uniqueSet = new HashSet<>(numbers);
List<Integer> result1 = new ArrayList<>(uniqueSet);
Collections.sort(result1);
System.out.println("方法1:" + result1);
// 方法2:使用TreeSet去重并排序
Set<Integer> sortedSet = new TreeSet<>(numbers);
System.out.println("方法2:" + sortedSet);
// 方法3:使用Stream API
List<Integer> result3 = numbers.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("方法3:" + result3);
}
}
六、集合选择指南
6.1 如何选择合适的集合?
选择List:
- 需要保持元素顺序
- 允许重复元素
- 需要通过索引访问
选择Set:
- 不允许重复元素
- 不关心元素顺序(HashSet)
- 需要自动排序(TreeSet)
选择Map:
- 需要键值对存储
- 通过键快速查找值
- 需要统计、分组等操作
6.2 性能对比
| 操作 | ArrayList | LinkedList | HashSet | TreeSet | HashMap | TreeMap |
|---|---|---|---|---|---|---|
| 添加 | O(1) | O(1) | O(1) | O(log n) | O(1) | O(log n) |
| 删除 | O(n) | O(1) | O(1) | O(log n) | O(1) | O(log n) |
| 查找 | O(1) | O(n) | O(1) | O(log n) | O(1) | O(log n) |
| 遍历 | 快 | 慢 | 快 | 快 | 快 | 快 |
七、常见面试题
7.1 ArrayList和LinkedList的区别?
java
// ArrayList:基于数组,查询快,增删慢
// LinkedList:基于链表,增删快,查询慢
// 场景1:频繁查询
List<String> list1 = new ArrayList<>(); // 推荐
for (int i = 0; i < 10000; i++) {
list1.add("Item" + i);
}
String item = list1.get(5000); // O(1)
// 场景2:频繁插入删除
List<String> list2 = new LinkedList<>(); // 推荐
for (int i = 0; i < 10000; i++) {
list2.add(0, "Item" + i); // 头部插入,O(1)
}
7.2 HashMap和Hashtable的区别?
java
// HashMap:非线程安全,允许null键和null值,性能更好
Map<String, String> hashMap = new HashMap<>();
hashMap.put(null, "value"); // 允许
hashMap.put("key", null); // 允许
// Hashtable:线程安全,不允许null键和null值,性能较差(已过时)
Map<String, String> hashtable = new Hashtable<>();
// hashtable.put(null, "value"); // 抛出NullPointerException
// 推荐使用ConcurrentHashMap替代Hashtable
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
7.3 如何实现线程安全的集合?
java
// 方法1:使用Collections工具类
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
// 方法2:使用并发集合(推荐)
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
Set<String> concurrentSet = ConcurrentHashMap.newKeySet();
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
八、总结
Java集合框架的核心要点:
- List - 有序可重复,ArrayList查询快,LinkedList增删快
- Set - 无序不重复,HashSet性能好,TreeSet自动排序
- Map - 键值对存储,HashMap性能好,TreeMap自动排序
- 选择原则 - 根据业务场景选择合适的集合
最佳实践:
- 优先使用接口类型声明变量
- 合理设置初始容量,减少扩容
- 自定义对象作为Key时重写hashCode和equals
- 使用Stream API简化集合操作
相关资源
- Java官方文档
- 《Java核心技术》
- 《Effective Java》
💡 小贴士: 集合框架是Java的基础,多练习才能熟练掌握!
关注我,获取更多Java干货! ☕