Java集合框架详解
Java集合框架详解
什么是集合框架?
集合框架是Java提供的一组用于存储和操作对象的接口和类,它提供了统一的方式来管理和操作数据集合。
集合框架的层次结构
Java集合框架主要包含以下接口:
1. Collection接口
Collection是所有集合的根接口,它定义了集合的基本操作。
- List:有序集合,允许重复元素
- Set:无序集合,不允许重复元素
- Queue:队列,先进先出
2. Map接口
Map接口用于存储键值对映射,它不直接继承自Collection接口。
- HashMap:基于哈希表实现,无序
- LinkedHashMap:基于哈希表和链表实现,有序(插入顺序)
- TreeMap:基于红黑树实现,有序(自然顺序或比较器顺序)
- Hashtable:线程安全的HashMap,效率较低
List接口
List接口是有序集合,允许重复元素,它提供了根据索引访问元素的方法。
List的实现类
1. ArrayList
- 底层实现:基于动态数组
- 特点:查询快,增删慢
- 适用场景:查询操作频繁的场景
2. LinkedList
- 底层实现:基于双向链表
- 特点:查询慢,增删快
- 适用场景:增删操作频繁的场景
3. Vector
- 底层实现:基于动态数组
- 特点:线程安全,效率较低
- 适用场景:多线程环境
List的常用方法
List<String> list = new ArrayList<>();
// 添加元素list.add("元素1"); // 添加到末尾list.add(1, "元素2"); // 添加到指定位置
// 获取元素String element = list.get(0);
// 修改元素list.set(0, "新元素1");
// 删除元素list.remove(0); // 根据索引删除list.remove("元素2"); // 根据元素删除
// 查找元素int index = list.indexOf("元素1"); // 查找元素的索引boolean contains = list.contains("元素1"); // 检查元素是否存在
// 其他方法int size = list.size(); // 获取集合大小boolean isEmpty = list.isEmpty(); // 检查集合是否为空list.clear(); // 清空集合Object[] array = list.toArray(); // 转换为数组List的遍历方式
1. 普通for循环
for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i));}2. 增强型for循环
for (String element : list) { System.out.println(element);}3. 迭代器
Iterator<String> iterator = list.iterator();while (iterator.hasNext()) { String element = iterator.next(); System.out.println(element);}4. ListIterator
ListIterator<String> listIterator = list.listIterator();while (listIterator.hasNext()) { String element = listIterator.next(); System.out.println(element);}
// 反向遍历while (listIterator.hasPrevious()) { String element = listIterator.previous(); System.out.println(element);}Set接口
Set接口是无序集合,不允许重复元素,它继承自Collection接口。
Set的实现类
1. HashSet
- 底层实现:基于哈希表
- 特点:无序,不允许重复元素,查询速度快
- 适用场景:需要快速查找且不关心顺序的场景
2. LinkedHashSet
- 底层实现:基于哈希表和链表
- 特点:有序(插入顺序),不允许重复元素
- 适用场景:需要保持插入顺序的场景
3. TreeSet
- 底层实现:基于红黑树
- 特点:有序(自然顺序或比较器顺序),不允许重复元素
- 适用场景:需要排序的场景
Set的常用方法
Set<String> set = new HashSet<>();
// 添加元素set.add("元素1");set.add("元素2");set.add("元素1"); // 重复元素,不会添加
// 删除元素set.remove("元素1");
// 查找元素boolean contains = set.contains("元素1");
// 其他方法int size = set.size();boolean isEmpty = set.isEmpty();set.clear();Object[] array = set.toArray();Set的遍历方式
1. 增强型for循环
for (String element : set) { System.out.println(element);}2. 迭代器
Iterator<String> iterator = set.iterator();while (iterator.hasNext()) { String element = iterator.next(); System.out.println(element);}Map接口
Map接口用于存储键值对映射,它提供了根据键访问值的方法。
Map的实现类
1. HashMap
- 底层实现:基于哈希表
- 特点:无序,键不允许重复
- 适用场景:需要快速查找的场景
2. LinkedHashMap
- 底层实现:基于哈希表和链表
- 特点:有序(插入顺序),键不允许重复
- 适用场景:需要保持插入顺序的场景
3. TreeMap
- 底层实现:基于红黑树
- 特点:有序(自然顺序或比较器顺序),键不允许重复
- 适用场景:需要排序的场景
4. Hashtable
- 底层实现:基于哈希表
- 特点:线程安全,效率较低,键不允许重复
- 适用场景:多线程环境
Map的常用方法
Map<String, Integer> map = new HashMap<>();
// 添加元素map.put("键1", 1);map.put("键2", 2);
// 获取元素Integer value = map.get("键1");
// 修改元素map.put("键1", 10); // 覆盖原有值
// 删除元素map.remove("键1");
// 查找元素boolean containsKey = map.containsKey("键1"); // 检查键是否存在boolean containsValue = map.containsValue(1); // 检查值是否存在
// 其他方法int size = map.size();boolean isEmpty = map.isEmpty();map.clear();
// 获取所有键Set<String> keys = map.keySet();
// 获取所有值Collection<Integer> values = map.values();
// 获取所有键值对Set<Map.Entry<String, Integer>> entries = map.entrySet();Map的遍历方式
1. 遍历键
for (String key : map.keySet()) { Integer value = map.get(key); System.out.println(key + ": " + value);}2. 遍历键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + ": " + value);}3. 遍历值
for (Integer value : map.values()) { System.out.println(value);}队列和栈
Queue接口
Queue接口是队列,它提供了先进先出(FIFO)的操作方式。
Queue的实现类
- LinkedList:实现了Queue接口
- PriorityQueue:优先级队列,基于堆实现
Queue的常用方法
Queue<String> queue = new LinkedList<>();
// 添加元素queue.offer("元素1"); // 添加失败返回falsequeue.add("元素2"); // 添加失败抛出异常
// 获取并移除队首元素String element = queue.poll(); // 队列为空返回nullString element = queue.remove(); // 队列为空抛出异常
// 获取队首元素但不移除String element = queue.peek(); // 队列为空返回nullString element = queue.element(); // 队列为空抛出异常Stack类
Stack类是栈,它提供了后进先出(LIFO)的操作方式。
Stack的常用方法
Stack<String> stack = new Stack<>();
// 压栈stack.push("元素1");stack.push("元素2");
// 出栈String element = stack.pop(); // 栈为空抛出异常
// 查看栈顶元素String element = stack.peek(); // 栈为空抛出异常
// 检查栈是否为空boolean isEmpty = stack.isEmpty();
// 获取栈大小int size = stack.size();并发集合
Java提供了线程安全的并发集合,它们位于java.util.concurrent包中。
并发集合的实现类
1. ConcurrentHashMap
- 特点:线程安全的HashMap,支持高并发读写
- 适用场景:多线程环境下的哈希表操作
2. CopyOnWriteArrayList
- 特点:线程安全的ArrayList,适用于读多写少的场景
- 适用场景:多线程环境下的读操作频繁的场景
3. CopyOnWriteArraySet
- 特点:基于CopyOnWriteArrayList实现,线程安全的Set
- 适用场景:多线程环境下的读操作频繁的场景
4. ConcurrentLinkedQueue
- 特点:线程安全的无界队列
- 适用场景:多线程环境下的队列操作
5. BlockingQueue
- 特点:阻塞队列,支持线程安全的入队和出队操作
- 实现类:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue等
- 适用场景:多线程环境下的生产者-消费者模式
集合工具类
Collections类
Collections类提供了操作集合的静态方法。
Collections的常用方法
// 排序Collections.sort(list); // 自然排序Collections.sort(list, comparator); // 自定义排序
// 查找int index = Collections.binarySearch(list, key); // 二分查找Object max = Collections.max(collection); // 最大值Object min = Collections.min(collection); // 最小值
// 填充Collections.fill(list, value); // 填充所有元素
// 拷贝Collections.copy(dest, src); // 拷贝集合
// 反转Collections.reverse(list); // 反转集合
// 随机打乱Collections.shuffle(list); // 随机打乱集合
// 替换Collections.replaceAll(list, oldValue, newValue); // 替换所有元素
// 线程安全List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());Set<String> synchronizedSet = Collections.synchronizedSet(new HashSet<>());Map<String, Integer> synchronizedMap = Collections.synchronizedMap(new HashMap<>());Arrays类
Arrays类提供了操作数组的静态方法,它也可以用于数组和集合的转换。
Arrays的常用方法
// 数组转集合List<String> list = Arrays.asList(array);
// 集合转数组String[] array = list.toArray(new String[0]);
// 排序Arrays.sort(array); // 自然排序Arrays.sort(array, comparator); // 自定义排序
// 查找int index = Arrays.binarySearch(array, key); // 二分查找
// 填充Arrays.fill(array, value); // 填充所有元素
// 比较boolean equals = Arrays.equals(array1, array2); // 比较两个数组
// 哈希码int hashCode = Arrays.hashCode(array); // 获取数组的哈希码
// 字符串表示String str = Arrays.toString(array); // 获取数组的字符串表示集合的最佳实践
1. 选择合适的集合
-
需要有序、可重复的元素:使用List
- 查询频繁:ArrayList
- 增删频繁:LinkedList
- 多线程环境:Vector
-
需要无序、不可重复的元素:使用Set
- 不需要排序:HashSet
- 需要保持插入顺序:LinkedHashSet
- 需要排序:TreeSet
-
需要键值对映射:使用Map
- 不需要排序:HashMap
- 需要保持插入顺序:LinkedHashMap
- 需要排序:TreeMap
- 多线程环境:Hashtable或ConcurrentHashMap
2. 初始化时指定容量
对于已知大小的集合,初始化时指定容量可以提高性能。
// 好的做法List<String> list = new ArrayList<>(100); // 指定初始容量Map<String, Integer> map = new HashMap<>(100); // 指定初始容量3. 使用泛型
使用泛型可以避免类型转换错误,提高代码安全性。
// 好的做法List<String> list = new ArrayList<>();String element = list.get(0); // 不需要类型转换
// 坏的做法List list = new ArrayList();String element = (String) list.get(0); // 需要类型转换,可能出错4. 优先使用增强型for循环
遍历集合时,优先使用增强型for循环,代码更简洁。
// 好的做法for (String element : list) { System.out.println(element);}
// 坏的做法for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i));}5. 避免在循环中修改集合
在遍历集合时修改集合,可能会导致ConcurrentModificationException异常。
// 坏的做法for (String element : list) { if (element.equals("删除元素")) { list.remove(element); // 可能抛出ConcurrentModificationException }}
// 好的做法Iterator<String> iterator = list.iterator();while (iterator.hasNext()) { String element = iterator.next(); if (element.equals("删除元素")) { iterator.remove(); // 正确的删除方式 }}6. 使用集合的isEmpty()方法
检查集合是否为空时,使用isEmpty()方法而不是size() == 0,代码更简洁,可读性更好。
// 好的做法if (list.isEmpty()) { System.out.println("集合为空");}
// 坏的做法if (list.size() == 0) { System.out.println("集合为空");}7. 合理使用并发集合
在多线程环境下,使用线程安全的并发集合,而不是手动同步。
// 好的做法ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 坏的做法Map<String, Integer> map = Collections.synchronizedMap(new HashMap<>());8. 注意集合的线程安全性
大多数集合类不是线程安全的,在多线程环境下需要手动同步或使用线程安全的集合。
常见陷阱
1. Arrays.asList()的局限性
Arrays.asList()返回的是一个固定大小的列表,不支持添加和删除操作。
String[] array = {"a", "b", "c"};List<String> list = Arrays.asList(array);list.add("d"); // 抛出UnsupportedOperationException异常2. 集合与原始类型
集合只能存储对象,不能存储原始类型,需要使用包装类。
// 错误List<int> list = new ArrayList<>();
// 正确List<Integer> list = new ArrayList<>();3. ConcurrentModificationException
在遍历集合时修改集合,可能会抛出ConcurrentModificationException异常。
4. 内存泄漏
集合中存储的对象如果没有被正确清理,可能会导致内存泄漏。
5. 哈希码和equals方法
使用HashSet或HashMap时,元素或键应该正确实现hashCode()和equals()方法。
public class Person { private String name; private int age;
// 必须实现equals()和hashCode()方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); }
@Override public int hashCode() { return Objects.hash(name, age); }}6. 集合的大小
集合的size()方法返回的是int类型,对于非常大的集合,可能会导致溢出。
7. 自动装箱和拆箱的性能影响
在使用集合存储包装类时,自动装箱和拆箱可能会影响性能。
总结
Java集合框架是Java编程中的重要部分,它提供了丰富的接口和类,用于存储和操作数据集合。不同的集合类有不同的特点和适用场景,我们应该根据具体需求选择合适的集合。
本文介绍了Java集合框架的层次结构、常用集合类的特点和使用方法,以及集合的最佳实践和常见陷阱。希望本文能够帮助你更好地理解和使用Java的集合框架。
练习
-
编写一个程序,使用ArrayList存储学生信息,并实现添加、删除、修改和查询操作。
-
编写一个程序,使用LinkedList实现一个队列。
-
编写一个程序,使用HashSet存储不重复的整数,并实现添加、删除和遍历操作。
-
编写一个程序,使用TreeSet存储字符串,并按照字典序排序。
-
编写一个程序,使用HashMap存储学生的姓名和成绩,并实现添加、删除、修改和查询操作。
-
编写一个程序,使用LinkedHashMap存储元素,并验证它保持插入顺序。
-
编写一个程序,使用TreeMap存储键值对,并按照键的自然顺序排序。
-
编写一个程序,使用Collections工具类对List进行排序、查找和反转操作。
-
编写一个程序,使用并发集合(如ConcurrentHashMap)在多线程环境下操作数据。
-
编写一个程序,使用BlockingQueue实现生产者-消费者模式。
通过这些练习,你将更加熟悉Java的集合框架,为后续的学习做好准备。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!