Java集合框架:深入理解List与Set及其实现类
在Java集合框架中,List和Set是两个最常用的接口,它们各自有着独特的特点和适用场景。本文将详细介绍List和Set的区别,以及HashSet、LinkedHashSet和TreeSet这三个Set接口的主要实现类的特点、使用方法和适用场景。
一、List和Set集合的区别
1. 有序性
- List:保证按插入顺序排序,元素的有序性是按照添加的先后顺序确定的
- Set:存储和取出顺序不一致(除LinkedHashSet外)
2. 唯一性
- List:元素可以重复,允许存储相同的元素
- Set:元素唯一,不允许存储重复的元素
3. 获取元素方式
- List:可以通过索引直接操作元素,提供了基于位置的访问方法
- Set:不能根据索引获取元素,只能通过迭代器或增强for循环遍历
二、HashSet集合
1. HashSet集合的特点
- HashSet底层使用的是HashMap实现
- 不能保证元素的顺序,元素是无序的
- 不能有重复的元素
- 集合元素值允许为null
- 线程不安全
- 性能在Set实现类中通常是最好的
2. HashSet常用方法
① 添加元素:add(Object o)
HashSet<String> set = new HashSet<String>();
set.add("青城");
set.add("博雅");
System.out.println(set); // 输出顺序不确定② 获取元素数量:size()
System.out.println(set.size()); // 输出元素个数
③ 删除元素:remove(Object o)
System.out.println(set.remove("博雅")); // 删除成功返回true
④ 判断集合是否为空:isEmpty()
System.out.println(set.isEmpty()); // 集合为空返回true
⑤ 清空集合:clear()
set.clear(); // 移除所有元素
⑥ 使用迭代器遍历:iterator()
Iterator<String> ite = set.iterator();
while(ite.hasNext()) {System.out.println(ite.next());
}⑦ 判断是否包含元素:contains(Object o)
System.out.println(set.contains("青城")); // 包含返回true
⑧ 使用增强for循环遍历
for (String name : set) {
System.out.println(name);
}
三、LinkedHashSet集合
1. LinkedHashSet集合的特点
- 元素有序且唯一
- 链表保证元素有序(插入顺序)
- 哈希表保证元素唯一
- 线程不安全
- 性能略低于HashSet,但高于TreeSet
2. LinkedHashSet的基本使用
① 创建LinkedHashSet
// 创建一个空的LinkedHashSet
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();// 创建具有初始容量的LinkedHashSet
LinkedHashSet<Integer> linkedHashSetWithCapacity = new LinkedHashSet<>(10);// 创建具有初始容量和负载因子的LinkedHashSet
LinkedHashSet<Double> linkedHashSetWithLoadFactor = new LinkedHashSet<>(10, 0.75f);② 添加元素
linkedHashSet.add("Apple");
linkedHashSet.add("Banana");
linkedHashSet.add("Cherry");
linkedHashSet.add("Apple"); // 重复元素,不会添加
System.out.println(linkedHashSet); // 输出: [Apple, Banana, Cherry] 保持插入顺序③ 删除元素
linkedHashSet.remove("Banana");
System.out.println(linkedHashSet); // 输出: [Apple, Cherry]linkedHashSet.clear(); // 清空所有元素④ 检查元素是否存在
boolean containsApple = linkedHashSet.contains("Apple");
System.out.println("Contains Apple: " + containsApple);⑤ 获取集合大小
int size = linkedHashSet.size();
System.out.println("Size: " + size);⑥ 遍历LinkedHashSet
// 使用for-each循环遍历
for (String fruit : linkedHashSet) {System.out.println(fruit);
}// 使用Iterator遍历
Iterator<String> iterator = linkedHashSet.iterator();
while (iterator.hasNext()) {System.out.println(iterator.next());
}⑦ 转换为数组
String[] array = linkedHashSet.toArray(new String[0]);
for (String s : array) {System.out.println(s);
}⑧ 其他常用方法
// 检查是否为空
boolean isEmpty = linkedHashSet.isEmpty();// 保留与指定集合相同的元素
LinkedHashSet<String> fruits = new LinkedHashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Cherry");LinkedHashSet<String> toRetain = new LinkedHashSet<>();
toRetain.add("Apple");
toRetain.add("Cherry");fruits.retainAll(toRetain); // 只保留Apple和Cherry// 删除与指定集合相同的元素
fruits.removeAll(toRetain);四、TreeSet集合
1. TreeSet集合的特点
- TreeSet内部使用的是TreeMap,基于红黑树实现
- 元素:TreeSet会对插入的数据排序,所以输入顺序和输出顺序不一致
- 插入数据内部有两种排序方法:自然排序(默认)和定制排序
- 值不能为null
- 值唯一
- 线程不安全
- 性能在三者中通常是最低的
2. TreeSet的基本使用
① 插入是按自然顺序排序的
TreeSet ts = new TreeSet();
ts.add("agg");
ts.add("abcd");
ts.add("ffas");
Iterator it = ts.iterator();
while(it.hasNext()) {System.out.println(it.next());
}
// 输出:按照字典序排序
// abcd
// agg
// ffas② 插入自定义对象需要实现Comparable接口
class Person implements Comparable {String name;int age;Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Object o) {Person p = (Person) o;// 先对姓名字典序比较,如果相同,比较年龄if(this.name.compareTo(p.name) != 0) {return this.name.compareTo(p.name);} else {if(this.age > p.age) return 1;else if(this.age < p.age) return -1;}return 0;}
}public class Test {public static void main(String args[]) {TreeSet ts = new TreeSet();ts.add(new Person("agg", 21));ts.add(new Person("abcd", 12));ts.add(new Person("ffas", 8));ts.add(new Person("agg", 12));Iterator it = ts.iterator();while(it.hasNext()) {Person p = (Person) it.next();System.out.println(p.name + ":" + p.age);}}
}
// 输出:
// abcd:12
// agg:12
// agg:21
// ffas:8五、HashSet、LinkedHashSet、TreeSet的使用场景
1. HashSet
- 适用场景:在大多数情况下,HashSet是Set接口的首选实现。它的性能基本上比LinkedHashSet和TreeSet要好,特别是添加和查询操作,这也是使用最多的两个操作。
- 特点:不关心元素的顺序,只关心元素是否存在。
2. LinkedHashSet
- 适用场景:只有当要求插入顺序和取出顺序一致的时候才使用LinkedHashSet。LinkedHashSet的查询性能稍慢于HashSet,但它可以维持元素的添加顺序。
- 特点:需要保持插入顺序,但又需要Set的唯一性特性。
3. TreeSet
- 适用场景:只有在需要对元素进行排序时使用。当需要按照自然顺序或者自定义顺序对元素进行排序时,TreeSet是最佳选择。
- 特点:元素有序,但性能相对较低。
总结
在Java集合框架中,List和Set各有其独特的用途和特点。List关注的是元素的索引和顺序,允许重复元素;而Set关注的是元素的唯一性,不关心元素的顺序(除LinkedHashSet外)。
对于Set的实现类:
- 如果不需要保持任何顺序,优先使用HashSet
- 如果需要保持插入顺序,使用LinkedHashSet
- 如果需要元素按照自然顺序或自定义顺序排序,使用TreeSet
在实际开发中,根据具体需求选择合适的集合类非常重要,这不仅能提高代码的效率,也能使代码更加清晰易懂。理解这些集合类的底层实现原理和特点,有助于我们做出更加合理的选择。
