Java 集合框架包含以下类和接口:
Collection 接口:Collection 是所有集合接口的基本接口,定义了集合对象的通用操作,如添加元素、删除元素、判断集合是否为空、获取集合大小等。
List 接口:List 是一个有序的集合接口,允许重复元素。常见的实现类有 ArrayList、LinkedList 和 Vector。
Set 接口:Set 是一个不允许重复元素的集合接口,常见的实现类有 HashSet、TreeSet 和 LinkedHashSet。
Queue 接口:Queue 是一个允许在队列尾部添加元素,在队列头部删除元素的集合接口。常见的实现类有 LinkedList 和 PriorityQueue。
Map 接口:Map 是一个 key-value 对的集合接口,允许使用 key 查找 value,常见的实现类有 HashMap、TreeMap 和 LinkedHashMap。
SortedSet 接口:SortedSet 是一个有序的不允许重复元素的集合接口,常见的实现类有 TreeSet。
SortedMap 接口:SortedMap 是一个有序的 key-value 对的集合接口,允许使用 key 查找 value,常见的实现类有 TreeMap。
除了这些基本接口和实现类,Java 集合框架还包含了一些辅助类和接口,如 Iterator、ListIterator、Comparator、Collections 等。这些类和接口可以帮助我们更方便地操作集合,提高开发效率。
List
List是一个有序的集合,可以通过索引访问其中的元素,可以有重复的元素。常见的实现类有ArrayList、LinkedList和Vector。
ArrayList是一个基于动态数组实现的List,当我们需要随机访问其中的元素时,它比LinkedList要快。而LinkedList则是基于双向链表实现的,当我们需要在其中进行添加或删除元素时,它比ArrayList要快。Vector与ArrayList类似,但它是线程安全的,所以当我们需要在多线程环境下使用时,可以选择使用Vector。
1.1 ArrayList
ArrayList 实现了 List 接口,可以动态地增加或缩小容量。内部使用数组来存储元素,所以支持随机访问,时间复杂度为 O(1)。
下面是一个简单的 ArrayList 示例:
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("orange");
System.out.println(list); // 输出 [apple, banana, orange]
1.2 LinkedList
LinkedList 也实现了 List 接口,但是使用链表来存储元素。相比于 ArrayList,LinkedList 更适合于插入和删除操作,因为只需要改变链表节点的指针指向即可,时间复杂度为 O(1)。
下面是一个简单的 LinkedList 示例:
List<String> list = new LinkedList<>();
list.add("apple");
list.add("banana");
list.add("orange");
System.out.println(list); // 输出 [apple, banana, orange]
1.3 Vector
Vector 也实现了 List 接口,与 ArrayList 相似,但是 Vector 是线程安全的。不过,由于线程安全的代价较高,所以在单线程场景下,建议使用 ArrayList。
下面是一个简单的 Vector 示例:
List<String> list = new Vector<>();
list.add("apple");
list.add("banana");
list.add("orange");
System.out.println(list); // 输出 [apple, banana, orange]
Set
Set是一个不允许有重复元素的集合。常见的实现类有HashSet、TreeSet和LinkedHashSet。
HashSet是基于散列表实现的Set,它是无序的,当我们需要进行快速查找元素时,可以选择使用它。TreeSet则是基于红黑树实现的Set,它是有序的,当我们需要进行排序操作时,可以选择使用它。LinkedHashSet是HashSet和LinkedList的混合体,它既可以快速查找元素,也可以保证元素的插入顺序。
2.1 HashSet
HashSet 内部使用 HashMap 实现,所以它的元素是无序的。如果要保持元素的插入顺序,建议使用 LinkedHashSet。
下面是一个简单的 HashSet 示例:
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("orange");
set.add("apple"); // 添加重复元素,但是不会被存储
System.out.println(set); // 输出 [banana, orange, apple]
2.2 TreeSet
TreeSet 内部使用红黑树实现,所以它的元素是有序的。不过,相比于 HashSet,TreeSet 的插入、删除、查找操作都比较耗时,时间复杂度为 O(logn)。
下面是一个简单的 TreeSet 示例:
Set<String> set = new TreeSet<>();
set.add("apple");
set.add("banana");
set.add("orange");
System.out.println(set); // 输出 [apple, banana, orange]
2.3 LinkedHashSet
LinkedHashSet 继承了 HashSet 的特性,但是它可以保持元素的插入顺序。因为内部使用了一个双向链表来维护元素的插入顺序,所以相比于 HashSet,LinkedHashSet 在插入和遍历时的性能略微低一些,但是可以在保持元素插入顺序的同时快速访问某个元素。
LinkedHashSet 的构造方法与 HashSet 相同,可以通过指定初始容量和负载因子来创建实例。它也实现了 Set 接口,提供了与 HashSet 相同的操作方法。
以下是一个使用 LinkedHashSet 的示例代码:
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("apple");
linkedHashSet.add("banana");
linkedHashSet.add("orange");
System.out.println(linkedHashSet); // [apple, banana, orange]
linkedHashSet.remove("banana");
System.out.println(linkedHashSet); // [apple, orange]
System.out.println(linkedHashSet.contains("apple")); // true
linkedHashSet.clear();
System.out.println(linkedHashSet); // []
从输出结果可以看到,LinkedHashSet 保持了元素插入的顺序,同时也提供了与 HashSet 相同的操作方法。
LinkedHashSet 在某些场景下非常有用,比如需要按照元素插入顺序遍历集合的情况。但是需要注意的是,由于 LinkedHashSet 内部使用了双向链表来维护元素插入顺序,因此它的内存占用比 HashSet 稍微高一些。
Map
Map是一个key-value对的集合,key和value都可以是任何类型的对象。常见的实现类有HashMap、TreeMap和LinkedHashMap。
HashMap是基于散列表实现的Map,它是无序的,当我们需要进行快速查找元素时,可以选择使用它。TreeMap则是基于红黑树实现的Map,它是有序的,当我们需要进行排序操作时,可以选择使用它。LinkedHashMap则是HashMap和LinkedList的混合体,它既可以快速查找元素,也可以保证元素的插入顺序。
在使用集合类时,我们需要注意它们的时间复杂度和空间复杂度,选择合适的实现类。同时,我们还需要注意集合类的线程安全问题,如果在多线程环境下使用集合类,需要选择线程安全的实现类或使用同步机制进行保护。
3.1 HashMap
HashMap是一种基于散列表实现的Map集合,它通过将键值对映射到一个桶数组中来存储元素。当我们需要查找元素时,HashMap会通过计算键的哈希值来快速定位到对应的桶,从而达到快速查找的目的。
HashMap的实现是非常高效的,查找元素的时间复杂度为O(1)。但是由于其内部是通过哈希函数来计算键的哈希值的,因此它的键必须实现hashCode()和equals()方法。hashCode()方法用来计算键的哈希值,equals()方法用来判断两个键是否相等。如果两个键相等,则它们的哈希值也必须相等。
在使用HashMap时,我们需要注意以下几点:
初始容量和负载因子:HashMap的初始容量和负载因子可以在创建HashMap对象时指定,初始容量指的是HashMap中初始的桶的数量,负载因子指的是HashMap在自动扩容之前可以达到的平均填充因子。在实际使用中,我们需要根据元素的数量来选择合适的初始容量和负载因子,以保证HashMap的性能和空间利用率。
线程安全问题:HashMap是非线程安全的,如果在多线程环境下使用HashMap,需要进行同步保护。
遍历元素:HashMap提供了三种遍历元素的方式,分别是使用迭代器、使用键集合和值集合来遍历。使用迭代器遍历时,HashMap并不保证元素的顺序;使用键集合遍历时,HashMap保证元素的顺序与插入顺序相同;使用值集合遍历时,HashMap保证元素的顺序与键集合相同。
下面是一个使用HashMap的例子:
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 35);
System.out.println("Size of map: " + map.size());
System.out.println("Age of Alice: " + map.get("Alice"));
for (String key : map.keySet()) {
System.out.println(key + " -> " + map.get(key));
}
}
}
输出:
Size of map: 3
Age of Alice: 25
Alice -> 25
Bob -> 30
Charlie -> 35
3.2 TreeMap
TreeMap 是一个基于红黑树实现的有序 Map,它根据键的自然顺序进行排序,或者根据用户指定的 Comparator 进行排序。TreeMap 内部维护了一棵红黑树,每个节点都是一个键值对。
TreeMap 的主要特点包括:
排序:TreeMap 内部会自动根据键进行排序,可以自定义排序方式。
线程不安全:和 HashMap 一样,TreeMap 是非线程安全的。
实现 NavigableMap 接口:TreeMap 实现了 NavigableMap 接口,提供了一些便于操作有序映射的方法。
数据结构是红黑树:红黑树是一种自平衡二叉查找树,保证了插入、删除、查找的时间复杂度均为 O(logN)。
使用 TreeMap 需要注意以下几点:
键不能为 null:TreeMap 不允许键为 null,因为排序依赖键的值,如果键为 null,无法进行比较。
自然排序和自定义排序:如果键实现了 Comparable 接口,则 TreeMap 会使用自然排序,否则需要提供一个 Comparator 进行自定义排序。
性能:虽然 TreeMap 提供了有序的功能,但是在插入、删除等操作时,需要进行红黑树的操作,因此性能不如 HashMap。
下面是一个使用 TreeMap 的例子:
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String[] args) {
TreeMap<String, Integer> map = new TreeMap<>();
// 添加元素
map.put("apple", 10);
map.put("banana", 5);
map.put("orange", 8);
// 遍历元素
for (String key : map.keySet()) {
System.out.println(key + " = " + map.get(key));
}
}
}
输出结果为:
apple = 10
banana = 5
orange = 8
从输出结果可以看出,TreeMap 内部按照键进行了排序,输出结果按照键的自然顺序进行排序。
3.3 LinkedHashMap
LinkedHashMap是Java中的一个Map集合,它是HashMap和LinkedList的结合体,具有HashMap的查询速度和LinkedList的顺序性,是一种既可以快速查找元素,又可以保证元素插入顺序的集合。
LinkedHashMap继承了HashMap的所有特性,也使用了哈希表来存储元素,但与HashMap不同的是,LinkedHashMap还维护了一个双向链表,用来保持插入顺序。每个Entry节点包含了指向前一个节点和后一个节点的指针,这样就可以按照插入的顺序遍历集合中的元素。
使用LinkedHashMap的时候,我们可以根据插入顺序或访问顺序来遍历元素,使用entrySet()方法可以获得一个包含Entry对象的Set集合,通过遍历这个集合,就可以按照插入顺序或访问顺序来遍历元素。如果希望按照访问顺序来遍历元素,可以在构造LinkedHashMap对象时传入accessOrder参数,并将其设置为true。
LinkedHashMap的性能和HashMap的性能基本一致,但是由于它需要维护双向链表,所以在内存消耗上会比HashMap多一些。如果我们需要保证元素插入顺序,或者需要按照插入顺序或访问顺序来遍历元素,可以考虑使用LinkedHashMap。
需要注意的是,由于LinkedHashMap维护了一个链表,所以它的插入、删除和查找操作都会比HashMap略微慢一些,尤其是在大量元素的情况下。因此,在选择集合实现类时,需要根据具体的需求来选择适合的实现类。
总之,LinkedHashMap是一种具有顺序性的Map集合,它能够快速查找元素,同时还能保证元素的插入顺序。在一些需要保证元素顺序的场合中,它可以起到很好的作用。
下面是一个使用LinkedHashMap的示例代码,实现了一个简单的LRU缓存:
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int MAX_ENTRIES;
public LRUCache(int capacity) {
super(capacity, 0.75f, true);
MAX_ENTRIES = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > MAX_ENTRIES;
}
public static void main(String[] args) {
LRUCache<Integer, String> cache = new LRUCache<>(3);
cache.put(1, "one");
cache.put(2, "two");
cache.put(3, "three");
System.out.println(cache);
cache.put(4, "four");
System.out.println(cache);
cache.get(2);
System.out.println(cache);
cache.put(5, "five");
System.out.println(cache);
}
}
在这个例子中,LRUCache继承了LinkedHashMap,用于实现一个基于LRU算法的缓存。构造方法传入了一个容量,表示缓存中最多能够容纳的键值对数量。在removeEldestEntry方法中,如果当前的键值对数量超过了容量,就会返回true,表示需要删除最老的键值对,以保证容量不超限。在put和get操作中,由于继承了LinkedHashMap,所以会自动维护元素的插入顺序,从而实现了LRU算法的效果。